mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 02:12:30 +01:00
improve cleanup after export #1425
This commit is contained in:
parent
ff56c44a32
commit
4cda6579a7
@ -118,6 +118,9 @@ const defaults = {
|
||||
captureFrameFileNameFormat: 'timestamp',
|
||||
enableNativeHevc: true,
|
||||
enableUpdateCheck: true,
|
||||
cleanupChoices: {
|
||||
trashTmpFiles: true, askForCleanup: true,
|
||||
},
|
||||
};
|
||||
|
||||
// For portable app: https://github.com/mifi/lossless-cut/issues/645
|
||||
|
49
src/App.jsx
49
src/App.jsx
@ -147,7 +147,6 @@ const App = memo(() => {
|
||||
const [timelineMode, setTimelineMode] = useState();
|
||||
const [keyframesEnabled, setKeyframesEnabled] = useState(true);
|
||||
const [showRightBar, setShowRightBar] = useState(true);
|
||||
const [cleanupChoices, setCleanupChoices] = useState({ tmpFiles: true });
|
||||
const [rememberConvertToSupportedFormat, setRememberConvertToSupportedFormat] = useState();
|
||||
const [lastCommandsVisible, setLastCommandsVisible] = useState(false);
|
||||
const [settingsVisible, setSettingsVisible] = useState(false);
|
||||
@ -180,7 +179,7 @@ const App = memo(() => {
|
||||
const allUserSettings = useUserSettingsRoot();
|
||||
|
||||
const {
|
||||
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc,
|
||||
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices,
|
||||
} = allUserSettings;
|
||||
|
||||
useEffect(() => {
|
||||
@ -823,7 +822,7 @@ const App = memo(() => {
|
||||
setSelectedBatchFiles([]);
|
||||
}, [askBeforeClose]);
|
||||
|
||||
const batchRemoveFile = useCallback((path) => {
|
||||
const batchListRemoveFile = useCallback((path) => {
|
||||
setBatchFiles((existingBatch) => {
|
||||
const index = existingBatch.findIndex((existingFile) => existingFile.path === path);
|
||||
if (index < 0) return existingBatch;
|
||||
@ -947,41 +946,52 @@ const App = memo(() => {
|
||||
// Store paths before we reset state
|
||||
const savedPaths = { previewFilePath, sourceFilePath: filePath, projectFilePath: projectFileSavePath };
|
||||
|
||||
batchListRemoveFile(savedPaths.sourceFilePath);
|
||||
|
||||
// close the file
|
||||
resetState();
|
||||
|
||||
batchRemoveFile(savedPaths.sourceFilePath);
|
||||
|
||||
if (!cleanupChoices2.tmpFiles && !cleanupChoices2.projectFile && !cleanupChoices2.sourceFile) return;
|
||||
|
||||
try {
|
||||
setWorking(i18n.t('Cleaning up'));
|
||||
console.log('trashing', cleanupChoices2);
|
||||
await deleteFiles({ toDelete: cleanupChoices2, paths: savedPaths });
|
||||
console.log('Cleaning up files', cleanupChoices2);
|
||||
|
||||
const pathsToDelete = [];
|
||||
if (cleanupChoices2.trashTmpFiles && savedPaths.previewFilePath) pathsToDelete.push(savedPaths.previewFilePath);
|
||||
if (cleanupChoices2.trashProjectFile && savedPaths.projectFilePath) pathsToDelete.push(savedPaths.projectFilePath);
|
||||
if (cleanupChoices2.trashSourceFile && savedPaths.sourceFilePath) pathsToDelete.push(savedPaths.sourceFilePath);
|
||||
|
||||
await deleteFiles(pathsToDelete, cleanupChoices2.deleteIfTrashFails);
|
||||
} catch (err) {
|
||||
errorToast(i18n.t('Unable to delete file: {{message}}', { message: err.message }));
|
||||
console.error(err);
|
||||
}
|
||||
}, [batchRemoveFile, filePath, previewFilePath, projectFileSavePath, resetState, setWorking]);
|
||||
}, [batchListRemoveFile, filePath, previewFilePath, projectFileSavePath, resetState, setWorking]);
|
||||
|
||||
const askForCleanupChoices = useCallback(async () => {
|
||||
const trashResponse = await showCleanupFilesDialog(cleanupChoices);
|
||||
if (!trashResponse) return undefined; // Canceled
|
||||
setCleanupChoices(trashResponse); // Store for next time
|
||||
return trashResponse;
|
||||
}, [cleanupChoices, setCleanupChoices]);
|
||||
|
||||
const cleanupFilesDialog = useCallback(async () => {
|
||||
if (!isFileOpened) return;
|
||||
|
||||
let trashResponse = cleanupChoices;
|
||||
if (!cleanupChoices.dontShowAgain) {
|
||||
trashResponse = await showCleanupFilesDialog(cleanupChoices);
|
||||
console.log('trashResponse', trashResponse);
|
||||
if (!trashResponse) return; // Cancelled
|
||||
setCleanupChoices(trashResponse); // Store for next time
|
||||
let response = cleanupChoices;
|
||||
if (cleanupChoices.askForCleanup) {
|
||||
response = await askForCleanupChoices();
|
||||
console.log('trashResponse', response);
|
||||
if (!response) return; // Canceled
|
||||
}
|
||||
|
||||
if (workingRef.current) return;
|
||||
|
||||
try {
|
||||
await cleanupFiles(trashResponse);
|
||||
await cleanupFiles(response);
|
||||
} finally {
|
||||
setWorking();
|
||||
}
|
||||
}, [isFileOpened, cleanupChoices, cleanupFiles, setWorking]);
|
||||
}, [isFileOpened, cleanupChoices, askForCleanupChoices, cleanupFiles, setWorking]);
|
||||
|
||||
// For invertCutSegments we do not support filtering
|
||||
const selectedSegmentsOrInverseRaw = useMemo(() => (invertCutSegments ? inverseCutSegments : selectedSegmentsRaw), [inverseCutSegments, invertCutSegments, selectedSegmentsRaw]);
|
||||
@ -2118,7 +2128,7 @@ const App = memo(() => {
|
||||
batchFiles={batchFiles}
|
||||
setBatchFiles={setBatchFiles}
|
||||
onBatchFileSelect={onBatchFileSelect}
|
||||
batchRemoveFile={batchRemoveFile}
|
||||
batchListRemoveFile={batchListRemoveFile}
|
||||
closeBatch={closeBatch}
|
||||
onMergeFilesClick={concatCurrentBatch}
|
||||
onBatchConvertToSupportedFormatClick={convertFormatBatch}
|
||||
@ -2349,6 +2359,7 @@ const App = memo(() => {
|
||||
<Settings
|
||||
onTunerRequested={onTunerRequested}
|
||||
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
|
||||
askForCleanupChoices={askForCleanupChoices}
|
||||
/>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { FaYinYang, FaKeyboard } from 'react-icons/fa';
|
||||
import { CogIcon, Button, Table, NumericalIcon, KeyIcon, FolderCloseIcon, DocumentIcon, TimeIcon, Checkbox, Select } from 'evergreen-ui';
|
||||
import { CleanIcon, CogIcon, Button, Table, NumericalIcon, KeyIcon, FolderCloseIcon, DocumentIcon, TimeIcon, Checkbox, Select } from 'evergreen-ui';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import CaptureFormatButton from './components/CaptureFormatButton';
|
||||
@ -28,6 +28,7 @@ const Header = ({ title }) => (
|
||||
const Settings = memo(({
|
||||
onTunerRequested,
|
||||
onKeyboardShortcutsDialogRequested,
|
||||
askForCleanupChoices,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -205,6 +206,13 @@ const Settings = memo(({
|
||||
</Table.TextCell>
|
||||
</Row>
|
||||
|
||||
<Row>
|
||||
<KeyCell>{t('Cleanup files after export?')}</KeyCell>
|
||||
<Table.TextCell>
|
||||
<Button iconBefore={<CleanIcon />} onClick={askForCleanupChoices}>{t('Change preferences')}</Button>
|
||||
</Table.TextCell>
|
||||
</Row>
|
||||
|
||||
<Header title={t('Snapshots and frame extraction')} />
|
||||
|
||||
<Row>
|
||||
|
@ -20,7 +20,7 @@ const iconStyle = {
|
||||
padding: '3px 5px',
|
||||
};
|
||||
|
||||
const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles, setBatchFiles, onBatchFileSelect, batchRemoveFile, closeBatch, onMergeFilesClick, onBatchConvertToSupportedFormatClick }) => {
|
||||
const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles, setBatchFiles, onBatchFileSelect, batchListRemoveFile, closeBatch, onMergeFilesClick, onBatchConvertToSupportedFormatClick }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [sortDesc, setSortDesc] = useState();
|
||||
@ -64,7 +64,7 @@ const BatchFilesList = memo(({ selectedBatchFiles, filePath, width, batchFiles,
|
||||
<div style={{ overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<ReactSortable list={sortableList} setList={setSortableList}>
|
||||
{sortableList.map(({ batchFile: { path, name } }) => (
|
||||
<BatchFile key={path} path={path} name={name} isSelected={selectedBatchFiles.includes(path)} isOpen={filePath === path} onSelect={onBatchFileSelect} onDelete={batchRemoveFile} />
|
||||
<BatchFile key={path} path={path} name={name} isSelected={selectedBatchFiles.includes(path)} isOpen={filePath === path} onSelect={onBatchFileSelect} onDelete={batchListRemoveFile} />
|
||||
))}
|
||||
</ReactSortable>
|
||||
</div>
|
||||
|
@ -337,21 +337,26 @@ const CleanupChoices = ({ cleanupChoicesInitial, onChange: onChangeProp }) => {
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'left' }}>
|
||||
<p>{i18n.t('Do you want to move the original file and/or any generated files to trash?')}</p>
|
||||
<p>{i18n.t('What do you want to do after exporting a file or when pressing the "delete source file" button?')}</p>
|
||||
|
||||
<Checkbox label={i18n.t('Trash auto-generated files')} checked={getVal('tmpFiles')} onChange={(e) => onChange('tmpFiles', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash project LLC file')} checked={getVal('projectFile')} onChange={(e) => onChange('projectFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash original source file')} checked={getVal('sourceFile')} onChange={(e) => onChange('sourceFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Close currently opened file')} checked disabled />
|
||||
|
||||
<div style={{ marginTop: 25 }}>
|
||||
<Checkbox label={i18n.t('Don\'t show dialog again until restarting app')} checked={getVal('dontShowAgain')} onChange={(e) => onChange('dontShowAgain', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Do this automatically after export')} disabled={!getVal('dontShowAgain')} checked={getVal('cleanupAfterExport')} onChange={(e) => onChange('cleanupAfterExport', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash auto-generated files')} checked={getVal('trashTmpFiles')} onChange={(e) => onChange('trashTmpFiles', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash project LLC file')} checked={getVal('trashProjectFile')} onChange={(e) => onChange('trashProjectFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Trash original source file')} checked={getVal('trashSourceFile')} onChange={(e) => onChange('trashSourceFile', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Permanently delete the files if trash fails?')} disabled={!(getVal('trashTmpFiles') || getVal('trashProjectFile') || getVal('trashSourceFile'))} checked={getVal('deleteIfTrashFails')} onChange={(e) => onChange('deleteIfTrashFails', e.target.checked)} />
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 25 }}>
|
||||
<Checkbox label={i18n.t('Show this dialog every time?')} checked={getVal('askForCleanup')} onChange={(e) => onChange('askForCleanup', e.target.checked)} />
|
||||
<Checkbox label={i18n.t('Do all of this automatically after exporting a file?')} checked={getVal('cleanupAfterExport')} onChange={(e) => onChange('cleanupAfterExport', e.target.checked)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export async function showCleanupFilesDialog(cleanupChoicesIn = {}) {
|
||||
export async function showCleanupFilesDialog(cleanupChoicesIn) {
|
||||
let cleanupChoices = cleanupChoicesIn;
|
||||
|
||||
const { value } = await ReactSwal.fire({
|
||||
|
@ -129,6 +129,8 @@ export default () => {
|
||||
useEffect(() => safeSetConfig({ enableNativeHevc }), [enableNativeHevc]);
|
||||
const [enableUpdateCheck, setEnableUpdateCheck] = useState(safeGetConfigInitial('enableUpdateCheck'));
|
||||
useEffect(() => safeSetConfig({ enableUpdateCheck }), [enableUpdateCheck]);
|
||||
const [cleanupChoices, setCleanupChoices] = useState(safeGetConfigInitial('cleanupChoices'));
|
||||
useEffect(() => safeSetConfig({ cleanupChoices }), [cleanupChoices]);
|
||||
|
||||
|
||||
const resetKeyBindings = useCallback(() => {
|
||||
@ -236,5 +238,7 @@ export default () => {
|
||||
setEnableNativeHevc,
|
||||
enableUpdateCheck,
|
||||
setEnableUpdateCheck,
|
||||
cleanupChoices,
|
||||
setCleanupChoices,
|
||||
};
|
||||
};
|
||||
|
47
src/util.js
47
src/util.js
@ -236,48 +236,33 @@ export function getHtml5ifiedPath(cod, fp, type) {
|
||||
return getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${type}.${ext}` });
|
||||
}
|
||||
|
||||
export async function deleteFiles({ toDelete, paths: { previewFilePath, sourceFilePath, projectFilePath } }) {
|
||||
export async function deleteFiles(paths, deleteIfTrashFails) {
|
||||
const failedToTrashFiles = [];
|
||||
|
||||
if (toDelete.tmpFiles && previewFilePath) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const path of paths) {
|
||||
try {
|
||||
await trashFile(previewFilePath);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await trashFile(path);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
failedToTrashFiles.push(previewFilePath);
|
||||
}
|
||||
}
|
||||
if (toDelete.projectFile && projectFilePath) {
|
||||
try {
|
||||
// throw new Error('test');
|
||||
await trashFile(projectFilePath);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
failedToTrashFiles.push(projectFilePath);
|
||||
}
|
||||
}
|
||||
if (toDelete.sourceFile) {
|
||||
try {
|
||||
await trashFile(sourceFilePath);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
failedToTrashFiles.push(sourceFilePath);
|
||||
failedToTrashFiles.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedToTrashFiles.length === 0) return; // All good!
|
||||
|
||||
// todo allow bypassing trash altogether? https://github.com/mifi/lossless-cut/discussions/1425
|
||||
const { value } = await Swal.fire({
|
||||
icon: 'warning',
|
||||
text: i18n.t('Unable to move file to trash. Do you want to permanently delete it?'),
|
||||
confirmButtonText: i18n.t('Permanently delete'),
|
||||
showCancelButton: true,
|
||||
});
|
||||
|
||||
if (value) {
|
||||
await pMap(failedToTrashFiles, async (path) => unlink(path), { concurrency: 1 });
|
||||
if (!deleteIfTrashFails) {
|
||||
const { value } = await Swal.fire({
|
||||
icon: 'warning',
|
||||
text: i18n.t('Unable to move file to trash. Do you want to permanently delete it?'),
|
||||
confirmButtonText: i18n.t('Permanently delete'),
|
||||
showCancelButton: true,
|
||||
});
|
||||
if (!value) return;
|
||||
}
|
||||
|
||||
await pMap(failedToTrashFiles, async (path) => unlink(path), { concurrency: 1 });
|
||||
}
|
||||
|
||||
export const deleteDispositionValue = 'llc_disposition_remove';
|
||||
|
Loading…
Reference in New Issue
Block a user