TelegramPWA/src/components/main/DownloadManager.tsx
2025-04-23 18:57:46 +02:00

111 lines
3.4 KiB
TypeScript

import type { FC } from '../../lib/teact/teact';
import { memo, useEffect } from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { TabState } from '../../global/types';
import { ApiMediaFormat } from '../../api/types';
import { selectTabState } from '../../global/selectors';
import { IS_OPFS_SUPPORTED, IS_SERVICE_WORKER_SUPPORTED, MAX_BUFFER_SIZE } from '../../util/browser/windowEnvironment';
import download from '../../util/download';
import generateUniqueId from '../../util/generateUniqueId';
import * as mediaLoader from '../../util/mediaLoader';
import useLastCallback from '../../hooks/useLastCallback';
import useRunDebounced from '../../hooks/useRunDebounced';
type StateProps = {
activeDownloads: TabState['activeDownloads'];
};
const GLOBAL_UPDATE_DEBOUNCE = 1000;
const processedHashes = new Set<string>();
const downloadedHashes = new Set<string>();
const DownloadManager: FC<StateProps> = ({
activeDownloads,
}) => {
const { cancelMediaHashDownloads, showNotification } = getActions();
const runDebounced = useRunDebounced(GLOBAL_UPDATE_DEBOUNCE, true);
const handleMediaDownloaded = useLastCallback((hash: string) => {
downloadedHashes.add(hash);
runDebounced(() => {
if (downloadedHashes.size) {
cancelMediaHashDownloads({ mediaHashes: Array.from(downloadedHashes) });
downloadedHashes.clear();
}
});
});
useEffect(() => {
if (!Object.keys(activeDownloads).length) {
processedHashes.clear();
return;
}
Object.entries(activeDownloads).forEach(([mediaHash, metadata]) => {
if (processedHashes.has(mediaHash)) {
return;
}
processedHashes.add(mediaHash);
const { size, filename, format: mediaFormat } = metadata;
const mediaData = mediaLoader.getFromMemory(mediaHash);
if (mediaData) {
download(mediaData, filename);
handleMediaDownloaded(mediaHash);
return;
}
if (size > MAX_BUFFER_SIZE && !IS_OPFS_SUPPORTED && !IS_SERVICE_WORKER_SUPPORTED) {
showNotification({
message: 'Downloading files bigger than 2GB is not supported in your browser.',
});
handleMediaDownloaded(mediaHash);
return;
}
const handleProgress = () => {
const currentDownloads = selectTabState(getGlobal()).activeDownloads;
if (!currentDownloads[mediaHash]) {
mediaLoader.cancelProgress(handleProgress);
}
};
mediaLoader.fetch(mediaHash, mediaFormat, true, handleProgress, generateUniqueId()).then((result) => {
if (mediaFormat === ApiMediaFormat.DownloadUrl) {
const url = new URL(result, window.document.baseURI);
url.searchParams.set('filename', encodeURIComponent(filename));
const downloadWindow = window.open(url.toString());
downloadWindow?.addEventListener('beforeunload', () => {
showNotification({
message: 'Download started. Please, do not close the app before it is finished.',
});
});
} else if (result) {
download(result, filename);
}
handleMediaDownloaded(mediaHash);
});
});
}, [activeDownloads]);
return undefined;
};
export default memo(withGlobal(
(global): StateProps => {
const activeDownloads = selectTabState(global).activeDownloads;
return {
activeDownloads,
};
},
)(DownloadManager));