1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-21 18:02:35 +01:00

retry deleting file

(hopefully) fixes #1797

also allow aborting
This commit is contained in:
Mikael Finstad 2023-12-08 16:21:32 +08:00
parent 5df1269b91
commit 7dbc8af226
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 86 additions and 27 deletions

View File

@ -71,6 +71,7 @@
"mkdirp": "^1.0.3",
"mousetrap": "^1.6.5",
"p-map": "^5.5.0",
"p-retry": "^6.1.0",
"pify": "^5.0.0",
"pretty-bytes": "^6.0.0",
"react": "^18.2.0",

View File

@ -171,13 +171,19 @@ const App = memo(() => {
const [selectedBatchFiles, setSelectedBatchFiles] = useState([]);
// Store "working" in a ref so we can avoid race conditions
const workingRef = useRef(working);
const workingRef = useRef(!!working);
const setWorking = useCallback((val) => {
workingRef.current = val;
setWorkingState(val);
workingRef.current = !!val;
setWorkingState(val ? { text: val.text, abortController: val.abortController } : undefined);
}, []);
useEffect(() => setDocumentTitle({ filePath, working, cutProgress }), [cutProgress, filePath, working]);
const handleAbortWorkingClick = useCallback(() => {
console.log('User clicked abort');
abortFfmpegs(); // todo use abortcontroller for this also
working?.abortController?.abort();
}, [working?.abortController]);
useEffect(() => setDocumentTitle({ filePath, working: working?.text, cutProgress }), [cutProgress, filePath, working?.text]);
const zoom = Math.floor(zoomUnrounded);
@ -593,7 +599,7 @@ const App = memo(() => {
const subtitleStream = index != null && subtitleStreams.find((s) => s.index === index);
if (!subtitleStream || workingRef.current) return;
try {
setWorking(i18n.t('Loading subtitle'));
setWorking({ text: i18n.t('Loading subtitle') });
const url = await extractSubtitleTrack(filePath, index);
setSubtitlesByStreamId((old) => ({ ...old, [index]: { url, lang: subtitleStream.tags && subtitleStream.tags.language } }));
setActiveSubtitleStreamIndex(index);
@ -805,7 +811,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Batch converting to supported format'));
setWorking({ text: i18n.t('Batch converting to supported format') });
setCutProgress(0);
// eslint-disable-next-line no-restricted-syntax
@ -841,7 +847,7 @@ const App = memo(() => {
const html5ifyAndLoadWithPreferences = useCallback(async (cod, fp, speed, hv, ha) => {
if (!enableAutoHtml5ify) return;
setWorking(i18n.t('Converting to supported format'));
setWorking({ text: i18n.t('Converting to supported format') });
await html5ifyAndLoad(cod, fp, getConvertToSupportedFormat(speed), hv, ha);
}, [enableAutoHtml5ify, setWorking, html5ifyAndLoad, getConvertToSupportedFormat]);
@ -996,7 +1002,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setConcatDialogVisible(false);
setWorking(i18n.t('Merging'));
setWorking({ text: i18n.t('Merging') });
const firstPath = paths[0];
if (!firstPath) return;
@ -1069,7 +1075,8 @@ const App = memo(() => {
}
try {
setWorking(i18n.t('Cleaning up'));
const abortController = new AbortController();
setWorking({ text: i18n.t('Cleaning up'), abortController });
console.log('Cleaning up files', cleanupChoices2);
const pathsToDelete = [];
@ -1077,7 +1084,7 @@ const App = memo(() => {
if (cleanupChoices2.trashProjectFile && savedPaths.projectFilePath) pathsToDelete.push(savedPaths.projectFilePath);
if (cleanupChoices2.trashSourceFile && savedPaths.sourceFilePath) pathsToDelete.push(savedPaths.sourceFilePath);
await deleteFiles(pathsToDelete, cleanupChoices2.deleteIfTrashFails);
await deleteFiles({ paths: pathsToDelete, deleteIfTrashFails: cleanupChoices2.deleteIfTrashFails, signal: abortController.signal });
} catch (err) {
errorToast(i18n.t('Unable to delete file: {{message}}', { message: err.message }));
console.error(err);
@ -1151,7 +1158,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Exporting'));
setWorking({ text: i18n.t('Exporting') });
// Special segments-to-chapters mode:
let chaptersToAdd;
@ -1199,7 +1206,7 @@ const App = memo(() => {
if (willMerge) {
setCutProgress(0);
setWorking(i18n.t('Merging'));
setWorking({ text: i18n.t('Merging') });
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
@ -1235,7 +1242,7 @@ const App = memo(() => {
if (exportExtraStreams) {
try {
setCutProgress(); // If extracting extra streams takes a long time, prevent loader from being stuck at 100%
setWorking(i18n.t('Extracting {{count}} unprocessable tracks', { count: nonCopiedExtraStreams.length }));
setWorking({ text: i18n.t('Extracting {{count}} unprocessable tracks', { count: nonCopiedExtraStreams.length }) });
await extractStreams({ filePath, customOutDir, streams: nonCopiedExtraStreams, enableOverwriteOutput });
notices.push(i18n.t('Unprocessable streams were exported as separate files.'));
} catch (err) {
@ -1311,7 +1318,7 @@ const App = memo(() => {
if (captureFramesResponse == null) return;
try {
setWorking(i18n.t('Extracting frames'));
setWorking({ text: i18n.t('Extracting frames') });
console.log('Extracting frames as images', { segIds, captureFramesResponse });
setCutProgress(0);
@ -1411,7 +1418,7 @@ const App = memo(() => {
}
}
setWorking(i18n.t('Loading file'));
setWorking({ text: i18n.t('Loading file') });
try {
// Need to check if file is actually readable
const pathReadAccessErrorCode = await getPathReadAccessError(fp);
@ -1586,7 +1593,7 @@ const App = memo(() => {
if (workingRef.current) return;
if (filePath === path) return;
try {
setWorking(i18n.t('Loading file'));
setWorking({ text: i18n.t('Loading file') });
await userOpenSingleFile({ path });
} catch (err) {
handleError(err);
@ -1645,7 +1652,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Extracting all streams'));
setWorking({ text: i18n.t('Extracting all streams') });
setStreamsSelectorShown(false);
const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainCopiedStreams, enableOverwriteOutput });
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: extractedPaths[0], text: i18n.t('All streams have been extracted as separate files') });
@ -1685,7 +1692,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Converting to supported format'));
setWorking({ text: i18n.t('Converting to supported format') });
await html5ifyAndLoad(customOutDir, filePath, selectedOption, hasVideo, hasAudio);
} catch (err) {
errorToast(i18n.t('Failed to convert file. Try a different conversion'));
@ -1712,7 +1719,7 @@ const App = memo(() => {
const tryFixInvalidDuration = useCallback(async () => {
if (!checkFileOpened() || workingRef.current) return;
try {
setWorking(i18n.t('Fixing file duration'));
setWorking({ text: i18n.t('Fixing file duration') });
setCutProgress(0);
const path = await fixInvalidDuration({ fileFormat, customOutDir, duration, onProgress: setCutProgress });
if (!hideAllNotifications) toast.fire({ icon: 'info', text: i18n.t('Duration has been fixed') });
@ -1809,7 +1816,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Loading file'));
setWorking({ text: i18n.t('Loading file') });
// Import segments for for already opened file
const matchingImportProjectType = getImportProjectType(firstFilePath);
@ -2075,7 +2082,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Extracting track'));
setWorking({ text: i18n.t('Extracting track') });
// setStreamsSelectorShown(false);
const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index), enableOverwriteOutput });
if (!hideAllNotifications) openDirToast({ icon: 'success', filePath: extractedPaths[0], text: i18n.t('Track has been extracted') });
@ -2111,7 +2118,7 @@ const App = memo(() => {
if (workingRef.current) return;
try {
setWorking(i18n.t('Converting to supported format'));
setWorking({ text: i18n.t('Converting to supported format') });
console.log('Trying to create preview');
@ -2388,7 +2395,7 @@ const App = memo(() => {
)}
<AnimatePresence>
{working && <Working text={working} cutProgress={cutProgress} onAbortClick={() => abortFfmpegs()} />}
{working && <Working text={working.text} cutProgress={cutProgress} onAbortClick={handleAbortWorkingClick} />}
</AnimatePresence>
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible()} />}

View File

@ -84,7 +84,7 @@ export default ({
if (!filePath) return;
if (workingRef.current) return;
try {
setWorking(workingText);
setWorking({ text: workingText });
setCutProgress(0);
const newSegments = await fn();
@ -250,7 +250,7 @@ export default ({
try {
const response = await askForAlignSegments();
if (response == null) return;
setWorking(i18n.t('Aligning segments to keyframes'));
setWorking({ text: i18n.t('Aligning segments to keyframes') });
const { mode, startOrEnd } = response;
await modifySelectedSegmentTimes(async (segment) => {
const newSegment = { ...segment };

View File

@ -3,6 +3,7 @@ import pMap from 'p-map';
import ky from 'ky';
import prettyBytes from 'pretty-bytes';
import sortBy from 'lodash/sortBy';
import pRetry from 'p-retry';
import isDev from './isDev';
import Swal, { toast } from './swal';
@ -234,14 +235,19 @@ export function getHtml5ifiedPath(cod, fp, type) {
return getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: `${html5ifiedPrefix}${type}.${ext}` });
}
export async function deleteFiles(paths, deleteIfTrashFails) {
export async function deleteFiles({ paths, deleteIfTrashFails, signal }) {
const failedToTrashFiles = [];
// const testFail = isDev;
const testFail = false;
// eslint-disable-next-line no-restricted-syntax
for (const path of paths) {
try {
if (testFail) throw new Error('test trash failure');
// eslint-disable-next-line no-await-in-loop
await trashFile(path);
signal.throwIfAborted();
} catch (err) {
console.error(err);
failedToTrashFiles.push(path);
@ -260,7 +266,19 @@ export async function deleteFiles(paths, deleteIfTrashFails) {
if (!value) return;
}
await pMap(failedToTrashFiles, async (path) => unlink(path), { concurrency: 1 });
// Retry because sometimes it fails on windows #272 #1797
await pMap(failedToTrashFiles, async (path) => {
await pRetry(async () => {
if (testFail) throw new Error('test delete failure');
await unlink(path);
}, {
retries: 3,
signal,
onFailedAttempt: async () => {
console.warn('Retrying delete', path);
},
});
}, { concurrency: 1 });
}
export const deleteDispositionValue = 'llc_disposition_remove';

View File

@ -1703,6 +1703,13 @@ __metadata:
languageName: node
linkType: hard
"@types/retry@npm:0.12.2":
version: 0.12.2
resolution: "@types/retry@npm:0.12.2"
checksum: e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a
languageName: node
linkType: hard
"@types/scheduler@npm:*":
version: 0.16.2
resolution: "@types/scheduler@npm:0.16.2"
@ -5964,6 +5971,13 @@ __metadata:
languageName: node
linkType: hard
"is-network-error@npm:^1.0.0":
version: 1.0.0
resolution: "is-network-error@npm:1.0.0"
checksum: 2ca2b4b2d420015e0237abe28ebf316fcd26a82304b07432abf155759a3bee6895609ac91e692a72ad61b7fc902c3283b2dece61e1ddb05a6257777a8573e468
languageName: node
linkType: hard
"is-number-object@npm:^1.0.4":
version: 1.0.6
resolution: "is-number-object@npm:1.0.6"
@ -6597,6 +6611,7 @@ __metadata:
morgan: ^1.10.0
mousetrap: ^1.6.5
p-map: ^5.5.0
p-retry: ^6.1.0
pify: ^5.0.0
pretty-bytes: ^6.0.0
react: ^18.2.0
@ -7543,6 +7558,17 @@ __metadata:
languageName: node
linkType: hard
"p-retry@npm:^6.1.0":
version: 6.1.0
resolution: "p-retry@npm:6.1.0"
dependencies:
"@types/retry": 0.12.2
is-network-error: ^1.0.0
retry: ^0.13.1
checksum: 1083b2b72672205680f8a736583e31dce5d4ae472996cd06f4a33cd7ea11798d7712c202d253eb8afbdc80abf52f049651989c59f2e2ccca529e6b64d722b1f7
languageName: node
linkType: hard
"p-try@npm:^1.0.0":
version: 1.0.0
resolution: "p-try@npm:1.0.0"
@ -8484,6 +8510,13 @@ __metadata:
languageName: node
linkType: hard
"retry@npm:^0.13.1":
version: 0.13.1
resolution: "retry@npm:0.13.1"
checksum: 47c4d5be674f7c13eee4cfe927345023972197dbbdfba5d3af7e461d13b44de1bfd663bfc80d2f601f8ef3fc8164c16dd99655a221921954a65d044a2fc1233b
languageName: node
linkType: hard
"reusify@npm:^1.0.4":
version: 1.0.4
resolution: "reusify@npm:1.0.4"