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(); const downloadedHashes = new Set(); const DownloadManager: FC = ({ 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));