Browse Source

Improved sliced array

Added tests for sliced array
Fix scrolling foreign channels from top
Fix loading top comments
Load new history of foreign channels every 30 seconds
master
Eduard Kuzmenko 4 years ago
parent
commit
ff06f0305b
  1. 61
      src/components/chat/bubbles.ts
  2. 167
      src/helpers/slicedArray.ts
  3. 8
      src/lib/appManagers/appImManager.ts
  4. 170
      src/lib/appManagers/appMessagesManager.ts
  5. 2
      src/lib/appManagers/appPhotosManager.ts
  6. 7
      src/lib/storages/dialogs.ts
  7. 121
      src/tests/slicedArray.test.ts

61
src/components/chat/bubbles.ts

@ -130,6 +130,8 @@ export default class ChatBubbles {
public isFirstLoad = true; public isFirstLoad = true;
private needReflowScroll: boolean; private needReflowScroll: boolean;
private fetchNewPromise: Promise<void>;
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) { constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) {
//this.chat.log.error('Bubbles construction'); //this.chat.log.error('Bubbles construction');
@ -1399,6 +1401,7 @@ export default class ChatBubbles {
this.messagesQueuePromise = null; this.messagesQueuePromise = null;
this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined; this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined;
this.fetchNewPromise = undefined;
if(this.stickyIntersector) { if(this.stickyIntersector) {
this.stickyIntersector.disconnect(); this.stickyIntersector.disconnect();
@ -1618,8 +1621,7 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, !isJump); this.chat.dispatchEvent('setPeer', lastMsgId, !isJump);
const isFetchIntervalNeeded = () => peerId < 0 && !this.appChatsManager.isInChat(peerId) && false; const needFetchInterval = this.appMessagesManager.isFetchIntervalNeeded(peerId);
const needFetchInterval = isFetchIntervalNeeded();
const needFetchNew = savedPosition || needFetchInterval; const needFetchNew = savedPosition || needFetchInterval;
if(!needFetchNew) { if(!needFetchNew) {
// warning // warning
@ -1627,20 +1629,44 @@ export default class ChatBubbles {
this.scrollable.loadedAll.bottom = true; this.scrollable.loadedAll.bottom = true;
} }
} else { } else {
const middleware = this.getMiddleware();
Promise.all([setPeerPromise, getHeavyAnimationPromise()]).then(() => { Promise.all([setPeerPromise, getHeavyAnimationPromise()]).then(() => {
if(!middleware()) {
return;
}
this.scrollable.checkForTriggers(); this.scrollable.checkForTriggers();
if(needFetchInterval) { if(needFetchInterval) {
const middleware = this.getMiddleware(); const f = () => {
const interval = window.setInterval(() => { this.fetchNewPromise = new Promise<void>((resolve) => {
if(!middleware() || !isFetchIntervalNeeded()) { if(!middleware() || !this.appMessagesManager.isFetchIntervalNeeded(peerId)) {
clearInterval(interval); resolve();
return; return;
} }
this.appMessagesManager.getNewHistory(peerId, this.chat.threadId).then((historyStorage) => {
if(!middleware()) {
resolve();
return;
}
const slice = historyStorage.history.slice;
const isBottomEnd = slice.isEnd(SliceEnd.Bottom);
if(this.scrollable.loadedAll.bottom !== isBottomEnd) {
this.scrollable.loadedAll.bottom = isBottomEnd;
this.onScroll();
}
setTimeout(f, 30e3);
resolve();
});
}).finally(() => {
this.fetchNewPromise = undefined;
});
};
this.scrollable.loadedAll.bottom = false; f();
this.loadMoreHistory(false);
}, 30e3);
} }
}); });
} }
@ -2626,10 +2652,16 @@ export default class ChatBubbles {
} */ } */
const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId);
if(history.includes(historyStorage.maxId)) { const firstSlice = historyStorage.history.first;
const lastSlice = historyStorage.history.last;
if(firstSlice.isEnd(SliceEnd.Bottom) && history.includes(firstSlice[0])) {
this.scrollable.loadedAll.bottom = true; this.scrollable.loadedAll.bottom = true;
} }
if(lastSlice.isEnd(SliceEnd.Top) && history.includes(lastSlice[lastSlice.length - 1])) {
this.scrollable.loadedAll.top = true;
}
//console.time('appImManager render history'); //console.time('appImManager render history');
return new Promise<boolean>((resolve, reject) => { return new Promise<boolean>((resolve, reject) => {
@ -2818,8 +2850,9 @@ export default class ChatBubbles {
additionMsgIds = [additionMsgId]; additionMsgIds = [additionMsgId];
} else { } else {
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
if(historyStorage.history.length < loadCount && !historyStorage.history.slice.isEnd(SliceEnd.Both)) { const slice = historyStorage.history.slice;
additionMsgIds = historyStorage.history.slice.slice(); if(slice.length < loadCount && !slice.isEnd(SliceEnd.Both)) {
additionMsgIds = slice.slice();
// * filter last album, because we don't know is it the last item // * filter last album, because we don't know is it the last item
for(let i = additionMsgIds.length - 1; i >= 0; --i) { for(let i = additionMsgIds.length - 1; i >= 0; --i) {

167
src/helpers/slicedArray.ts

@ -4,6 +4,8 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { MOUNT_CLASS_TO } from "../config/debug";
/** /**
* Descend sorted storage * Descend sorted storage
*/ */
@ -14,15 +16,19 @@ export enum SliceEnd {
None = 0, None = 0,
Top = 1, Top = 1,
Bottom = 2, Bottom = 2,
Both = 4 Both = SliceEnd.Top | SliceEnd.Bottom
}; };
export interface Slice extends Array<ItemType> { export interface Slice extends Array<ItemType> {
slicedArray: SlicedArray; //slicedArray: SlicedArray;
end: SliceEnd; end: SliceEnd;
isEnd: (side: SliceEnd) => boolean; isEnd: (side: SliceEnd) => boolean;
setEnd: (side: SliceEnd) => void; setEnd: (side: SliceEnd) => void;
unsetEnd: (side: SliceEnd) => void;
slice: (from?: number, to?: number) => Slice;
splice: (start: number, deleteCount: number, ...items: ItemType[]) => Slice;
} }
export interface SliceConstructor { export interface SliceConstructor {
@ -35,51 +41,84 @@ export default class SlicedArray {
private sliceConstructor: SliceConstructor; private sliceConstructor: SliceConstructor;
constructor() { constructor() {
const self = this; // @ts-ignore
this.sliceConstructor = class Slice extends Array<ItemType> implements Slice { this.sliceConstructor = SlicedArray.getSliceConstructor(this);
slicedArray: SlicedArray;
const first = this.constructSlice();
//first.setEnd(SliceEnd.Bottom);
this.slices = [first];
}
private static getSliceConstructor(slicedArray: SlicedArray) {
return class Slice extends Array<ItemType> implements Slice {
//slicedArray: SlicedArray;
end: SliceEnd = SliceEnd.None; end: SliceEnd = SliceEnd.None;
constructor(...items: ItemType[]) { /* constructor(...items: ItemType[]) {
super(...items); super(...items);
this.slicedArray = self; //this.slicedArray = slicedArray;
} } */
isEnd(side: SliceEnd) { isEnd(side: SliceEnd): boolean {
if(this.end & side) { if((this.end & side) === side) {
return true; return true;
} }/* else if(!this.slicedArray) {
return false;
} */
let isEnd = false;
if(side === SliceEnd.Top) { if(side === SliceEnd.Top) {
const slice = self.last; const slice = slicedArray.last;
return slice.end & side ? this.includes(slice[slice.length - 1]) || !slice.length : false; isEnd = slice.end & side ? this.includes(slice[slice.length - 1])/* || !slice.length */ : false;
} else if(side === SliceEnd.Bottom) { } else if(side === SliceEnd.Bottom) {
const slice = self.first; const slice = slicedArray.first;
return slice.end & side ? this.includes(slice[0]) || !slice.length : false; isEnd = slice.end & side ? this.includes(slice[0])/* || !slice.length */ : false;
}/* else if(side === SliceEnd.Both) { } else if(side === SliceEnd.Both) {
return this.isEnd(SliceEnd.Top) && this.isEnd(SliceEnd.Bottom);
}
} */ if(isEnd) {
this.setEnd(side);
}
return false; return isEnd;
} }
setEnd(side: SliceEnd) { setEnd(side: SliceEnd) {
this.end |= side; this.end |= side;
}
if(side !== SliceEnd.Both && this.end & SliceEnd.Top && this.end & SliceEnd.Bottom) { unsetEnd(side: SliceEnd) {
this.end |= SliceEnd.Both; this.end ^= side;
}
splice(start: number, deleteCount: number, ...items: ItemType[]) {
const ret = super.splice(start, deleteCount, ...items);
if(!this.length) {
const slices = slicedArray.slices as number[][];
const idx = slices.indexOf(this);
if(idx !== -1) {
if(slices.length === 1) { // left empty slice without ends
this.unsetEnd(SliceEnd.Both);
} else { // delete this slice
slices.splice(idx, 1);
}
}
} }
return ret;
} }
} }
const first = this.constructSlice();
first.setEnd(SliceEnd.Bottom);
this.slices = [first];
} }
public constructSlice(...items: ItemType[]) { public constructSlice(...items: ItemType[]) {
//const slice = new Slice(this, ...items); //const slice = new Slice(this, ...items);
const slice = new this.sliceConstructor(...items); // can't pass items directly to constructor because first argument is length
const slice = new this.sliceConstructor(items.length);
for(let i = 0, length = items.length; i < length; ++i) {
slice[i] = items[i];
}
return slice; return slice;
// ! code below will slow execution in 15 times // ! code below will slow execution in 15 times
@ -128,7 +167,7 @@ export default class SlicedArray {
*/ */
} }
public insertSlice(slice: ItemType[]) { public insertSlice(slice: ItemType[], flatten = true) {
if(!slice.length) { if(!slice.length) {
return; return;
} }
@ -136,15 +175,15 @@ export default class SlicedArray {
const first = this.slices[0]; const first = this.slices[0];
if(!first.length) { if(!first.length) {
first.push(...slice); first.push(...slice);
return; return first;
} }
const lowerBound = slice[slice.length - 1]; const lowerBound = slice[slice.length - 1];
const upperBound = slice[0]; const upperBound = slice[0];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1; let foundSlice: Slice, lowerIndex = -1, upperIndex = -1, foundSliceIndex = 0;
for(let i = 0; i < this.slices.length; ++i) { for(; foundSliceIndex < this.slices.length; ++foundSliceIndex) {
foundSlice = this.slices[i]; foundSlice = this.slices[foundSliceIndex];
lowerIndex = foundSlice.indexOf(lowerBound); lowerIndex = foundSlice.indexOf(lowerBound);
upperIndex = foundSlice.indexOf(upperBound); upperIndex = foundSlice.indexOf(upperBound);
@ -173,29 +212,38 @@ export default class SlicedArray {
} }
this.slices.splice(insertIndex, 0, this.constructSlice(...slice)); this.slices.splice(insertIndex, 0, this.constructSlice(...slice));
foundSliceIndex = insertIndex;
} }
this.flatten(); if(flatten) {
return this.flatten(foundSliceIndex);
}
} }
private flatten() { private flatten(foundSliceIndex: number) {
if(this.slices.length < 2) { if(this.slices.length >= 2) {
return; for(let i = 0, length = this.slices.length; i < (length - 1); ++i) {
} const prevSlice = this.slices[i];
const nextSlice = this.slices[i + 1];
for(let i = 0, length = this.slices.length; i < (length - 1); ++i) { const upperIndex = prevSlice.indexOf(nextSlice[0]);
const prevSlice = this.slices[i]; if(upperIndex !== -1) {
const nextSlice = this.slices[i + 1]; prevSlice.setEnd(nextSlice.end);
this.slices.splice(i + 1, 1);
const upperIndex = prevSlice.indexOf(nextSlice[0]); if(i < foundSliceIndex) {
if(upperIndex !== -1) { --foundSliceIndex;
prevSlice.setEnd(nextSlice.end); }
this.slices.splice(i + 1, 1);
length--;
this.insertSlice(nextSlice); --length; // respect array bounds
--i; // repeat from the same place
this.insertSlice(nextSlice, false);
}
} }
} }
return this.slices[foundSliceIndex];
} }
// * // *
@ -217,7 +265,7 @@ export default class SlicedArray {
} }
public findSlice(item: ItemType) { public findSlice(item: ItemType) {
for(let i = 0; i < this.slices.length; ++i) { for(let i = 0, length = this.slices.length; i < length; ++i) {
const slice = this.slices[i]; const slice = this.slices[i];
const index = slice.indexOf(item); const index = slice.indexOf(item);
if(index !== -1) { if(index !== -1) {
@ -295,6 +343,8 @@ export default class SlicedArray {
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit; const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const bottomWasMeantToLoad = Math.abs(add_offset); const bottomWasMeantToLoad = Math.abs(add_offset);
// can use 'slice' here to check because if it's end, then 'sliced' is out of 'slice'
// useful when there is only 1 message in chat on its reopening
const topFulfilled = (slice.length - sliceOffset) >= topWasMeantToLoad || (slice.isEnd(SliceEnd.Top) ? (sliced.setEnd(SliceEnd.Top), true) : false); const topFulfilled = (slice.length - sliceOffset) >= topWasMeantToLoad || (slice.isEnd(SliceEnd.Top) ? (sliced.setEnd(SliceEnd.Top), true) : false);
const bottomFulfilled = (sliceOffset - bottomWasMeantToLoad) >= 0 || (slice.isEnd(SliceEnd.Bottom) ? (sliced.setEnd(SliceEnd.Bottom), true) : false); const bottomFulfilled = (sliceOffset - bottomWasMeantToLoad) >= 0 || (slice.isEnd(SliceEnd.Bottom) ? (sliced.setEnd(SliceEnd.Bottom), true) : false);
@ -308,19 +358,40 @@ export default class SlicedArray {
} }
public unshift(...items: ItemType[]) { public unshift(...items: ItemType[]) {
this.first.unshift(...items); let slice = this.first;
if(!slice.length) {
slice.setEnd(SliceEnd.Bottom);
} else if(!slice.isEnd(SliceEnd.Bottom)) {
slice = this.constructSlice();
slice.setEnd(SliceEnd.Bottom);
this.slices.unshift(slice);
}
slice.unshift(...items);
} }
public push(...items: ItemType[]) { public push(...items: ItemType[]) {
this.last.push(...items); let slice = this.last;
if(!slice.length) {
slice.setEnd(SliceEnd.Top);
} else if(!slice.isEnd(SliceEnd.Top)) {
slice = this.constructSlice();
slice.setEnd(SliceEnd.Top);
this.slices.push(slice);
}
slice.push(...items);
} }
public delete(item: ItemType) { public delete(item: ItemType) {
const found = this.findSlice(item); const found = this.findSlice(item);
if(found) { if(found) {
found.slice.splice(found.index, 1); found.slice.splice(found.index, 1);
return true;
} }
return false;
} }
} }
(window as any).slicedArray = new SlicedArray(); MOUNT_CLASS_TO && (MOUNT_CLASS_TO.SlicedArray = SlicedArray);

8
src/lib/appManagers/appImManager.ts

@ -50,6 +50,7 @@ import { copy, getObjectKeysAndSort } from '../../helpers/object';
import { getFilesFromEvent } from '../../helpers/files'; import { getFilesFromEvent } from '../../helpers/files';
import PeerTitle from '../../components/peerTitle'; import PeerTitle from '../../components/peerTitle';
import PopupPeer from '../../components/popups/peer'; import PopupPeer from '../../components/popups/peer';
import { SliceEnd } from '../../helpers/slicedArray';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -445,10 +446,11 @@ export class AppImManager {
return; return;
} else if(e.code === 'ArrowUp') { } else if(e.code === 'ArrowUp') {
if(!chat.input.editMsgId && chat.input.isInputEmpty()) { if(!chat.input.editMsgId && chat.input.isInputEmpty()) {
const history = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId); const historyStorage = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
if(history.history.length) { const slice = historyStorage.history.slice;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
let goodMid: number; let goodMid: number;
for(const mid of history.history.slice) { for(const mid of slice) {
const message = chat.getMessage(mid); const message = chat.getMessage(mid);
const good = this.myId === chat.peerId ? message.fromId === this.myId : message.pFlags.out; const good = this.myId === chat.peerId ? message.fromId === this.myId : message.pFlags.out;

170
src/lib/appManagers/appMessagesManager.ts

@ -14,7 +14,7 @@ import ProgressivePreloader from "../../components/preloader";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date"; import { tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files"; import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object"; import { copy, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random"; import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string"; import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string";
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer"; import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer";
@ -168,7 +168,7 @@ export class AppMessagesManager {
public migratedFromTo: {[peerId: number]: number} = {}; public migratedFromTo: {[peerId: number]: number} = {};
public migratedToFrom: {[peerId: number]: number} = {}; public migratedToFrom: {[peerId: number]: number} = {};
public newMessagesHandlePromise = 0; public newMessagesHandleTimeout = 0;
public newMessagesToHandle: {[peerId: string]: Set<number>} = {}; public newMessagesToHandle: {[peerId: string]: Set<number>} = {};
public newDialogsHandlePromise: Promise<any>; public newDialogsHandlePromise: Promise<any>;
public newDialogsToHandle: {[peerId: string]: Dialog} = {}; public newDialogsToHandle: {[peerId: string]: Dialog} = {};
@ -1301,14 +1301,17 @@ export class AppMessagesManager {
/* if(options.threadId && this.threadsStorage[peerId]) { /* if(options.threadId && this.threadsStorage[peerId]) {
delete this.threadsStorage[peerId][options.threadId]; delete this.threadsStorage[peerId][options.threadId];
} */ } */
if(options.threadId) { const storages: HistoryStorage[] = [
const historyStorage = this.getHistoryStorage(peerId, options.threadId); this.getHistoryStorage(peerId),
historyStorage.history.unshift(messageId); options.threadId ? this.getHistoryStorage(peerId, options.threadId) : undefined
];
for(const storage of storages) {
if(storage) {
storage.history.unshift(messageId);
}
} }
const historyStorage = this.getHistoryStorage(peerId);
historyStorage.history.unshift(messageId);
//if(!options.isGroupedItem) { //if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true}); this.saveMessages([message], {storage, isOutgoing: true});
setTimeout(() => { setTimeout(() => {
@ -1514,7 +1517,6 @@ export class AppMessagesManager {
if(pendingData) { if(pendingData) {
const {peerId, tempId, storage} = pendingData; const {peerId, tempId, storage} = pendingData;
const historyStorage = this.getHistoryStorage(peerId); const historyStorage = this.getHistoryStorage(peerId);
const pos = historyStorage.history.findSlice(tempId);
apiUpdatesManager.processUpdateMessage({ apiUpdatesManager.processUpdateMessage({
_: 'updateShort', _: 'updateShort',
@ -1524,9 +1526,7 @@ export class AppMessagesManager {
} }
}); });
if(pos) { historyStorage.history.delete(tempId);
pos.slice.splice(pos.index, 1);
}
delete this.pendingByRandomId[randomId]; delete this.pendingByRandomId[randomId];
delete storage[tempId]; delete storage[tempId];
@ -1627,7 +1627,7 @@ export class AppMessagesManager {
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА: // ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ??? // ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0 // ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
return apiManager.invokeApi('messages.getDialogs', { return apiManager.invokeApiSingle('messages.getDialogs', {
folder_id: folderId, folder_id: folderId,
offset_date: offsetDate, offset_date: offsetDate,
offset_id: offsetId, offset_id: offsetId,
@ -3271,7 +3271,7 @@ export class AppMessagesManager {
} }
public getDiscussionMessage(peerId: number, mid: number) { public getDiscussionMessage(peerId: number, mid: number) {
return apiManager.invokeApi('messages.getDiscussionMessage', { return apiManager.invokeApiSingle('messages.getDiscussionMessage', {
peer: appPeersManager.getInputPeerById(peerId), peer: appPeersManager.getInputPeerById(peerId),
msg_id: this.getServerMessageId(mid) msg_id: this.getServerMessageId(mid)
}).then(result => { }).then(result => {
@ -3295,9 +3295,20 @@ export class AppMessagesManager {
}); });
} }
private handleNewMessage(peerId: number, mid: number) {
if(this.newMessagesToHandle[peerId] === undefined) {
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(mid);
if(!this.newMessagesHandleTimeout) {
this.newMessagesHandleTimeout = window.setTimeout(this.handleNewMessages, 0);
}
}
handleNewMessages = () => { handleNewMessages = () => {
clearTimeout(this.newMessagesHandlePromise); clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandlePromise = 0; this.newMessagesHandleTimeout = 0;
rootScope.broadcast('history_multiappend', this.newMessagesToHandle); rootScope.broadcast('history_multiappend', this.newMessagesToHandle);
this.newMessagesToHandle = {}; this.newMessagesToHandle = {};
@ -3404,6 +3415,7 @@ export class AppMessagesManager {
return promise; return promise;
} }
// TODO: cancel notification by peer when this function is being called
public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) { public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) {
//return Promise.resolve(); //return Promise.resolve();
// console.trace('start read') // console.trace('start read')
@ -3452,7 +3464,7 @@ export class AppMessagesManager {
_: 'updateReadChannelInbox', _: 'updateReadChannelInbox',
max_id: maxId, max_id: maxId,
channel_id: -peerId channel_id: -peerId
} } as Update.updateReadChannelInbox
}); });
} else { } else {
if(!historyStorage.readPromise) { if(!historyStorage.readPromise) {
@ -3477,21 +3489,10 @@ export class AppMessagesManager {
_: 'updateReadHistoryInbox', _: 'updateReadHistoryInbox',
max_id: maxId, max_id: maxId,
peer: appPeersManager.getOutputPeer(peerId) peer: appPeersManager.getOutputPeer(peerId)
} } as Update.updateReadHistoryInbox
}); });
} }
if(!threadId && historyStorage && historyStorage.history.length) {
const slice = historyStorage.history.slice;
for(const mid of slice) {
const message = this.getMessageByPeer(peerId, mid);
if(message && !message.pFlags.out) {
message.pFlags.unread = false;
appNotificationsManager.cancel('msg' + mid);
}
}
}
appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId)); appNotificationsManager.soundReset(appPeersManager.getPeerString(peerId));
if(historyStorage.readPromise) { if(historyStorage.readPromise) {
@ -3534,7 +3535,7 @@ export class AppMessagesManager {
_: 'updateChannelReadMessagesContents', _: 'updateChannelReadMessagesContents',
channel_id: channelId, channel_id: channelId,
messages: msgIds messages: msgIds
} } as Update.updateChannelReadMessagesContents
}); });
}); });
} else { } else {
@ -3548,7 +3549,7 @@ export class AppMessagesManager {
messages: msgIds, messages: msgIds,
pts: affectedMessages.pts, pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count pts_count: affectedMessages.pts_count
} } as Update.updateReadMessagesContents
}); });
}); });
} }
@ -3694,15 +3695,8 @@ export class AppMessagesManager {
return false; return false;
} }
const history = historyStorage.history.slice; const slice = historyStorage.history.insertSlice([message.mid]);
const topMsgId = history[0]; slice.setEnd(SliceEnd.Bottom);
history.unshift(message.mid);
if(message.mid < topMsgId) {
//this.log.error('this should\'nt have happenned!', message, history);
history.sort((a, b) => {
return b - a;
});
}
if(historyStorage.count !== null) { if(historyStorage.count !== null) {
historyStorage.count++; historyStorage.count++;
@ -3717,14 +3711,7 @@ export class AppMessagesManager {
} }
if(!pendingMessage) { if(!pendingMessage) {
if(this.newMessagesToHandle[peerId] === undefined) { this.handleNewMessage(peerId, message.mid);
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(message.mid);
if(!this.newMessagesHandlePromise) {
this.newMessagesHandlePromise = window.setTimeout(this.handleNewMessages, 0);
}
} }
if(isLocalThreadUpdate) { if(isLocalThreadUpdate) {
@ -4471,7 +4458,7 @@ export class AppMessagesManager {
return Promise.resolve(Object.keys(storage).map(id => +id)); return Promise.resolve(Object.keys(storage).map(id => +id));
} }
return apiManager.invokeApi('messages.getScheduledHistory', { return apiManager.invokeApiSingle('messages.getScheduledHistory', {
peer: appPeersManager.getInputPeerById(peerId), peer: appPeersManager.getInputPeerById(peerId),
hash: 0 hash: 0
}).then(historyResult => { }).then(historyResult => {
@ -4506,6 +4493,36 @@ export class AppMessagesManager {
}); });
} }
public isFetchIntervalNeeded(peerId: number) {
return peerId < 0 && !appChatsManager.isInChat(peerId);
}
public async getNewHistory(peerId: number, threadId?: number) {
if(!this.isFetchIntervalNeeded(peerId)) {
return;
}
const historyStorage = this.getHistoryStorage(peerId, threadId);
const slice = historyStorage.history.slice;
if(!slice.isEnd(SliceEnd.Bottom)) {
return;
}
delete historyStorage.maxId;
slice.unsetEnd(SliceEnd.Bottom);
let historyResult = this.getHistory(peerId, slice[0], 0, 50, threadId);
if(historyResult instanceof Promise) {
historyResult = await historyResult;
}
for(let i = 0, length = historyResult.history.length; i < length; ++i) {
this.handleNewMessage(peerId, historyResult.history[i]);
}
return historyStorage;
}
/** /**
* * https://core.telegram.org/api/offsets, offset_id is inclusive * * https://core.telegram.org/api/offsets, offset_id is inclusive
*/ */
@ -4567,7 +4584,7 @@ export class AppMessagesManager {
} }
const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit); const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit);
if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both))) { if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both) === SliceEnd.Both)) {
return { return {
count: historyStorage.count, count: historyStorage.count,
history: haveSlice.slice, history: haveSlice.slice,
@ -4587,10 +4604,15 @@ export class AppMessagesManager {
public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> { public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> {
return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => { return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => {
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; const {offset_id_offset, count, messages} = historyResult as MessagesMessages.messagesMessagesSlice;
historyStorage.count = count || messages.length;
const offsetIdOffset = offset_id_offset || 0;
const offsetIdOffset = (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0; const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const isTopEnd = offsetIdOffset >= (historyStorage.count - limit) || historyStorage.count < (limit + add_offset);
const isTopEnd = offsetIdOffset >= (historyStorage.count - topWasMeantToLoad) || historyStorage.count < topWasMeantToLoad;
const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
/* if(!maxId && historyResult.messages.length) { /* if(!maxId && historyResult.messages.length) {
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1); maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1);
@ -4598,13 +4620,14 @@ export class AppMessagesManager {
const wasTotalCount = historyStorage.history.length; */ const wasTotalCount = historyStorage.history.length; */
historyResult.messages.forEach((message) => { const mids = messages.map((message) => {
if(this.mergeReplyKeyboard(historyStorage, message)) { if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId}); rootScope.broadcast('history_reply_markup', {peerId});
} }
return (message as MyMessage).mid;
}); });
const mids = historyResult.messages.map((message) => (message as MyMessage).mid);
// * add bound manually. // * add bound manually.
// * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') // * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id')
if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) { if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) {
@ -4618,10 +4641,16 @@ export class AppMessagesManager {
mids.splice(i, 0, offset_id); mids.splice(i, 0, offset_id);
} }
historyStorage.history.insertSlice(mids); const slice = historyStorage.history.insertSlice(mids);
if(slice) {
if(isTopEnd) {
slice.setEnd(SliceEnd.Top);
}
if(isTopEnd) { if(isBottomEnd) {
historyStorage.history.last.setEnd(SliceEnd.Top); slice.setEnd(SliceEnd.Bottom);
historyStorage.maxId = slice[0]; // ! WARNING
}
} }
/* const isBackLimit = offset < 0 && -offset !== fullLimit; /* const isBackLimit = offset < 0 && -offset !== fullLimit;
@ -4681,7 +4710,7 @@ export class AppMessagesManager {
options.msg_id = this.getServerMessageId(threadId) || 0; options.msg_id = this.getServerMessageId(threadId) || 0;
} }
const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApi(threadId ? 'messages.getReplies' : 'messages.getHistory', options, { const promise: ReturnType<AppMessagesManager['requestHistory']> = apiManager.invokeApiSingle(threadId ? 'messages.getReplies' : 'messages.getHistory', options, {
//timeout: APITIMEOUT, //timeout: APITIMEOUT,
noErrorBox: true noErrorBox: true
}) as any; }) as any;
@ -4699,21 +4728,24 @@ export class AppMessagesManager {
apiUpdatesManager.addChannelState(-peerId, (historyResult as MessagesMessages.messagesChannelMessages).pts); apiUpdatesManager.addChannelState(-peerId, (historyResult as MessagesMessages.messagesChannelMessages).pts);
} }
let length = historyResult.messages.length; let length = historyResult.messages.length, count = (historyResult as MessagesMessages.messagesMessagesSlice).count;
if(length && historyResult.messages[length - 1].deleted) { if(length && historyResult.messages[length - 1].deleted) {
historyResult.messages.splice(length - 1, 1); historyResult.messages.splice(length - 1, 1);
length--; length--;
(historyResult as MessagesMessages.messagesMessagesSlice).count--; count--;
} }
// will load more history if last message is album grouped (because it can be not last item) // will load more history if last message is album grouped (because it can be not last item)
const historyStorage = this.getHistoryStorage(peerId, threadId);
// historyResult.messages: desc sorted // historyResult.messages: desc sorted
if(length && (historyResult.messages[length - 1] as Message.message).grouped_id const historyStorage = this.getHistoryStorage(peerId, threadId);
&& (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) { const oldestMessage: Message.message = historyResult.messages[length - 1] as any;
return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0, offsetDate, threadId).then((_historyResult) => { if(length && oldestMessage.grouped_id) {
return historyResult; const foundSlice = historyStorage.history.findSlice(oldestMessage.mid);
}); if(foundSlice && (foundSlice.slice.length + historyResult.messages.length) < count) {
return this.requestHistory(peerId, oldestMessage.mid, 10, 0, offsetDate, threadId).then((_historyResult) => {
return historyResult;
});
}
} }
return historyResult; return historyResult;
@ -4760,12 +4792,12 @@ export class AppMessagesManager {
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>; let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>;
if(+peerId < 0 && appPeersManager.isChannel(+peerId)) { if(+peerId < 0 && appPeersManager.isChannel(+peerId)) {
promise = apiManager.invokeApi('channels.getMessages', { promise = apiManager.invokeApiSingle('channels.getMessages', {
channel: appChatsManager.getChannelInput(-+peerId), channel: appChatsManager.getChannelInput(-+peerId),
id: msgIds id: msgIds
}); });
} else { } else {
promise = apiManager.invokeApi('messages.getMessages', { promise = apiManager.invokeApiSingle('messages.getMessages', {
id: msgIds id: msgIds
}); });
} }

2
src/lib/appManagers/appPhotosManager.ts

@ -13,7 +13,7 @@ import type { DownloadOptions } from "../mtproto/apiFileManager";
import { bytesFromHex } from "../../helpers/bytes"; import { bytesFromHex } from "../../helpers/bytes";
import { CancellablePromise } from "../../helpers/cancellablePromise"; import { CancellablePromise } from "../../helpers/cancellablePromise";
import { getFileNameByLocation } from "../../helpers/fileName"; import { getFileNameByLocation } from "../../helpers/fileName";
import { safeReplaceArrayInObject, defineNotNumerableProperties, isObject } from "../../helpers/object"; import { safeReplaceArrayInObject, isObject } from "../../helpers/object";
import { isSafari } from "../../helpers/userAgent"; import { isSafari } from "../../helpers/userAgent";
import { InputFileLocation, InputMedia, Photo, PhotoSize, PhotosPhotos } from "../../layer"; import { InputFileLocation, InputMedia, Photo, PhotoSize, PhotosPhotos } from "../../layer";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";

7
src/lib/storages/dialogs.ts

@ -25,6 +25,7 @@ import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array"
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object"; import { safeReplaceObject } from "../../helpers/object";
import { AppStateManager } from "../appManagers/appStateManager"; import { AppStateManager } from "../appManagers/appStateManager";
import { SliceEnd } from "../../helpers/slicedArray";
export default class DialogsStorage { export default class DialogsStorage {
private storage: AppStateManager['storages']['dialogs']; private storage: AppStateManager['storages']['dialogs'];
@ -490,13 +491,17 @@ export default class DialogsStorage {
} }
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId); const historyStorage = this.appMessagesManager.getHistoryStorage(peerId);
const slice = historyStorage.history.slice;
/* if(historyStorage === undefined) { // warning /* if(historyStorage === undefined) { // warning
historyStorage.history.push(mid); historyStorage.history.push(mid);
if(this.mergeReplyKeyboard(historyStorage, message)) { if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId}); rootScope.broadcast('history_reply_markup', {peerId});
} }
} else */if(!historyStorage.history.slice.length) { } else */if(!slice.length) {
historyStorage.history.unshift(mid); historyStorage.history.unshift(mid);
} else if(!slice.isEnd(SliceEnd.Bottom)) { // * this will probably never happen, however, if it does, then it will fix slice with top_message
const slice = historyStorage.history.insertSlice([mid]);
slice.setEnd(SliceEnd.Bottom);
} }
historyStorage.maxId = mid; historyStorage.maxId = mid;

121
src/tests/slicedArray.test.ts

@ -0,0 +1,121 @@
import SlicedArray, { Slice } from "../helpers/slicedArray";
test('Slicing returns new Slice', () => {
const sliced = new SlicedArray();
const newSlice = sliced.slice.slice();
expect(newSlice.isEnd).toBeDefined();
});
describe('Inserting', () => {
const sliced = new SlicedArray();
// @ts-ignore
const slices = sliced.slices;
const arr = [100, 99, 98, 97, 96, 95];
const distantArr = arr.slice(-2).map(v => v - 2);
const missingArr = [arr[arr.length - 1], arr[arr.length - 1] - 1, distantArr[0]];
const startValue = 90;
const values: number[] = [];
const valuesPerArray = 3;
const totalArrays = 10;
for(let i = 0, length = valuesPerArray * totalArrays; i < length; ++i) {
values.push(startValue - i);
}
const arrays: number[][] = [];
for(let i = 0; i < totalArrays; ++i) {
arrays.push(values.slice(valuesPerArray * i, valuesPerArray * (i + 1)));
}
test('Insert & flatten', () => {
const idx = 2;
sliced.insertSlice(arr.slice(0, idx + 1));
sliced.insertSlice(arr.slice(idx));
expect([...sliced.first]).toEqual(arr);
});
test('Insert inner values', () => {
sliced.insertSlice(arr.slice(1, -1));
expect([...sliced.first]).toEqual(arr);
});
test('Insert distant slice', () => {
const length = slices.length;
sliced.insertSlice(distantArr);
expect(slices.length).toEqual(length + 1);
});
test('Insert intersection & join them', () => {
const length = slices.length;
sliced.insertSlice(missingArr);
expect(slices.length).toEqual(length - 1);
});
let returnedSlice: Slice;
test('Insert arrays with gap & join them', () => {
slices[0].length = 0;
for(const arr of arrays) {
sliced.insertSlice(arr);
}
expect(slices.length).toEqual(totalArrays);
returnedSlice = sliced.insertSlice(values.slice(0, -valuesPerArray + 1));
expect(slices.length).toEqual(1);
});
test('Return inserted & flattened slice', () => {
expect(slices[0]).toEqual(returnedSlice);
});
});
describe('Slicing', () => {
const sliced = new SlicedArray();
// @ts-ignore
const slices = sliced.slices;
const VALUES_LENGTH = 100;
const INCREMENTOR = 0xFFFF;
const values: number[] = [];
for(let i = 0; i < VALUES_LENGTH; ++i) {
values[i] = i + INCREMENTOR * i;
}
values.sort((a, b) => b - a);
sliced.insertSlice(values);
const addOffset = 40;
const limit = 40;
const r = (func: (idx: number) => void) => {
const max = VALUES_LENGTH * 3;
for(let i = 0; i < max; ++i) {
const idx = Math.random() * max | 0;
func(idx);
}
};
describe('Positive addOffset', () => {
test('From the start', () => {
const {slice} = sliced.sliceMe(0, addOffset, limit);
expect([...slice]).toEqual(values.slice(addOffset, addOffset + limit));
});
test('From existing offsetId', () => {
r((idx) => {
const value = values[idx] || 1;
idx += 1; // because is it inclusive
const {slice} = sliced.sliceMe(value, addOffset, limit);
expect([...slice]).toEqual(values.slice(idx + addOffset, idx + addOffset + limit));
});
});
});
});
Loading…
Cancel
Save