Message List: Fix duplicated downloads (#1781)
This commit is contained in:
parent
e2ef55f91c
commit
d4629cca8c
@ -1,4 +1,6 @@
|
|||||||
import { FC, memo, useEffect } from '../../lib/teact/teact';
|
import {
|
||||||
|
FC, memo, useCallback, useEffect,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import { Thread } from '../../global/types';
|
import { Thread } from '../../global/types';
|
||||||
@ -10,58 +12,76 @@ import {
|
|||||||
getMessageContentFilename, getMessageMediaHash,
|
getMessageContentFilename, getMessageMediaHash,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
|
|
||||||
|
import useDebounce from '../../hooks/useDebounce';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
activeDownloads: Record<number, number[]>;
|
activeDownloads: Record<string, number[]>;
|
||||||
messages: Record<number, {
|
messages: Record<string, {
|
||||||
byId: Record<number, ApiMessage>;
|
byId: Record<number, ApiMessage>;
|
||||||
threadsById: Record<number, Thread>;
|
threadsById: Record<number, Thread>;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const startedDownloads = new Set<string>();
|
const GLOBAL_UPDATE_DEBOUNCE = 1000;
|
||||||
|
|
||||||
|
const processedMessages = new Set<ApiMessage>();
|
||||||
|
const downloadedMessages = new Set<ApiMessage>();
|
||||||
|
|
||||||
const DownloadManager: FC<StateProps> = ({
|
const DownloadManager: FC<StateProps> = ({
|
||||||
activeDownloads,
|
activeDownloads,
|
||||||
messages,
|
messages,
|
||||||
}) => {
|
}) => {
|
||||||
const { cancelMessageMediaDownload } = getActions();
|
const { cancelMessagesMediaDownload } = getActions();
|
||||||
|
|
||||||
|
const debouncedGlobalUpdate = useDebounce(GLOBAL_UPDATE_DEBOUNCE, true);
|
||||||
|
|
||||||
|
const handleMessageDownloaded = useCallback((message: ApiMessage) => {
|
||||||
|
downloadedMessages.add(message);
|
||||||
|
debouncedGlobalUpdate(() => {
|
||||||
|
if (downloadedMessages.size) {
|
||||||
|
cancelMessagesMediaDownload({ messages: Array.from(downloadedMessages) });
|
||||||
|
downloadedMessages.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [cancelMessagesMediaDownload, debouncedGlobalUpdate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Object.entries(activeDownloads).forEach(([chatId, messageIds]) => {
|
const activeMessages = Object.entries(activeDownloads).map(([chatId, messageIds]) => (
|
||||||
const activeMessages = messageIds.map((id) => messages[Number(chatId)].byId[id]);
|
messageIds.map((id) => messages[chatId].byId[id])
|
||||||
activeMessages.forEach((message) => {
|
)).flat();
|
||||||
const downloadHash = getMessageMediaHash(message, 'download');
|
|
||||||
if (!downloadHash) {
|
if (!activeMessages.length) {
|
||||||
cancelMessageMediaDownload({ message });
|
processedMessages.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!startedDownloads.has(downloadHash)) {
|
activeMessages.forEach((message) => {
|
||||||
const mediaData = mediaLoader.getFromMemory(downloadHash);
|
if (processedMessages.has(message)) {
|
||||||
if (mediaData) {
|
return;
|
||||||
startedDownloads.delete(downloadHash);
|
}
|
||||||
download(mediaData, getMessageContentFilename(message));
|
processedMessages.add(message);
|
||||||
cancelMessageMediaDownload({ message });
|
const downloadHash = getMessageMediaHash(message, 'download');
|
||||||
return;
|
if (!downloadHash) {
|
||||||
}
|
handleMessageDownloaded(message);
|
||||||
|
return;
|
||||||
mediaLoader.fetch(downloadHash, ApiMediaFormat.BlobUrl, true).then((result) => {
|
}
|
||||||
startedDownloads.delete(downloadHash);
|
|
||||||
if (result) {
|
const mediaData = mediaLoader.getFromMemory(downloadHash);
|
||||||
download(result, getMessageContentFilename(message));
|
|
||||||
}
|
if (mediaData) {
|
||||||
cancelMessageMediaDownload({ message });
|
download(mediaData, getMessageContentFilename(message));
|
||||||
});
|
handleMessageDownloaded(message);
|
||||||
|
return;
|
||||||
startedDownloads.add(downloadHash);
|
}
|
||||||
|
|
||||||
|
mediaLoader.fetch(downloadHash, ApiMediaFormat.BlobUrl, true).then((result) => {
|
||||||
|
if (result) {
|
||||||
|
download(result, getMessageContentFilename(message));
|
||||||
}
|
}
|
||||||
|
handleMessageDownloaded(message);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [
|
}, [messages, activeDownloads, cancelMessagesMediaDownload, handleMessageDownloaded]);
|
||||||
cancelMessageMediaDownload,
|
|
||||||
messages,
|
|
||||||
activeDownloads,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -63,9 +63,9 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleDownloadClick = useCallback(() => {
|
const handleDownloadClick = useCallback(() => {
|
||||||
if (isDownloading) {
|
if (isDownloading) {
|
||||||
cancelMessageMediaDownload({ message });
|
cancelMessageMediaDownload({ message: message! });
|
||||||
} else {
|
} else {
|
||||||
downloadMessageMedia({ message });
|
downloadMessageMedia({ message: message! });
|
||||||
}
|
}
|
||||||
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
|
}, [cancelMessageMediaDownload, downloadMessageMedia, isDownloading, message]);
|
||||||
|
|
||||||
|
|||||||
@ -460,7 +460,7 @@ addActionHandler('openForwardMenuForSelectedMessages', (global, actions) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('cancelMessageMediaDownload', (global, actions, payload) => {
|
addActionHandler('cancelMessageMediaDownload', (global, actions, payload) => {
|
||||||
const { message } = payload!;
|
const { message } = payload;
|
||||||
|
|
||||||
const byChatId = global.activeDownloads.byChatId[message.chatId];
|
const byChatId = global.activeDownloads.byChatId[message.chatId];
|
||||||
if (!byChatId || !byChatId.length) return;
|
if (!byChatId || !byChatId.length) return;
|
||||||
@ -476,9 +476,24 @@ addActionHandler('cancelMessageMediaDownload', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('cancelMessagesMediaDownload', (global, actions, payload) => {
|
||||||
|
const { messages } = payload;
|
||||||
|
|
||||||
|
const byChatId = global.activeDownloads.byChatId;
|
||||||
|
const newByChatId: GlobalState['activeDownloads']['byChatId'] = {};
|
||||||
|
Object.keys(byChatId).forEach((chatId) => {
|
||||||
|
newByChatId[chatId] = byChatId[chatId].filter((id) => !messages.find((message) => message.id === id));
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
activeDownloads: {
|
||||||
|
byChatId: newByChatId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('downloadMessageMedia', (global, actions, payload) => {
|
addActionHandler('downloadMessageMedia', (global, actions, payload) => {
|
||||||
const { message } = payload!;
|
const { message } = payload;
|
||||||
if (!message) return;
|
|
||||||
|
|
||||||
setGlobal({
|
setGlobal({
|
||||||
...global,
|
...global,
|
||||||
|
|||||||
@ -585,6 +585,18 @@ export interface ActionPayloads {
|
|||||||
origin: AudioOrigin;
|
origin: AudioOrigin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Downloads
|
||||||
|
downloadSelectedMessages: {};
|
||||||
|
downloadMessageMedia: {
|
||||||
|
message: ApiMessage;
|
||||||
|
};
|
||||||
|
cancelMessageMediaDownload: {
|
||||||
|
message: ApiMessage;
|
||||||
|
};
|
||||||
|
cancelMessagesMediaDownload: {
|
||||||
|
messages: ApiMessage[];
|
||||||
|
};
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
openAddContactDialog: {
|
openAddContactDialog: {
|
||||||
userId?: string;
|
userId?: string;
|
||||||
@ -640,8 +652,6 @@ export type NonTypedActionNames = (
|
|||||||
'sendDefaultReaction' | 'sendEmojiInteraction' | 'sendWatchingEmojiInteraction' | 'loadMessageReactions' |
|
'sendDefaultReaction' | 'sendEmojiInteraction' | 'sendWatchingEmojiInteraction' | 'loadMessageReactions' |
|
||||||
'stopActiveReaction' | 'startActiveReaction' | 'copySelectedMessages' | 'copyMessagesByIds' |
|
'stopActiveReaction' | 'startActiveReaction' | 'copySelectedMessages' | 'copyMessagesByIds' |
|
||||||
'setEditingId' |
|
'setEditingId' |
|
||||||
// downloads
|
|
||||||
'downloadSelectedMessages' | 'downloadMessageMedia' | 'cancelMessageMediaDownload' |
|
|
||||||
// scheduled messages
|
// scheduled messages
|
||||||
'loadScheduledHistory' | 'sendScheduledMessages' | 'rescheduleMessage' | 'deleteScheduledMessages' |
|
'loadScheduledHistory' | 'sendScheduledMessages' | 'rescheduleMessage' | 'deleteScheduledMessages' |
|
||||||
// poll result
|
// poll result
|
||||||
|
|||||||
@ -1,4 +1,42 @@
|
|||||||
|
import { sleep } from '../lib/gramjs/Helpers';
|
||||||
|
|
||||||
|
type PendingDownload = {
|
||||||
|
url: string;
|
||||||
|
filename: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chrome prevents more than 10 downloads per second
|
||||||
|
const LIMIT_PER_BATCH = 10;
|
||||||
|
const BATCH_INTERVAL = 1000;
|
||||||
|
|
||||||
|
let pendingDownloads: PendingDownload[] = [];
|
||||||
|
let planned = false;
|
||||||
|
|
||||||
export default function download(url: string, filename: string) {
|
export default function download(url: string, filename: string) {
|
||||||
|
pendingDownloads.push({ url, filename });
|
||||||
|
if (!planned) {
|
||||||
|
planned = true;
|
||||||
|
setTimeout(async () => {
|
||||||
|
await processQueue();
|
||||||
|
planned = false;
|
||||||
|
}, BATCH_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processQueue() {
|
||||||
|
let count = 0;
|
||||||
|
for (const pendingDownload of pendingDownloads) {
|
||||||
|
downloadOne(pendingDownload);
|
||||||
|
count++;
|
||||||
|
if (count === LIMIT_PER_BATCH) {
|
||||||
|
await sleep(BATCH_INTERVAL);
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pendingDownloads = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadOne({ url, filename }: PendingDownload) {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = filename;
|
link.download = filename;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user