Implement filtering within skip list

This commit is contained in:
R Midhun Suresh
2025-02-27 14:19:46 +05:30
parent 57f32fc2be
commit 33d9427aad
5 changed files with 98 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import type { EmptyObject, Room } from "matrix-js-sdk/src/matrix";
import type { MatrixDispatcher } from "../../dispatcher/dispatcher";
import type { ActionPayload } from "../../dispatcher/payloads";
import type { Filter } from "./skip-list/filters";
import { AsyncStoreWithClient } from "../AsyncStoreWithClient";
import SettingsStore from "../../settings/SettingsStore";
import { VisibilityProvider } from "../room-list/filters/VisibilityProvider";
@@ -16,10 +17,12 @@ import { LISTS_UPDATE_EVENT } from "../room-list/RoomListStore";
import { RoomSkipList } from "./skip-list/RoomSkipList";
import { RecencySorter } from "./skip-list/sorters/RecencySorter";
import { AlphabeticSorter } from "./skip-list/sorters/AlphabeticSorter";
import { FavouriteFilter } from "./skip-list/filters/FavouriteFilter";
export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
private roomSkipList?: RoomSkipList;
private readonly msc3946ProcessDynamicPredecessor: boolean;
private filters: Filter[] = [new FavouriteFilter()];
public constructor(dispatcher: MatrixDispatcher) {
super(dispatcher);
@@ -54,7 +57,7 @@ export class RoomListStoreV3Class extends AsyncStoreWithClient<EmptyObject> {
protected async onReady(): Promise<any> {
if (this.roomSkipList?.initialized || !this.matrixClient) return;
const sorter = new RecencySorter(this.matrixClient.getSafeUserId());
this.roomSkipList = new RoomSkipList(sorter);
this.roomSkipList = new RoomSkipList(sorter, this.filters);
const rooms = this.getRooms();
this.roomSkipList.seed(rooms);
this.emit(LISTS_UPDATE_EVENT);

View File

@@ -6,6 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Filter, Filters } from "./filters";
/**
* Room skip list stores room nodes.
@@ -26,4 +27,18 @@ export class RoomNode {
* eg: previous[i] gives the previous room node from this room node in level i.
*/
public previous: RoomNode[] = [];
/**
* Aggregates all the filters that apply to this room.
* eg: if filters[Filter.FavouriteFilter] is true, then this room is a favourite
* room.
*/
public filters: Map<Filters, boolean> = new Map();
public calculateFilters(filters: Filter[]): void {
for (const filter of filters) {
const matchesFilter = filter.matches(this.room);
this.filters.set(filter.key, matchesFilter);
}
}
}

View File

@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Sorter } from "./sorters";
import type { Filter, Filters } from "./filters";
import { RoomNode } from "./RoomNode";
import { shouldPromote } from "./utils";
import { Level } from "./Level";
@@ -20,7 +21,10 @@ export class RoomSkipList implements Iterable<Room> {
private roomNodeMap: Map<string, RoomNode> = new Map();
public initialized: boolean = false;
public constructor(private sorter: Sorter) {}
public constructor(
private sorter: Sorter,
private filters: Filter[] = [],
) {}
private reset(): void {
this.levels = [new Level(0)];
@@ -35,6 +39,7 @@ export class RoomSkipList implements Iterable<Room> {
const sortedRoomNodes = this.sorter.sort(rooms).map((room) => new RoomNode(room));
let currentLevel = this.levels[0];
for (const node of sortedRoomNodes) {
node.calculateFilters(this.filters);
currentLevel.setNext(node);
this.roomNodeMap.set(node.room.roomId, node);
}
@@ -81,6 +86,7 @@ export class RoomSkipList implements Iterable<Room> {
this.removeRoom(room);
const newNode = new RoomNode(room);
newNode.calculateFilters(this.filters);
this.roomNodeMap.set(room.roomId, newNode);
/**
@@ -159,6 +165,10 @@ export class RoomSkipList implements Iterable<Room> {
return new SortedRoomIterator(this.levels[0].head!);
}
public getFiltered(filterKeys: Filters[]): SortedFilteredIterator {
return new SortedFilteredIterator(this.levels[0].head!, filterKeys);
}
/**
* The number of rooms currently in the skip list.
*/
@@ -179,3 +189,26 @@ class SortedRoomIterator implements Iterator<Room> {
};
}
}
class SortedFilteredIterator implements Iterator<Room> {
public constructor(
private current: RoomNode,
private filterKeys: Filters[],
) {}
public [Symbol.iterator](): SortedFilteredIterator {
return this;
}
public next(): IteratorResult<Room> {
let current = this.current;
while (current && this.filterKeys.some((key) => !current.filters.get(key))) {
current = current.next[0];
}
if (!current) return { value: undefined, done: true };
this.current = current.next[0];
return {
value: current.room,
};
}
}

View File

@@ -0,0 +1,20 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import type { Room } from "matrix-js-sdk/src/matrix";
import type { Filter } from ".";
import { Filters } from ".";
import { DefaultTagID } from "../../../room-list/models";
export class FavouriteFilter implements Filter {
public matches(room: Room): boolean {
return !!room.tags[DefaultTagID.Favourite];
}
public get key(): Filters.FavouriteFilter {
return Filters.FavouriteFilter;
}
}

View File

@@ -0,0 +1,25 @@
/*
Copyright 2025 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import type { Room } from "matrix-js-sdk/src/matrix";
export const enum Filters {
FavouriteFilter,
}
export interface Filter {
/**
* Boolean return value indicates whether this room satisfies
* the filter condition.
*/
matches(room: Room): boolean;
/**
* An unique string used to identify a given filter.
*/
key: Filters;
}