Early handling of dispatched events

A possible approach to handling the various triggers for recategorizing rooms.
pull/21833/head
Travis Ralston 2020-05-04 09:06:34 -06:00
parent 09b7f39df8
commit 5dda7f02cf
6 changed files with 75 additions and 37 deletions

View File

@ -28,7 +28,6 @@ import { Dispatcher } from "flux";
import { ActionPayload } from "../../../dispatcher-types"; import { ActionPayload } from "../../../dispatcher-types";
import dis from "../../../dispatcher"; import dis from "../../../dispatcher";
import { RoomSublist2 } from "./RoomSublist2"; import { RoomSublist2 } from "./RoomSublist2";
import { isNullOrUndefined } from "matrix-js-sdk/lib/src/utils";
interface IProps { interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void; onKeyDown: (ev: React.KeyboardEvent) => void;

View File

@ -18,12 +18,14 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { ActionPayload, defaultDispatcher } from "../../dispatcher-types"; import { ActionPayload, defaultDispatcher } from "../../dispatcher-types";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import { DefaultTagID, OrderedDefaultTagIDs, TagID } from "./models"; import { DefaultTagID, OrderedDefaultTagIDs, RoomUpdateCause, TagID } from "./models";
import { Algorithm } from "./algorithms/list_ordering/Algorithm"; import { Algorithm } from "./algorithms/list_ordering/Algorithm";
import TagOrderStore from "../TagOrderStore"; import TagOrderStore from "../TagOrderStore";
import { getListAlgorithmInstance } from "./algorithms/list_ordering"; import { getListAlgorithmInstance } from "./algorithms/list_ordering";
import { AsyncStore } from "../AsyncStore"; import { AsyncStore } from "../AsyncStore";
import { ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models"; import { ITagMap, ITagSortingMap, ListAlgorithm, SortAlgorithm } from "./algorithms/models";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
interface IState { interface IState {
tagsEnabled?: boolean; tagsEnabled?: boolean;
@ -123,7 +125,14 @@ class _RoomListStore extends AsyncStore<ActionPayload> {
await this.regenerateAllLists(); // regenerate the lists now await this.regenerateAllLists(); // regenerate the lists now
} }
} else if (payload.action === 'MatrixActions.Room.receipt') { }
if (!this.algorithm) {
// This shouldn't happen because `initialListsGenerated` implies we have an algorithm.
throw new Error("Room list store has no algorithm to process dispatcher update with");
}
if (payload.action === 'MatrixActions.Room.receipt') {
// First see if the receipt event is for our own user. If it was, trigger // First see if the receipt event is for our own user. If it was, trigger
// a room update (we probably read the room on a different device). // a room update (we probably read the room on a different device).
// noinspection JSObjectNullOrUndefined - this.matrixClient can't be null by this point in the lifecycle // noinspection JSObjectNullOrUndefined - this.matrixClient can't be null by this point in the lifecycle
@ -132,23 +141,45 @@ class _RoomListStore extends AsyncStore<ActionPayload> {
const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {}); const receiptUsers = Object.keys(payload.event.getContent()[eventId]['m.read'] || {});
if (receiptUsers.includes(myUserId)) { if (receiptUsers.includes(myUserId)) {
// TODO: Update room now that it's been read // TODO: Update room now that it's been read
console.log(payload);
return; return;
} }
} }
} else if (payload.action === 'MatrixActions.Room.tags') { } else if (payload.action === 'MatrixActions.Room.tags') {
// TODO: Update room from tags // TODO: Update room from tags
} else if (payload.action === 'MatrixActions.room.timeline') { console.log(payload);
// TODO: Update room from new events } else if (payload.action === 'MatrixActions.Room.timeline') {
const eventPayload = (<any>payload); // TODO: Type out the dispatcher types
// Ignore non-live events (backfill)
if (!eventPayload.isLiveEvent || !payload.isLiveUnfilteredRoomTimelineEvent) return;
const roomId = eventPayload.event.getRoomId();
const room = this.matrixClient.getRoom(roomId);
await this.handleRoomUpdate(room, RoomUpdateCause.Timeline);
} else if (payload.action === 'MatrixActions.Event.decrypted') { } else if (payload.action === 'MatrixActions.Event.decrypted') {
// TODO: Update room from decrypted event // TODO: Update room from decrypted event
console.log(payload);
} else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') { } else if (payload.action === 'MatrixActions.accountData' && payload.event_type === 'm.direct') {
// TODO: Update DMs // TODO: Update DMs
console.log(payload);
} else if (payload.action === 'MatrixActions.Room.myMembership') { } else if (payload.action === 'MatrixActions.Room.myMembership') {
// TODO: Update room from membership change // TODO: Update room from membership change
} else if (payload.action === 'MatrixActions.room') { console.log(payload);
} else if (payload.action === 'MatrixActions.Room') {
// TODO: Update room from creation/join // TODO: Update room from creation/join
console.log(payload);
} else if (payload.action === 'view_room') { } else if (payload.action === 'view_room') {
// TODO: Update sticky room // TODO: Update sticky room
console.log(payload);
}
}
private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<any> {
const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
if (shouldUpdate) {
console.log(`[DEBUG] Room "${room.name}" (${room.roomId}) triggered by ${cause} requires list update`);
this.emit(LISTS_UPDATE_EVENT, this);
} }
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { DefaultTagID, TagID } from "../../models"; import { DefaultTagID, RoomUpdateCause, TagID } from "../../models";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils"; import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { EffectiveMembership, splitRoomsByMembership } from "../../membership"; import { EffectiveMembership, splitRoomsByMembership } from "../../membership";
@ -33,9 +33,8 @@ export abstract class Algorithm {
protected cached: ITagMap = {}; protected cached: ITagMap = {};
protected sortAlgorithms: ITagSortingMap; protected sortAlgorithms: ITagSortingMap;
protected rooms: Room[] = []; protected rooms: Room[] = [];
protected roomsByTag: { protected roomIdsToTags: {
// @ts-ignore - TS wants this to be a string but we know better [roomId: string]: TagID[];
[tagId: TagID]: Room[];
} = {}; } = {};
protected constructor() { protected constructor() {
@ -132,6 +131,25 @@ export abstract class Algorithm {
await this.generateFreshTags(newTags); await this.generateFreshTags(newTags);
this.cached = newTags; this.cached = newTags;
this.updateTagsFromCache();
}
/**
* Updates the roomsToTags map
*/
protected updateTagsFromCache() {
const newMap = {};
const tags = Object.keys(this.cached);
for (const tagId of tags) {
const rooms = this.cached[tagId];
for (const room of rooms) {
if (!newMap[room.roomId]) newMap[room.roomId] = [];
newMap[room.roomId].push(tagId);
}
}
this.roomIdsToTags = newMap;
} }
/** /**
@ -144,27 +162,16 @@ export abstract class Algorithm {
*/ */
protected abstract generateFreshTags(updatedTagMap: ITagMap): Promise<any>; protected abstract generateFreshTags(updatedTagMap: ITagMap): Promise<any>;
/**
* Called when the Algorithm wants a whole tag to be reordered. Typically this will
* be done whenever the tag's scope changes (added/removed rooms).
* @param {TagID} tagId The tag ID which changed.
* @param {Room[]} rooms The rooms within the tag, unordered.
* @returns {Promise<Room[]>} Resolves to the ordered rooms in the tag.
*/
// TODO: Do we need this?
protected abstract regenerateTag(tagId: TagID, rooms: Room[]): Promise<Room[]>;
/** /**
* Asks the Algorithm to update its knowledge of a room. For example, when * Asks the Algorithm to update its knowledge of a room. For example, when
* a user tags a room, joins/creates a room, or leaves a room the Algorithm * a user tags a room, joins/creates a room, or leaves a room the Algorithm
* should be told that the room's info might have changed. The Algorithm * should be told that the room's info might have changed. The Algorithm
* may no-op this request if no changes are required. * may no-op this request if no changes are required.
* @param {Room} room The room which might have affected sorting. * @param {Room} room The room which might have affected sorting.
* @param {RoomUpdateCause} cause The reason for the update being triggered.
* @returns {Promise<boolean>} A promise which resolve to true or false * @returns {Promise<boolean>} A promise which resolve to true or false
* depending on whether or not getOrderedRooms() should be called after * depending on whether or not getOrderedRooms() should be called after
* processing. * processing.
*/ */
// TODO: Take a ReasonForChange to better predict the behaviour? public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean>;
// TODO: Intercept here and handle tag changes automatically? May be best to let the impl do that.
public abstract handleRoomUpdate(room: Room): Promise<boolean>;
} }

