diff --git a/apps/photos/src/components/FilesDownloadProgress.tsx b/apps/photos/src/components/FilesDownloadProgress.tsx index 0451fd71a..19e4d5a39 100644 --- a/apps/photos/src/components/FilesDownloadProgress.tsx +++ b/apps/photos/src/components/FilesDownloadProgress.tsx @@ -128,7 +128,10 @@ export const FilesDownloadProgress: React.FC = ({ void; isInHiddenSection?: boolean; + setFilesDownloadProgressAttributesCreator?: SetFilesDownloadProgressAttributesCreator; } const PhotoFrame = ({ @@ -81,6 +85,7 @@ const PhotoFrame = ({ showAppDownloadBanner, setIsPhotoSwipeOpen, isInHiddenSection, + setFilesDownloadProgressAttributesCreator, }: Props) => { const [open, setOpen] = useState(false); const [currentIndex, setCurrentIndex] = useState(0); @@ -607,6 +612,9 @@ const PhotoFrame = ({ enableDownload={enableDownload} fileToCollectionsMap={fileToCollectionsMap} collectionNameMap={collectionNameMap} + setFilesDownloadProgressAttributesCreator={ + setFilesDownloadProgressAttributesCreator + } /> ); diff --git a/apps/photos/src/components/PhotoViewer/index.tsx b/apps/photos/src/components/PhotoViewer/index.tsx index 0aa4f3197..9f9ccf00c 100644 --- a/apps/photos/src/components/PhotoViewer/index.tsx +++ b/apps/photos/src/components/PhotoViewer/index.tsx @@ -8,12 +8,12 @@ import { } from 'services/collectionService'; import { EnteFile } from 'types/file'; import { - downloadFile, copyFileToClipboard, getFileExtension, getFileFromURL, isSupportedRawFormat, isRawFile, + downloadSingleFile, } from 'utils/file'; import { logError } from '@ente/shared/sentry'; @@ -58,6 +58,7 @@ import isElectron from 'is-electron'; import ReplayIcon from '@mui/icons-material/Replay'; import ImageEditorOverlay from './ImageEditorOverlay'; import EditIcon from '@mui/icons-material/Edit'; +import { SetFilesDownloadProgressAttributesCreator } from 'types/gallery'; interface PhotoswipeFullscreenAPI { enter: () => void; @@ -92,6 +93,7 @@ interface Iprops { enableDownload: boolean; fileToCollectionsMap: Map; collectionNameMap: Map; + setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator; } function PhotoViewer(props: Iprops) { @@ -259,7 +261,7 @@ function PhotoViewer(props: Iprops) { `download-btn-${item.id}` ) as HTMLButtonElement; const downloadFile = () => { - downloadFileHelper(photoSwipe.currItem); + downloadFileHelper(photoSwipe.currItem as unknown as EnteFile); }; if (downloadLivePhotoBtn) { @@ -599,15 +601,17 @@ function PhotoViewer(props: Iprops) { setShowImageEditorOverlay(false); }; - const downloadFileHelper = async (file) => { + const downloadFileHelper = async (file: EnteFile) => { if (file && props.enableDownload) { - appContext.startLoading(); try { - await downloadFile(file); + const setSingleFileDownloadProgress = + props.setFilesDownloadProgressAttributesCreator( + file.metadata.title + ); + await downloadSingleFile(file, setSingleFileDownloadProgress); } catch (e) { // do nothing } - appContext.finishLoading(); } }; @@ -702,7 +706,9 @@ function PhotoViewer(props: Iprops) { onClose={() => setConversionFailedNotificationOpen(false) } - onClick={() => downloadFileHelper(photoSwipe.currItem)} + onClick={() => + downloadFileHelper(photoSwipe.currItem as EnteFile) + } /> - downloadFileHelper(photoSwipe.currItem) + downloadFileHelper( + photoSwipe.currItem as EnteFile + ) }> diff --git a/apps/photos/src/pages/gallery/index.tsx b/apps/photos/src/pages/gallery/index.tsx index 4e068a90b..74bcd4704 100644 --- a/apps/photos/src/pages/gallery/index.tsx +++ b/apps/photos/src/pages/gallery/index.tsx @@ -1165,6 +1165,9 @@ export default function Gallery() { files.length < 30 && !isInSearchMode } isInHiddenSection={isInHiddenSection} + setFilesDownloadProgressAttributesCreator={ + setFilesDownloadProgressAttributesCreator + } /> )} {selected.count > 0 && diff --git a/apps/photos/src/utils/file/index.ts b/apps/photos/src/utils/file/index.ts index 3cf6ed39d..1934f1707 100644 --- a/apps/photos/src/utils/file/index.ts +++ b/apps/photos/src/utils/file/index.ts @@ -1,4 +1,8 @@ -import { SelectedState } from 'types/gallery'; +import { + SelectedState, + SetFilesDownloadProgressAttributes, + SetFilesDownloadProgressAttributesCreator, +} from 'types/gallery'; import { EnteFile, EncryptedEnteFile, @@ -625,9 +629,96 @@ export function getUniqueFiles(files: EnteFile[]) { return uniqueFiles; } +export async function downloadFilesWithProgress( + files: EnteFile[], + downloadDirPath: string, + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes +) { + if (!files.length) { + return; + } + const canceller = new AbortController(); + const increaseSuccess = () => { + if (canceller.signal.aborted) return; + setFilesDownloadProgressAttributes((prev) => ({ + ...prev, + success: prev.success + 1, + })); + }; + const increaseFailed = () => { + if (canceller.signal.aborted) return; + setFilesDownloadProgressAttributes((prev) => ({ + ...prev, + failed: prev.failed + 1, + })); + }; + const isCancelled = () => canceller.signal.aborted; + + setFilesDownloadProgressAttributes({ + downloadDirPath, + success: 0, + failed: 0, + total: files.length, + canceller, + }); + + if (isElectron()) { + await downloadFilesDesktop( + files, + { increaseSuccess, increaseFailed, isCancelled }, + downloadDirPath + ); + } else { + await downloadFiles(files, { + increaseSuccess, + increaseFailed, + isCancelled, + }); + } +} + +export async function downloadSelectedFiles( + files: EnteFile[], + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes +) { + if (!files.length) { + return; + } + let downloadDirPath: string; + if (isElectron()) { + downloadDirPath = await ElectronAPIs.selectDirectory(); + if (!downloadDirPath) { + return; + } + } + await downloadFilesWithProgress( + files, + downloadDirPath, + setFilesDownloadProgressAttributes + ); +} + +export async function downloadSingleFile( + file: EnteFile, + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes +) { + let downloadDirPath: string; + if (isElectron()) { + downloadDirPath = await ElectronAPIs.selectDirectory(); + if (!downloadDirPath) { + return; + } + } + await downloadFilesWithProgress( + [file], + downloadDirPath, + setFilesDownloadProgressAttributes + ); +} + export async function downloadFiles( files: EnteFile[], - progressBarUpdater?: { + progressBarUpdater: { increaseSuccess: () => void; increaseFailed: () => void; isCancelled: () => boolean; @@ -869,7 +960,8 @@ export const handleFileOps = async ( files: EnteFile[]; } | ((prev: { files: EnteFile[] }) => { files: EnteFile[] }) - ) => void + ) => void, + setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator ) => { switch (ops) { case FILE_OPS_TYPE.TRASH: @@ -881,9 +973,17 @@ export const handleFileOps = async ( case FILE_OPS_TYPE.HIDE: await hideFilesHelper(files, setHiddenFileIds); break; - case FILE_OPS_TYPE.DOWNLOAD: - await downloadFiles(files); + case FILE_OPS_TYPE.DOWNLOAD: { + const setSelectedFileDownloadProgressAttributes = + setFilesDownloadProgressAttributesCreator( + `${files.length} files` + ); + await downloadSelectedFiles( + files, + setSelectedFileDownloadProgressAttributes + ); break; + } case FILE_OPS_TYPE.FIX_TIME: fixTimeHelper(files, setFixCreationTimeAttributes); break;