Merge pull request #464 from ente-io/zip

Upload Google Takeout
This commit is contained in:
Abhinav Kumar 2022-04-25 11:00:06 +05:30 committed by GitHub
commit 11a2e0c3b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 248 additions and 154 deletions

View file

@ -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}

View file

@ -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 ===

View file

@ -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>

View file

@ -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}

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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);

View 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,
]);
};

View file

@ -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),
];
}

View file

@ -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',