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. 57
      src/components/chat/bubbles.ts
  2. 151
      src/helpers/slicedArray.ts
  3. 8
      src/lib/appManagers/appImManager.ts
  4. 166
      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

57
src/components/chat/bubbles.ts

@ -130,6 +130,8 @@ export default class ChatBubbles { @@ -130,6 +130,8 @@ export default class ChatBubbles {
public isFirstLoad = true;
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) {
//this.chat.log.error('Bubbles construction');
@ -1399,6 +1401,7 @@ export default class ChatBubbles { @@ -1399,6 +1401,7 @@ export default class ChatBubbles {
this.messagesQueuePromise = null;
this.getHistoryTopPromise = this.getHistoryBottomPromise = undefined;
this.fetchNewPromise = undefined;
if(this.stickyIntersector) {
this.stickyIntersector.disconnect();
@ -1618,8 +1621,7 @@ export default class ChatBubbles { @@ -1618,8 +1621,7 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, !isJump);
const isFetchIntervalNeeded = () => peerId < 0 && !this.appChatsManager.isInChat(peerId) && false;
const needFetchInterval = isFetchIntervalNeeded();
const needFetchInterval = this.appMessagesManager.isFetchIntervalNeeded(peerId);
const needFetchNew = savedPosition || needFetchInterval;
if(!needFetchNew) {
// warning
@ -1627,20 +1629,44 @@ export default class ChatBubbles { @@ -1627,20 +1629,44 @@ export default class ChatBubbles {
this.scrollable.loadedAll.bottom = true;
}
} else {
const middleware = this.getMiddleware();
Promise.all([setPeerPromise, getHeavyAnimationPromise()]).then(() => {
if(!middleware()) {
return;
}
this.scrollable.checkForTriggers();
if(needFetchInterval) {
const middleware = this.getMiddleware();
const interval = window.setInterval(() => {
if(!middleware() || !isFetchIntervalNeeded()) {
clearInterval(interval);
const f = () => {
this.fetchNewPromise = new Promise<void>((resolve) => {
if(!middleware() || !this.appMessagesManager.isFetchIntervalNeeded(peerId)) {
resolve();
return;
}
this.scrollable.loadedAll.bottom = false;
this.loadMoreHistory(false);
}, 30e3);
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;
});
};
f();
}
});
}
@ -2626,10 +2652,16 @@ export default class ChatBubbles { @@ -2626,10 +2652,16 @@ export default class ChatBubbles {
} */
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;
}
if(lastSlice.isEnd(SliceEnd.Top) && history.includes(lastSlice[lastSlice.length - 1])) {
this.scrollable.loadedAll.top = true;
}
//console.time('appImManager render history');
return new Promise<boolean>((resolve, reject) => {
@ -2818,8 +2850,9 @@ export default class ChatBubbles { @@ -2818,8 +2850,9 @@ export default class ChatBubbles {
additionMsgIds = [additionMsgId];
} else {
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
if(historyStorage.history.length < loadCount && !historyStorage.history.slice.isEnd(SliceEnd.Both)) {
additionMsgIds = historyStorage.history.slice.slice();
const slice = historyStorage.history.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
for(let i = additionMsgIds.length - 1; i >= 0; --i) {

151
src/helpers/slicedArray.ts

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { MOUNT_CLASS_TO } from "../config/debug";
/**
* Descend sorted storage
*/
@ -14,15 +16,19 @@ export enum SliceEnd { @@ -14,15 +16,19 @@ export enum SliceEnd {
None = 0,
Top = 1,
Bottom = 2,
Both = 4
Both = SliceEnd.Top | SliceEnd.Bottom
};
export interface Slice extends Array<ItemType> {
slicedArray: SlicedArray;
//slicedArray: SlicedArray;
end: SliceEnd;
isEnd: (side: SliceEnd) => boolean;
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 {
@ -35,51 +41,84 @@ export default class SlicedArray { @@ -35,51 +41,84 @@ export default class SlicedArray {
private sliceConstructor: SliceConstructor;
constructor() {
const self = this;
this.sliceConstructor = class Slice extends Array<ItemType> implements Slice {
slicedArray: SlicedArray;
// @ts-ignore
this.sliceConstructor = SlicedArray.getSliceConstructor(this);
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;
constructor(...items: ItemType[]) {
/* constructor(...items: ItemType[]) {
super(...items);
this.slicedArray = self;
}
//this.slicedArray = slicedArray;
} */
isEnd(side: SliceEnd) {
if(this.end & side) {
isEnd(side: SliceEnd): boolean {
if((this.end & side) === side) {
return true;
}
}/* else if(!this.slicedArray) {
return false;
} */
let isEnd = false;
if(side === SliceEnd.Top) {
const slice = self.last;
return slice.end & side ? this.includes(slice[slice.length - 1]) || !slice.length : false;
const slice = slicedArray.last;
isEnd = slice.end & side ? this.includes(slice[slice.length - 1])/* || !slice.length */ : false;
} else if(side === SliceEnd.Bottom) {
const slice = self.first;
return slice.end & side ? this.includes(slice[0]) || !slice.length : false;
}/* else if(side === SliceEnd.Both) {
const slice = slicedArray.first;
isEnd = slice.end & side ? this.includes(slice[0])/* || !slice.length */ : false;
} 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) {
this.end |= side;
}
if(side !== SliceEnd.Both && this.end & SliceEnd.Top && this.end & SliceEnd.Bottom) {
this.end |= SliceEnd.Both;
unsetEnd(side: SliceEnd) {
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);
}
}
}
const first = this.constructSlice();
first.setEnd(SliceEnd.Bottom);
this.slices = [first];
return ret;
}
}
}
public constructSlice(...items: ItemType[]) {
//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;
// ! code below will slow execution in 15 times
@ -128,7 +167,7 @@ export default class SlicedArray { @@ -128,7 +167,7 @@ export default class SlicedArray {
*/
}
public insertSlice(slice: ItemType[]) {
public insertSlice(slice: ItemType[], flatten = true) {
if(!slice.length) {
return;
}
@ -136,15 +175,15 @@ export default class SlicedArray { @@ -136,15 +175,15 @@ export default class SlicedArray {
const first = this.slices[0];
if(!first.length) {
first.push(...slice);
return;
return first;
}
const lowerBound = slice[slice.length - 1];
const upperBound = slice[0];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1;
for(let i = 0; i < this.slices.length; ++i) {
foundSlice = this.slices[i];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1, foundSliceIndex = 0;
for(; foundSliceIndex < this.slices.length; ++foundSliceIndex) {
foundSlice = this.slices[foundSliceIndex];
lowerIndex = foundSlice.indexOf(lowerBound);
upperIndex = foundSlice.indexOf(upperBound);
@ -173,16 +212,16 @@ export default class SlicedArray { @@ -173,16 +212,16 @@ export default class SlicedArray {
}
this.slices.splice(insertIndex, 0, this.constructSlice(...slice));
foundSliceIndex = insertIndex;
}
this.flatten();
if(flatten) {
return this.flatten(foundSliceIndex);
}
private flatten() {
if(this.slices.length < 2) {
return;
}
private flatten(foundSliceIndex: number) {
if(this.slices.length >= 2) {
for(let i = 0, length = this.slices.length; i < (length - 1); ++i) {
const prevSlice = this.slices[i];
const nextSlice = this.slices[i + 1];
@ -191,11 +230,20 @@ export default class SlicedArray { @@ -191,11 +230,20 @@ export default class SlicedArray {
if(upperIndex !== -1) {
prevSlice.setEnd(nextSlice.end);
this.slices.splice(i + 1, 1);
length--;
this.insertSlice(nextSlice);
if(i < foundSliceIndex) {
--foundSliceIndex;
}
--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 { @@ -217,7 +265,7 @@ export default class SlicedArray {
}
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 index = slice.indexOf(item);
if(index !== -1) {
@ -295,6 +343,8 @@ export default class SlicedArray { @@ -295,6 +343,8 @@ export default class SlicedArray {
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
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 bottomFulfilled = (sliceOffset - bottomWasMeantToLoad) >= 0 || (slice.isEnd(SliceEnd.Bottom) ? (sliced.setEnd(SliceEnd.Bottom), true) : false);
@ -308,19 +358,40 @@ export default class SlicedArray { @@ -308,19 +358,40 @@ export default class SlicedArray {
}
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[]) {
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) {
const found = this.findSlice(item);
if(found) {
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'; @@ -50,6 +50,7 @@ import { copy, getObjectKeysAndSort } from '../../helpers/object';
import { getFilesFromEvent } from '../../helpers/files';
import PeerTitle from '../../components/peerTitle';
import PopupPeer from '../../components/popups/peer';
import { SliceEnd } from '../../helpers/slicedArray';
//console.log('appImManager included33!');
@ -445,10 +446,11 @@ export class AppImManager { @@ -445,10 +446,11 @@ export class AppImManager {
return;
} else if(e.code === 'ArrowUp') {
if(!chat.input.editMsgId && chat.input.isInputEmpty()) {
const history = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
if(history.history.length) {
const historyStorage = appMessagesManager.getHistoryStorage(chat.peerId, chat.threadId);
const slice = historyStorage.history.slice;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
let goodMid: number;
for(const mid of history.history.slice) {
for(const mid of slice) {
const message = chat.getMessage(mid);
const good = this.myId === chat.peerId ? message.fromId === this.myId : message.pFlags.out;

166
src/lib/appManagers/appMessagesManager.ts

@ -14,7 +14,7 @@ import ProgressivePreloader from "../../components/preloader"; @@ -14,7 +14,7 @@ import ProgressivePreloader from "../../components/preloader";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date";
import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { copy, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
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";
@ -168,7 +168,7 @@ export class AppMessagesManager { @@ -168,7 +168,7 @@ export class AppMessagesManager {
public migratedFromTo: {[peerId: number]: number} = {};
public migratedToFrom: {[peerId: number]: number} = {};
public newMessagesHandlePromise = 0;
public newMessagesHandleTimeout = 0;
public newMessagesToHandle: {[peerId: string]: Set<number>} = {};
public newDialogsHandlePromise: Promise<any>;
public newDialogsToHandle: {[peerId: string]: Dialog} = {};
@ -1301,13 +1301,16 @@ export class AppMessagesManager { @@ -1301,13 +1301,16 @@ export class AppMessagesManager {
/* if(options.threadId && this.threadsStorage[peerId]) {
delete this.threadsStorage[peerId][options.threadId];
} */
if(options.threadId) {
const historyStorage = this.getHistoryStorage(peerId, options.threadId);
historyStorage.history.unshift(messageId);
}
const storages: HistoryStorage[] = [
this.getHistoryStorage(peerId),
options.threadId ? this.getHistoryStorage(peerId, options.threadId) : undefined
];
const historyStorage = this.getHistoryStorage(peerId);
historyStorage.history.unshift(messageId);
for(const storage of storages) {
if(storage) {
storage.history.unshift(messageId);
}
}
//if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true});
@ -1514,7 +1517,6 @@ export class AppMessagesManager { @@ -1514,7 +1517,6 @@ export class AppMessagesManager {
if(pendingData) {
const {peerId, tempId, storage} = pendingData;
const historyStorage = this.getHistoryStorage(peerId);
const pos = historyStorage.history.findSlice(tempId);
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
@ -1524,9 +1526,7 @@ export class AppMessagesManager { @@ -1524,9 +1526,7 @@ export class AppMessagesManager {
}
});
if(pos) {
pos.slice.splice(pos.index, 1);
}
historyStorage.history.delete(tempId);
delete this.pendingByRandomId[randomId];
delete storage[tempId];
@ -1627,7 +1627,7 @@ export class AppMessagesManager { @@ -1627,7 +1627,7 @@ export class AppMessagesManager {
// ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
// ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
// ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
return apiManager.invokeApi('messages.getDialogs', {
return apiManager.invokeApiSingle('messages.getDialogs', {
folder_id: folderId,
offset_date: offsetDate,
offset_id: offsetId,
@ -3271,7 +3271,7 @@ export class AppMessagesManager { @@ -3271,7 +3271,7 @@ export class AppMessagesManager {
}
public getDiscussionMessage(peerId: number, mid: number) {
return apiManager.invokeApi('messages.getDiscussionMessage', {
return apiManager.invokeApiSingle('messages.getDiscussionMessage', {
peer: appPeersManager.getInputPeerById(peerId),
msg_id: this.getServerMessageId(mid)
}).then(result => {
@ -3295,9 +3295,20 @@ export class AppMessagesManager { @@ -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 = () => {
clearTimeout(this.newMessagesHandlePromise);
this.newMessagesHandlePromise = 0;
clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0;
rootScope.broadcast('history_multiappend', this.newMessagesToHandle);
this.newMessagesToHandle = {};
@ -3404,6 +3415,7 @@ export class AppMessagesManager { @@ -3404,6 +3415,7 @@ export class AppMessagesManager {
return promise;
}
// TODO: cancel notification by peer when this function is being called
public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) {
//return Promise.resolve();
// console.trace('start read')
@ -3452,7 +3464,7 @@ export class AppMessagesManager { @@ -3452,7 +3464,7 @@ export class AppMessagesManager {
_: 'updateReadChannelInbox',
max_id: maxId,
channel_id: -peerId
}
} as Update.updateReadChannelInbox
});
} else {
if(!historyStorage.readPromise) {
@ -3477,21 +3489,10 @@ export class AppMessagesManager { @@ -3477,21 +3489,10 @@ export class AppMessagesManager {
_: 'updateReadHistoryInbox',
max_id: maxId,
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));
if(historyStorage.readPromise) {
@ -3534,7 +3535,7 @@ export class AppMessagesManager { @@ -3534,7 +3535,7 @@ export class AppMessagesManager {
_: 'updateChannelReadMessagesContents',
channel_id: channelId,
messages: msgIds
}
} as Update.updateChannelReadMessagesContents
});
});
} else {
@ -3548,7 +3549,7 @@ export class AppMessagesManager { @@ -3548,7 +3549,7 @@ export class AppMessagesManager {
messages: msgIds,
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
} as Update.updateReadMessagesContents
});
});
}
@ -3694,15 +3695,8 @@ export class AppMessagesManager { @@ -3694,15 +3695,8 @@ export class AppMessagesManager {
return false;
}
const history = historyStorage.history.slice;
const topMsgId = history[0];
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;
});
}
const slice = historyStorage.history.insertSlice([message.mid]);
slice.setEnd(SliceEnd.Bottom);
if(historyStorage.count !== null) {
historyStorage.count++;
@ -3717,14 +3711,7 @@ export class AppMessagesManager { @@ -3717,14 +3711,7 @@ export class AppMessagesManager {
}
if(!pendingMessage) {
if(this.newMessagesToHandle[peerId] === undefined) {
this.newMessagesToHandle[peerId] = new Set();
}
this.newMessagesToHandle[peerId].add(message.mid);
if(!this.newMessagesHandlePromise) {
this.newMessagesHandlePromise = window.setTimeout(this.handleNewMessages, 0);
}
this.handleNewMessage(peerId, message.mid);
}
if(isLocalThreadUpdate) {
@ -4471,7 +4458,7 @@ export class AppMessagesManager { @@ -4471,7 +4458,7 @@ export class AppMessagesManager {
return Promise.resolve(Object.keys(storage).map(id => +id));
}
return apiManager.invokeApi('messages.getScheduledHistory', {
return apiManager.invokeApiSingle('messages.getScheduledHistory', {
peer: appPeersManager.getInputPeerById(peerId),
hash: 0
}).then(historyResult => {
@ -4506,6 +4493,36 @@ export class AppMessagesManager { @@ -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
*/
@ -4567,7 +4584,7 @@ export class AppMessagesManager { @@ -4567,7 +4584,7 @@ export class AppMessagesManager {
}
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 {
count: historyStorage.count,
history: haveSlice.slice,
@ -4587,10 +4604,15 @@ export class AppMessagesManager { @@ -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> {
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 isTopEnd = offsetIdOffset >= (historyStorage.count - limit) || historyStorage.count < (limit + add_offset);
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const isTopEnd = offsetIdOffset >= (historyStorage.count - topWasMeantToLoad) || historyStorage.count < topWasMeantToLoad;
const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
/* if(!maxId && historyResult.messages.length) {
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1);
@ -4598,13 +4620,14 @@ export class AppMessagesManager { @@ -4598,13 +4620,14 @@ export class AppMessagesManager {
const wasTotalCount = historyStorage.history.length; */
historyResult.messages.forEach((message) => {
const mids = messages.map((message) => {
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId});
}
return (message as MyMessage).mid;
});
const mids = historyResult.messages.map((message) => (message as MyMessage).mid);
// * add bound manually.
// * 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) {
@ -4618,10 +4641,16 @@ export class AppMessagesManager { @@ -4618,10 +4641,16 @@ export class AppMessagesManager {
mids.splice(i, 0, offset_id);
}
historyStorage.history.insertSlice(mids);
const slice = historyStorage.history.insertSlice(mids);
if(slice) {
if(isTopEnd) {
historyStorage.history.last.setEnd(SliceEnd.Top);
slice.setEnd(SliceEnd.Top);
}
if(isBottomEnd) {
slice.setEnd(SliceEnd.Bottom);
historyStorage.maxId = slice[0]; // ! WARNING
}
}
/* const isBackLimit = offset < 0 && -offset !== fullLimit;
@ -4681,7 +4710,7 @@ export class AppMessagesManager { @@ -4681,7 +4710,7 @@ export class AppMessagesManager {
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,
noErrorBox: true
}) as any;
@ -4699,22 +4728,25 @@ export class AppMessagesManager { @@ -4699,22 +4728,25 @@ export class AppMessagesManager {
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) {
historyResult.messages.splice(length - 1, 1);
length--;
(historyResult as MessagesMessages.messagesMessagesSlice).count--;
count--;
}
// 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
if(length && (historyResult.messages[length - 1] as Message.message).grouped_id
&& (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) {
return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0, offsetDate, threadId).then((_historyResult) => {
const historyStorage = this.getHistoryStorage(peerId, threadId);
const oldestMessage: Message.message = historyResult.messages[length - 1] as any;
if(length && oldestMessage.grouped_id) {
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;
}, (error) => {
@ -4760,12 +4792,12 @@ export class AppMessagesManager { @@ -4760,12 +4792,12 @@ export class AppMessagesManager {
let promise: Promise<MethodDeclMap['channels.getMessages']['res'] | MethodDeclMap['messages.getMessages']['res']>;
if(+peerId < 0 && appPeersManager.isChannel(+peerId)) {
promise = apiManager.invokeApi('channels.getMessages', {
promise = apiManager.invokeApiSingle('channels.getMessages', {
channel: appChatsManager.getChannelInput(-+peerId),
id: msgIds
});
} else {
promise = apiManager.invokeApi('messages.getMessages', {
promise = apiManager.invokeApiSingle('messages.getMessages', {
id: msgIds
});
}

2
src/lib/appManagers/appPhotosManager.ts

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

7
src/lib/storages/dialogs.ts

@ -25,6 +25,7 @@ import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array" @@ -25,6 +25,7 @@ import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array"
import rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object";
import { AppStateManager } from "../appManagers/appStateManager";
import { SliceEnd } from "../../helpers/slicedArray";
export default class DialogsStorage {
private storage: AppStateManager['storages']['dialogs'];
@ -490,13 +491,17 @@ export default class DialogsStorage { @@ -490,13 +491,17 @@ export default class DialogsStorage {
}
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId);
const slice = historyStorage.history.slice;
/* if(historyStorage === undefined) { // warning
historyStorage.history.push(mid);
if(this.mergeReplyKeyboard(historyStorage, message)) {
rootScope.broadcast('history_reply_markup', {peerId});
}
} else */if(!historyStorage.history.slice.length) {
} else */if(!slice.length) {
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;

121
src/tests/slicedArray.test.ts

@ -0,0 +1,121 @@ @@ -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