View File

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import { Algorithm } from "./Algorithm"; import { Algorithm } from "./Algorithm";
import { DefaultTagID } from "../../models";
import { ITagMap } from "../models"; import { ITagMap } from "../models";
/** /**
@ -33,11 +32,7 @@ export class ChaoticAlgorithm extends Algorithm {
return Promise.resolve(); return Promise.resolve();
} }
protected async regenerateTag(tagId: string | DefaultTagID, rooms: []): Promise<[]> { public async handleRoomUpdate(room, cause): Promise<boolean> {
return Promise.resolve(rooms);
}
public async handleRoomUpdate(room): Promise<boolean> {
return Promise.resolve(false); return Promise.resolve(false);
} }
} }

View File

@ -17,9 +17,9 @@ limitations under the License.
import { Algorithm } from "./Algorithm"; import { Algorithm } from "./Algorithm";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { DefaultTagID, TagID } from "../../models"; import { DefaultTagID, RoomUpdateCause, TagID } from "../../models";
import { ITagMap, SortAlgorithm } from "../models"; import { ITagMap, SortAlgorithm } from "../models";
import { getSortingAlgorithmInstance, sortRoomsWithAlgorithm } from "../tag_sorting"; import { sortRoomsWithAlgorithm } from "../tag_sorting";
import * as Unread from '../../../../Unread'; import * as Unread from '../../../../Unread';
/** /**
@ -194,11 +194,12 @@ export class ImportanceAlgorithm extends Algorithm {
} }
} }
protected async regenerateTag(tagId: string | DefaultTagID, rooms: []): Promise<[]> { public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise<boolean> {
return Promise.resolve(rooms); const tags = this.roomIdsToTags[room.roomId];
} if (!tags) {
console.warn(`No tags known for "${room.name}" (${room.roomId})`);
public async handleRoomUpdate(room): Promise<boolean> { return false;
return Promise.resolve(false); }
return false;
} }
} }

View File

@ -34,3 +34,8 @@ export const OrderedDefaultTagIDs = [
]; ];
export type TagID = string | DefaultTagID; export type TagID = string | DefaultTagID;
export enum RoomUpdateCause {
Timeline = "TIMELINE",
RoomRead = "ROOM_READ",
}