commit
11a2e0c3b2
|
@ -23,19 +23,22 @@ import { SetLoading, SetFiles } from 'types/gallery';
|
|||
import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload';
|
||||
import { ElectronFile, FileWithCollection } from 'types/upload';
|
||||
import UploadTypeChoiceModal from './UploadTypeChoiceModal';
|
||||
import Router from 'next/router';
|
||||
|
||||
const FIRST_ALBUM_NAME = 'My First Album';
|
||||
|
||||
interface Props {
|
||||
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
||||
setBannerMessage: (message: string | JSX.Element) => void;
|
||||
acceptedFiles: File[];
|
||||
droppedFiles: File[];
|
||||
clearDroppedFiles: () => void;
|
||||
closeCollectionSelector: () => void;
|
||||
setCollectionSelectorAttributes: SetCollectionSelectorAttributes;
|
||||
setCollectionNamerAttributes: SetCollectionNamerAttributes;
|
||||
setLoading: SetLoading;
|
||||
setDialogMessage: SetDialogMessage;
|
||||
setUploadInProgress: any;
|
||||
uploadInProgress: boolean;
|
||||
setUploadInProgress: (value: boolean) => void;
|
||||
showCollectionSelector: () => void;
|
||||
fileRejections: FileRejection[];
|
||||
setFiles: SetFiles;
|
||||
|
@ -51,9 +54,10 @@ enum UPLOAD_STRATEGY {
|
|||
COLLECTION_PER_FOLDER,
|
||||
}
|
||||
|
||||
enum DESKTOP_UPLOAD_TYPE {
|
||||
FILES,
|
||||
FOLDERS,
|
||||
export enum DESKTOP_UPLOAD_TYPE {
|
||||
FILES = 'files',
|
||||
FOLDERS = 'folders',
|
||||
ZIPS = 'zips',
|
||||
}
|
||||
|
||||
interface AnalysisResult {
|
||||
|
@ -61,6 +65,11 @@ interface AnalysisResult {
|
|||
multipleFolders: boolean;
|
||||
}
|
||||
|
||||
const NULL_ANALYSIS_RESULT = {
|
||||
suggestedCollectionName: '',
|
||||
multipleFolders: false,
|
||||
};
|
||||
|
||||
export default function Upload(props: Props) {
|
||||
const [progressView, setProgressView] = useState(false);
|
||||
const [uploadStage, setUploadStage] = useState<UPLOAD_STAGES>(
|
||||
|
@ -76,10 +85,8 @@ export default function Upload(props: Props) {
|
|||
const [hasLivePhotos, setHasLivePhotos] = useState(false);
|
||||
|
||||
const [choiceModalView, setChoiceModalView] = useState(false);
|
||||
const [analysisResult, setAnalysisResult] = useState<AnalysisResult>({
|
||||
suggestedCollectionName: '',
|
||||
multipleFolders: false,
|
||||
});
|
||||
const [analysisResult, setAnalysisResult] =
|
||||
useState<AnalysisResult>(NULL_ANALYSIS_RESULT);
|
||||
const appContext = useContext(AppContext);
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
|
@ -87,6 +94,7 @@ export default function Upload(props: Props) {
|
|||
const isPendingDesktopUpload = useRef(false);
|
||||
const pendingDesktopUploadCollectionName = useRef<string>('');
|
||||
const desktopUploadType = useRef<DESKTOP_UPLOAD_TYPE>(null);
|
||||
const zipPaths = useRef<string[]>(null);
|
||||
|
||||
useEffect(() => {
|
||||
UploadManager.initUploader(
|
||||
|
@ -104,8 +112,8 @@ export default function Upload(props: Props) {
|
|||
|
||||
if (isElectron()) {
|
||||
ImportService.getPendingUploads().then(
|
||||
({ files: electronFiles, collectionName }) => {
|
||||
resumeDesktopUpload(electronFiles, collectionName);
|
||||
({ files: electronFiles, collectionName, type }) => {
|
||||
resumeDesktopUpload(type, electronFiles, collectionName);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -113,39 +121,34 @@ export default function Upload(props: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.acceptedFiles?.length > 0 ||
|
||||
appContext.sharedFiles?.length > 0 ||
|
||||
props.electronFiles?.length > 0
|
||||
!props.uploadInProgress &&
|
||||
(props.electronFiles?.length > 0 ||
|
||||
props.droppedFiles?.length > 0 ||
|
||||
appContext.sharedFiles?.length > 0)
|
||||
) {
|
||||
props.setLoading(true);
|
||||
|
||||
let analysisResult: AnalysisResult;
|
||||
if (
|
||||
props.acceptedFiles?.length > 0 ||
|
||||
props.electronFiles?.length > 0
|
||||
) {
|
||||
if (props.acceptedFiles?.length > 0) {
|
||||
// File selection by drag and drop or selection of file.
|
||||
toUploadFiles.current = props.acceptedFiles;
|
||||
} else {
|
||||
// File selection from desktop app
|
||||
toUploadFiles.current = props.electronFiles;
|
||||
}
|
||||
|
||||
analysisResult = analyseUploadFiles();
|
||||
if (analysisResult) {
|
||||
setAnalysisResult(analysisResult);
|
||||
}
|
||||
} else if (appContext.sharedFiles.length > 0) {
|
||||
if (props.droppedFiles?.length > 0) {
|
||||
// File selection by drag and drop or selection of file.
|
||||
toUploadFiles.current = props.droppedFiles;
|
||||
props.clearDroppedFiles();
|
||||
} else if (appContext.sharedFiles?.length > 0) {
|
||||
toUploadFiles.current = appContext.sharedFiles;
|
||||
appContext.resetSharedFiles();
|
||||
} else if (props.electronFiles?.length > 0) {
|
||||
// File selection from desktop app
|
||||
toUploadFiles.current = props.electronFiles;
|
||||
props.setElectronFiles([]);
|
||||
}
|
||||
const analysisResult = analyseUploadFiles();
|
||||
setAnalysisResult(analysisResult);
|
||||
|
||||
handleCollectionCreationAndUpload(
|
||||
analysisResult,
|
||||
props.isFirstUpload
|
||||
);
|
||||
props.setLoading(false);
|
||||
}
|
||||
}, [props.acceptedFiles, appContext.sharedFiles, props.electronFiles]);
|
||||
}, [props.droppedFiles, appContext.sharedFiles, props.electronFiles]);
|
||||
|
||||
const uploadInit = function () {
|
||||
setUploadStage(UPLOAD_STAGES.START);
|
||||
|
@ -158,24 +161,27 @@ export default function Upload(props: Props) {
|
|||
};
|
||||
|
||||
const resumeDesktopUpload = async (
|
||||
type: DESKTOP_UPLOAD_TYPE,
|
||||
electronFiles: ElectronFile[],
|
||||
collectionName: string
|
||||
) => {
|
||||
if (electronFiles && electronFiles?.length > 0) {
|
||||
isPendingDesktopUpload.current = true;
|
||||
pendingDesktopUploadCollectionName.current = collectionName;
|
||||
desktopUploadType.current = type;
|
||||
props.setElectronFiles(electronFiles);
|
||||
}
|
||||
};
|
||||
|
||||
function analyseUploadFiles(): AnalysisResult {
|
||||
if (toUploadFiles.current.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (desktopUploadType.current === DESKTOP_UPLOAD_TYPE.FILES) {
|
||||
desktopUploadType.current = null;
|
||||
return { suggestedCollectionName: '', multipleFolders: false };
|
||||
if (
|
||||
isElectron() &&
|
||||
(!desktopUploadType.current ||
|
||||
desktopUploadType.current === DESKTOP_UPLOAD_TYPE.FILES)
|
||||
) {
|
||||
return NULL_ANALYSIS_RESULT;
|
||||
}
|
||||
|
||||
const paths: string[] = toUploadFiles.current.map(
|
||||
(file) => file['path']
|
||||
);
|
||||
|
@ -183,19 +189,21 @@ export default function Upload(props: Props) {
|
|||
paths.sort((path1, path2) => getCharCount(path1) - getCharCount(path2));
|
||||
const firstPath = paths[0];
|
||||
const lastPath = paths[paths.length - 1];
|
||||
|
||||
const L = firstPath.length;
|
||||
let i = 0;
|
||||
const firstFileFolder = firstPath.substr(0, firstPath.lastIndexOf('/'));
|
||||
const lastFileFolder = lastPath.substr(0, lastPath.lastIndexOf('/'));
|
||||
const firstFileFolder = firstPath.slice(0, firstPath.lastIndexOf('/'));
|
||||
const lastFileFolder = lastPath.slice(0, lastPath.lastIndexOf('/'));
|
||||
while (i < L && firstPath.charAt(i) === lastPath.charAt(i)) i++;
|
||||
let commonPathPrefix = firstPath.substring(0, i);
|
||||
let commonPathPrefix = firstPath.slice(0, i);
|
||||
|
||||
if (commonPathPrefix) {
|
||||
commonPathPrefix = commonPathPrefix.substr(
|
||||
1,
|
||||
commonPathPrefix.lastIndexOf('/') - 1
|
||||
commonPathPrefix = commonPathPrefix.slice(
|
||||
0,
|
||||
commonPathPrefix.lastIndexOf('/')
|
||||
);
|
||||
if (commonPathPrefix) {
|
||||
commonPathPrefix = commonPathPrefix.substr(
|
||||
commonPathPrefix = commonPathPrefix.slice(
|
||||
commonPathPrefix.lastIndexOf('/') + 1
|
||||
);
|
||||
}
|
||||
|
@ -210,11 +218,11 @@ export default function Upload(props: Props) {
|
|||
for (const file of toUploadFiles.current) {
|
||||
const filePath = file['path'] as string;
|
||||
|
||||
let folderPath = filePath.substr(0, filePath.lastIndexOf('/'));
|
||||
let folderPath = filePath.slice(0, filePath.lastIndexOf('/'));
|
||||
if (folderPath.endsWith(METADATA_FOLDER_NAME)) {
|
||||
folderPath = folderPath.substr(0, folderPath.lastIndexOf('/'));
|
||||
folderPath = folderPath.slice(0, folderPath.lastIndexOf('/'));
|
||||
}
|
||||
const folderName = folderPath.substr(
|
||||
const folderName = folderPath.slice(
|
||||
folderPath.lastIndexOf('/') + 1
|
||||
);
|
||||
if (!collectionWiseFiles.has(folderName)) {
|
||||
|
@ -227,7 +235,6 @@ export default function Upload(props: Props) {
|
|||
|
||||
const uploadFilesToExistingCollection = async (collection: Collection) => {
|
||||
try {
|
||||
uploadInit();
|
||||
const filesWithCollectionToUpload: FileWithCollection[] =
|
||||
toUploadFiles.current.map((file, index) => ({
|
||||
file,
|
||||
|
@ -245,8 +252,6 @@ export default function Upload(props: Props) {
|
|||
collectionName?: string
|
||||
) => {
|
||||
try {
|
||||
uploadInit();
|
||||
|
||||
const filesWithCollectionToUpload: FileWithCollection[] = [];
|
||||
const collections: Collection[] = [];
|
||||
let collectionWiseFiles = new Map<
|
||||
|
@ -298,13 +303,24 @@ export default function Upload(props: Props) {
|
|||
collections: Collection[]
|
||||
) => {
|
||||
try {
|
||||
uploadInit();
|
||||
props.setUploadInProgress(true);
|
||||
props.closeCollectionSelector();
|
||||
await props.syncWithRemote(true, true);
|
||||
if (isElectron()) {
|
||||
if (isElectron() && !isPendingDesktopUpload.current) {
|
||||
await ImportService.setToUploadCollection(collections);
|
||||
if (zipPaths.current) {
|
||||
await ImportService.setToUploadFiles(
|
||||
DESKTOP_UPLOAD_TYPE.ZIPS,
|
||||
zipPaths.current
|
||||
);
|
||||
zipPaths.current = null;
|
||||
}
|
||||
await ImportService.setToUploadFiles(
|
||||
filesWithCollectionToUpload,
|
||||
collections
|
||||
DESKTOP_UPLOAD_TYPE.FILES,
|
||||
filesWithCollectionToUpload.map(
|
||||
({ file }) => (file as ElectronFile).path
|
||||
)
|
||||
);
|
||||
}
|
||||
await uploadManager.queueFilesForUpload(
|
||||
|
@ -320,7 +336,6 @@ export default function Upload(props: Props) {
|
|||
setProgressView(false);
|
||||
throw err;
|
||||
} finally {
|
||||
appContext.resetSharedFiles();
|
||||
props.setUploadInProgress(false);
|
||||
props.syncWithRemote();
|
||||
}
|
||||
|
@ -374,6 +389,7 @@ export default function Upload(props: Props) {
|
|||
uploadToSingleNewCollection(
|
||||
pendingDesktopUploadCollectionName.current
|
||||
);
|
||||
pendingDesktopUploadCollectionName.current = null;
|
||||
} else {
|
||||
uploadFilesToNewCollections(
|
||||
UPLOAD_STRATEGY.COLLECTION_PER_FOLDER
|
||||
|
@ -381,6 +397,13 @@ export default function Upload(props: Props) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isElectron() &&
|
||||
desktopUploadType.current === DESKTOP_UPLOAD_TYPE.ZIPS
|
||||
) {
|
||||
uploadFilesToNewCollections(UPLOAD_STRATEGY.COLLECTION_PER_FOLDER);
|
||||
return;
|
||||
}
|
||||
if (isFirstUpload && !analysisResult.suggestedCollectionName) {
|
||||
analysisResult.suggestedCollectionName = FIRST_ALBUM_NAME;
|
||||
}
|
||||
|
@ -404,21 +427,26 @@ export default function Upload(props: Props) {
|
|||
desktopUploadType.current = type;
|
||||
if (type === DESKTOP_UPLOAD_TYPE.FILES) {
|
||||
files = await ImportService.showUploadFilesDialog();
|
||||
} else {
|
||||
} else if (type === DESKTOP_UPLOAD_TYPE.FOLDERS) {
|
||||
files = await ImportService.showUploadDirsDialog();
|
||||
} else {
|
||||
const response = await ImportService.showUploadZipDialog();
|
||||
files = response.files;
|
||||
zipPaths.current = response.zipPaths;
|
||||
}
|
||||
if (files?.length > 0) {
|
||||
props.setElectronFiles(files);
|
||||
props.setShowUploadTypeChoiceModal(false);
|
||||
}
|
||||
props.setElectronFiles(files);
|
||||
props.setShowUploadTypeChoiceModal(false);
|
||||
};
|
||||
|
||||
const cancelUploads = async () => {
|
||||
setProgressView(false);
|
||||
UploadManager.cancelRemainingUploads();
|
||||
if (isElectron()) {
|
||||
ImportService.updatePendingUploads([]);
|
||||
ImportService.cancelRemainingUploads();
|
||||
}
|
||||
await props.setUploadInProgress(false);
|
||||
await props.syncWithRemote();
|
||||
Router.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -446,6 +474,9 @@ export default function Upload(props: Props) {
|
|||
uploadFolders={() =>
|
||||
handleDesktopUploadTypes(DESKTOP_UPLOAD_TYPE.FOLDERS)
|
||||
}
|
||||
uploadGoogleTakeoutZips={() =>
|
||||
handleDesktopUploadTypes(DESKTOP_UPLOAD_TYPE.ZIPS)
|
||||
}
|
||||
/>
|
||||
<UploadProgress
|
||||
now={percentComplete}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ButtonVariant, getVariantColor } from './LinkButton';
|
|||
import { FileUploadResults, UPLOAD_STAGES } from 'constants/upload';
|
||||
import FileList from 'components/FileList';
|
||||
import { AppContext } from 'pages/_app';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
interface Props {
|
||||
fileCounter;
|
||||
uploadStage;
|
||||
|
@ -225,7 +226,6 @@ export default function UploadProgress(props: Props) {
|
|||
<>
|
||||
<Modal
|
||||
show={props.show}
|
||||
onHide={handleHideModal()}
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
backdrop={fileProgressStatuses?.length !== 0 ? 'static' : true}>
|
||||
|
@ -237,8 +237,7 @@ export default function UploadProgress(props: Props) {
|
|||
borderBottom: 'none',
|
||||
paddingTop: '30px',
|
||||
paddingBottom: '0px',
|
||||
}}
|
||||
closeButton={true}>
|
||||
}}>
|
||||
<h4 style={{ width: '100%' }}>
|
||||
{props.uploadStage === UPLOAD_STAGES.UPLOADING
|
||||
? constants.UPLOAD[props.uploadStage](
|
||||
|
@ -246,6 +245,11 @@ export default function UploadProgress(props: Props) {
|
|||
)
|
||||
: constants.UPLOAD[props.uploadStage]}
|
||||
</h4>
|
||||
<IoMdClose
|
||||
size={30}
|
||||
onClick={handleHideModal()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{(props.uploadStage ===
|
||||
|
|
|
@ -4,12 +4,49 @@ import constants from 'utils/strings/constants';
|
|||
import { IoIosArrowForward, IoMdClose } from 'react-icons/io';
|
||||
import FileUploadIcon from 'components/icons/FileUploadIcon';
|
||||
import FolderUploadIcon from 'components/icons/FolderUploadIcon';
|
||||
import { BsGoogle } from 'react-icons/bs';
|
||||
|
||||
function UploadTypeRow({ uploadFunc, Icon, uploadName }) {
|
||||
return (
|
||||
<Row className="justify-content-sm-center py-2">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={uploadFunc}
|
||||
style={{ width: '90%', height: '50px' }}>
|
||||
<Container>
|
||||
<Row>
|
||||
<div>
|
||||
<Icon />
|
||||
<b className="ml-2">{uploadName}</b>
|
||||
</div>
|
||||
<div className="ml-auto d-flex align-items-center">
|
||||
<IoIosArrowForward />
|
||||
</div>
|
||||
</Row>
|
||||
</Container>
|
||||
</Button>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function GoogleIcon() {
|
||||
return (
|
||||
<BsGoogle
|
||||
size={25}
|
||||
style={{
|
||||
marginRight: '0.2em',
|
||||
marginLeft: '0.2em',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UploadTypeChoiceModal({
|
||||
onHide,
|
||||
show,
|
||||
uploadFiles,
|
||||
uploadFolders,
|
||||
uploadGoogleTakeoutZips,
|
||||
}) {
|
||||
return (
|
||||
<Modal
|
||||
|
@ -38,51 +75,23 @@ export default function UploadTypeChoiceModal({
|
|||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Modal.Header>
|
||||
<Modal.Body
|
||||
style={{
|
||||
height: '10em',
|
||||
}}>
|
||||
<Modal.Body>
|
||||
<Container>
|
||||
<Row className="justify-content-center py-2">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={uploadFiles}
|
||||
style={{ width: '90%', height: '3em' }}>
|
||||
<Container>
|
||||
<Row>
|
||||
<div>
|
||||
<FileUploadIcon />
|
||||
<b className="ml-2">
|
||||
{constants.UPLOAD_FILES}
|
||||
</b>
|
||||
</div>
|
||||
<div className="ml-auto d-flex align-items-center">
|
||||
<IoIosArrowForward />
|
||||
</div>
|
||||
</Row>
|
||||
</Container>
|
||||
</Button>
|
||||
</Row>
|
||||
<Row className="justify-content-center py-2">
|
||||
<Button
|
||||
variant="light"
|
||||
onClick={uploadFolders}
|
||||
style={{ width: '90%', height: '3em' }}>
|
||||
<Container>
|
||||
<Row>
|
||||
<div>
|
||||
<FolderUploadIcon />
|
||||
<b className="ml-2">
|
||||
{constants.UPLOAD_DIRS}
|
||||
</b>
|
||||
</div>
|
||||
<div className="ml-auto d-flex align-items-center">
|
||||
<IoIosArrowForward />
|
||||
</div>
|
||||
</Row>
|
||||
</Container>
|
||||
</Button>
|
||||
</Row>
|
||||
<UploadTypeRow
|
||||
uploadFunc={uploadFiles}
|
||||
Icon={FileUploadIcon}
|
||||
uploadName={constants.UPLOAD_FILES}
|
||||
/>
|
||||
<UploadTypeRow
|
||||
uploadFunc={uploadFolders}
|
||||
Icon={FolderUploadIcon}
|
||||
uploadName={constants.UPLOAD_DIRS}
|
||||
/>
|
||||
<UploadTypeRow
|
||||
uploadFunc={uploadGoogleTakeoutZips}
|
||||
Icon={GoogleIcon}
|
||||
uploadName={constants.UPLOAD_GOOGLE_TAKEOUT}
|
||||
/>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
|
|
|
@ -206,6 +206,7 @@ export default function Gallery() {
|
|||
const [electronFiles, setElectronFiles] = useState<ElectronFile[]>(null);
|
||||
const [showUploadTypeChoiceModal, setShowUploadTypeChoiceModal] =
|
||||
useState(false);
|
||||
const [droppedFiles, setDroppedFiles] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const key = getKey(SESSION_KEYS.ENCRYPTION_KEY);
|
||||
|
@ -253,6 +254,8 @@ export default function Gallery() {
|
|||
[fixCreationTimeAttributes]
|
||||
);
|
||||
|
||||
useEffect(() => setDroppedFiles(acceptedFiles), [acceptedFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof activeCollection === 'undefined') {
|
||||
return;
|
||||
|
@ -640,7 +643,8 @@ export default function Gallery() {
|
|||
<Upload
|
||||
syncWithRemote={syncWithRemote}
|
||||
setBannerMessage={setBannerMessage}
|
||||
acceptedFiles={acceptedFiles}
|
||||
droppedFiles={droppedFiles}
|
||||
clearDroppedFiles={() => setDroppedFiles([])}
|
||||
showCollectionSelector={setCollectionSelectorView.bind(
|
||||
null,
|
||||
true
|
||||
|
@ -655,6 +659,7 @@ export default function Gallery() {
|
|||
setLoading={setBlockingLoad}
|
||||
setCollectionNamerAttributes={setCollectionNamerAttributes}
|
||||
setDialogMessage={setDialogMessage}
|
||||
uploadInProgress={uploadInProgress}
|
||||
setUploadInProgress={setUploadInProgress}
|
||||
fileRejections={fileRejections}
|
||||
setFiles={setFiles}
|
||||
|
|
|
@ -4,7 +4,9 @@ import QueueProcessor from 'services/queueProcessor';
|
|||
import { ParsedExtractedMetadata } from 'types/upload';
|
||||
|
||||
import { FFmpegWorker } from 'utils/comlink';
|
||||
import { promiseWithTimeout } from 'utils/common';
|
||||
|
||||
const FFMPEG_EXECUTION_WAIT_TIME = 10 * 1000;
|
||||
class FFmpegService {
|
||||
private ffmpegWorker = null;
|
||||
private ffmpegTaskQueue = new QueueProcessor<any>(1);
|
||||
|
@ -18,8 +20,11 @@ class FFmpegService {
|
|||
await this.init();
|
||||
}
|
||||
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(
|
||||
async () => await this.ffmpegWorker.generateThumbnail(file)
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(() =>
|
||||
promiseWithTimeout(
|
||||
this.ffmpegWorker.generateThumbnail(file),
|
||||
FFMPEG_EXECUTION_WAIT_TIME
|
||||
)
|
||||
);
|
||||
try {
|
||||
return await response.promise;
|
||||
|
@ -39,8 +44,11 @@ class FFmpegService {
|
|||
await this.init();
|
||||
}
|
||||
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(
|
||||
async () => await this.ffmpegWorker.extractVideoMetadata(file)
|
||||
const response = this.ffmpegTaskQueue.queueUpRequest(() =>
|
||||
promiseWithTimeout(
|
||||
this.ffmpegWorker.extractVideoMetadata(file),
|
||||
FFMPEG_EXECUTION_WAIT_TIME
|
||||
)
|
||||
);
|
||||
try {
|
||||
return await response.promise;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { DESKTOP_UPLOAD_TYPE } from 'components/pages/gallery/Upload';
|
||||
import { Collection } from 'types/collection';
|
||||
import { ElectronFile, FileWithCollection } from 'types/upload';
|
||||
import { runningInBrowser } from 'utils/common';
|
||||
|
@ -6,6 +7,12 @@ import { logError } from 'utils/sentry';
|
|||
interface PendingUploads {
|
||||
files: ElectronFile[];
|
||||
collectionName: string;
|
||||
type: DESKTOP_UPLOAD_TYPE;
|
||||
}
|
||||
|
||||
interface selectZipResult {
|
||||
files: ElectronFile[];
|
||||
zipPaths: string[];
|
||||
}
|
||||
class ImportService {
|
||||
ElectronAPIs: any;
|
||||
|
@ -16,6 +23,14 @@ class ImportService {
|
|||
this.allElectronAPIsExist = !!this.ElectronAPIs?.getPendingUploads;
|
||||
}
|
||||
|
||||
async getElectronFilesFromGoogleZip(
|
||||
zipPath: string
|
||||
): Promise<ElectronFile[]> {
|
||||
if (this.allElectronAPIsExist) {
|
||||
return this.ElectronAPIs.getElectronFilesFromGoogleZip(zipPath);
|
||||
}
|
||||
}
|
||||
|
||||
checkAllElectronAPIsExists = () => this.allElectronAPIsExist;
|
||||
|
||||
async showUploadFilesDialog(): Promise<ElectronFile[]> {
|
||||
|
@ -30,6 +45,11 @@ class ImportService {
|
|||
}
|
||||
}
|
||||
|
||||
async showUploadZipDialog(): Promise<selectZipResult> {
|
||||
if (this.allElectronAPIsExist) {
|
||||
return this.ElectronAPIs.showUploadZipDialog();
|
||||
}
|
||||
}
|
||||
async getPendingUploads(): Promise<PendingUploads> {
|
||||
try {
|
||||
if (this.allElectronAPIsExist) {
|
||||
|
@ -39,16 +59,13 @@ class ImportService {
|
|||
}
|
||||
} catch (e) {
|
||||
logError(e, 'failed to getPendingUploads ');
|
||||
return { files: [], collectionName: null };
|
||||
return { files: [], collectionName: null, type: null };
|
||||
}
|
||||
}
|
||||
|
||||
async setToUploadFiles(
|
||||
files: FileWithCollection[],
|
||||
collections: Collection[]
|
||||
) {
|
||||
async setToUploadCollection(collections: Collection[]) {
|
||||
if (this.allElectronAPIsExist) {
|
||||
let collectionName: string;
|
||||
let collectionName: string = null;
|
||||
/* collection being one suggest one of two things
|
||||
1. Either the user has upload to a single existing collection
|
||||
2. Created a new single collection to upload to
|
||||
|
@ -61,13 +78,17 @@ class ImportService {
|
|||
if (collections.length === 1) {
|
||||
collectionName = collections[0].name;
|
||||
}
|
||||
const filePaths = files.map(
|
||||
(file) => (file.file as ElectronFile).path
|
||||
);
|
||||
this.ElectronAPIs.setToUploadFiles(filePaths);
|
||||
this.ElectronAPIs.setToUploadCollection(collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
async setToUploadFiles(
|
||||
type: DESKTOP_UPLOAD_TYPE.FILES | DESKTOP_UPLOAD_TYPE.ZIPS,
|
||||
filePaths: string[]
|
||||
) {
|
||||
this.ElectronAPIs.setToUploadFiles(type, filePaths);
|
||||
}
|
||||
|
||||
updatePendingUploads(files: FileWithCollection[]) {
|
||||
if (this.allElectronAPIsExist) {
|
||||
const filePaths = [];
|
||||
|
@ -89,9 +110,14 @@ class ImportService {
|
|||
);
|
||||
}
|
||||
}
|
||||
this.ElectronAPIs.setToUploadFiles(filePaths);
|
||||
this.setToUploadFiles(DESKTOP_UPLOAD_TYPE.FILES, filePaths);
|
||||
}
|
||||
}
|
||||
cancelRemainingUploads() {
|
||||
this.ElectronAPIs.setToUploadCollection(null);
|
||||
this.ElectronAPIs.setToUploadFiles(DESKTOP_UPLOAD_TYPE.ZIPS, []);
|
||||
this.ElectronAPIs.setToUploadFiles(DESKTOP_UPLOAD_TYPE.FILES, []);
|
||||
}
|
||||
}
|
||||
|
||||
export default new ImportService();
|
||||
|
|
|
@ -60,10 +60,8 @@ class UploadManager {
|
|||
UIService.init(progressUpdater);
|
||||
this.setFiles = setFiles;
|
||||
}
|
||||
private uploadCancelled: boolean;
|
||||
|
||||
private resetState() {
|
||||
this.uploadCancelled = false;
|
||||
this.filesToBeUploaded = [];
|
||||
this.remainingFiles = [];
|
||||
this.failedFiles = [];
|
||||
|
@ -115,6 +113,7 @@ class UploadManager {
|
|||
UploadService.setMetadataAndFileTypeInfoMap(
|
||||
this.metadataAndFileTypeInfoMap
|
||||
);
|
||||
|
||||
UIService.setUploadStage(UPLOAD_STAGES.START);
|
||||
logUploadInfo(`clusterLivePhotoFiles called`);
|
||||
const analysedMediaFiles =
|
||||
|
@ -159,9 +158,6 @@ class UploadManager {
|
|||
const reader = new FileReader();
|
||||
for (const { file, collectionID } of metadataFiles) {
|
||||
try {
|
||||
if (this.uploadCancelled) {
|
||||
break;
|
||||
}
|
||||
logUploadInfo(
|
||||
`parsing metadata json file ${getFileNameSize(file)}`
|
||||
);
|
||||
|
@ -206,9 +202,6 @@ class UploadManager {
|
|||
const reader = new FileReader();
|
||||
for (const { file, localID, collectionID } of mediaFiles) {
|
||||
try {
|
||||
if (this.uploadCancelled) {
|
||||
break;
|
||||
}
|
||||
const { fileTypeInfo, metadata } = await (async () => {
|
||||
if (file.size >= MAX_FILE_SIZE_SUPPORTED) {
|
||||
logUploadInfo(
|
||||
|
@ -269,9 +262,6 @@ class UploadManager {
|
|||
}
|
||||
|
||||
private async uploadMediaFiles(mediaFiles: FileWithCollection[]) {
|
||||
if (this.uploadCancelled) {
|
||||
return;
|
||||
}
|
||||
logUploadInfo(`uploadMediaFiles called`);
|
||||
this.filesToBeUploaded.push(...mediaFiles);
|
||||
|
||||
|
@ -308,9 +298,6 @@ class UploadManager {
|
|||
|
||||
private async uploadNextFileInQueue(worker: any, reader: FileReader) {
|
||||
while (this.filesToBeUploaded.length > 0) {
|
||||
if (this.uploadCancelled) {
|
||||
return;
|
||||
}
|
||||
const fileWithCollection = this.filesToBeUploaded.pop();
|
||||
const { collectionID } = fileWithCollection;
|
||||
const existingFilesInCollection =
|
||||
|
@ -376,11 +363,6 @@ class UploadManager {
|
|||
...this.collections.values(),
|
||||
]);
|
||||
}
|
||||
|
||||
cancelRemainingUploads() {
|
||||
this.remainingFiles = [];
|
||||
this.uploadCancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
export default new UploadManager();
|
||||
|
|
|
@ -2,13 +2,18 @@ import { NULL_EXTRACTED_METADATA } from 'constants/upload';
|
|||
import ffmpegService from 'services/ffmpeg/ffmpegService';
|
||||
import { ElectronFile } from 'types/upload';
|
||||
import { logError } from 'utils/sentry';
|
||||
import { logUploadInfo } from 'utils/upload';
|
||||
|
||||
export async function getVideoMetadata(file: File | ElectronFile) {
|
||||
let videoMetadata = NULL_EXTRACTED_METADATA;
|
||||
if (!(file instanceof File)) {
|
||||
logUploadInfo('get file blob for video metadata extraction');
|
||||
file = new File([await file.blob()], file.name, {
|
||||
lastModified: file.lastModified,
|
||||
});
|
||||
logUploadInfo(
|
||||
'get file blob for video metadata extraction successfully'
|
||||
);
|
||||
}
|
||||
try {
|
||||
videoMetadata = await ffmpegService.extractMetadata(file);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import constants from 'utils/strings/constants';
|
||||
import { CustomError } from 'utils/error';
|
||||
|
||||
export const DESKTOP_APP_DOWNLOAD_URL =
|
||||
'https://github.com/ente-io/bhari-frame/releases/latest';
|
||||
|
@ -30,3 +31,25 @@ export function reverseString(title: string) {
|
|||
?.split(' ')
|
||||
.reduce((reversedString, currWord) => `${currWord} ${reversedString}`);
|
||||
}
|
||||
|
||||
export const promiseWithTimeout = async (
|
||||
request: Promise<any>,
|
||||
timeout: number
|
||||
) => {
|
||||
const timeoutRef = { current: null };
|
||||
const rejectOnTimeout = new Promise((_, reject) => {
|
||||
timeoutRef.current = setTimeout(
|
||||
() => reject(Error(CustomError.WAIT_TIME_EXCEEDED)),
|
||||
timeout
|
||||
);
|
||||
});
|
||||
const requestWithTimeOutCancellation = async () => {
|
||||
const resp = await request;
|
||||
clearTimeout(timeoutRef.current);
|
||||
return resp;
|
||||
};
|
||||
return await Promise.race([
|
||||
requestWithTimeOutCancellation(),
|
||||
rejectOnTimeout,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -298,25 +298,25 @@ export const preservePhotoswipeProps =
|
|||
return fileWithPreservedProperty;
|
||||
};
|
||||
|
||||
export function fileNameWithoutExtension(filename) {
|
||||
export function fileNameWithoutExtension(filename: string) {
|
||||
const lastDotPosition = filename.lastIndexOf('.');
|
||||
if (lastDotPosition === -1) return filename;
|
||||
else return filename.substr(0, lastDotPosition);
|
||||
else return filename.slice(0, lastDotPosition);
|
||||
}
|
||||
|
||||
export function fileExtensionWithDot(filename) {
|
||||
export function fileExtensionWithDot(filename: string) {
|
||||
const lastDotPosition = filename.lastIndexOf('.');
|
||||
if (lastDotPosition === -1) return '';
|
||||
else return filename.substr(lastDotPosition);
|
||||
else return filename.slice(lastDotPosition);
|
||||
}
|
||||
|
||||
export function splitFilenameAndExtension(filename): [string, string] {
|
||||
export function splitFilenameAndExtension(filename: string): [string, string] {
|
||||
const lastDotPosition = filename.lastIndexOf('.');
|
||||
if (lastDotPosition === -1) return [filename, null];
|
||||
else
|
||||
return [
|
||||
filename.substr(0, lastDotPosition),
|
||||
filename.substr(lastDotPosition + 1),
|
||||
filename.slice(0, lastDotPosition),
|
||||
filename.slice(lastDotPosition + 1),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -693,9 +693,10 @@ const englishConstants = {
|
|||
LOCK: 'lock',
|
||||
DOWNLOAD_UPLOAD_LOGS: 'debug logs',
|
||||
CHOOSE_UPLOAD_TYPE: 'Upload',
|
||||
UPLOAD_FILES: 'File Upload',
|
||||
UPLOAD_DIRS: 'Folder Upload',
|
||||
CANCEL_UPLOADS: 'cancel uploads',
|
||||
UPLOAD_FILES: 'File',
|
||||
UPLOAD_DIRS: 'Folder',
|
||||
UPLOAD_GOOGLE_TAKEOUT: 'Google Takeout',
|
||||
DEDUPLICATE_FILES: 'deduplicate files',
|
||||
NO_DUPLICATES_FOUND: "you've no duplicate files that can be cleared",
|
||||
CLUB_BY_CAPTURE_TIME: 'club by capture time',
|
||||
|
|
Loading…
Reference in a new issue