import { join } from "https://deno.land/std@0.173.0/path/mod.ts"; (async () => { const mainDir = '/home/ben/Nextcloud'; const backupDir = '/home/ben/Nextcloud.bak'; const DRY_RUN = true; const BATCH_SIZE = 16; const ENCRYPTED_CONTENT_STRING = 'HBEGIN:oc_encryption_module:OC_DEFAULT_MODULE:cipher'; const CONFLICTED_COPY_REGEX = /.*( \(conflicted copy \d{4}-\d{2}-\d{2} \d+\))(\.[^\.]*)?/; const p = Deno.run({ cmd: ['bash', '-c', `find . -type f | grep conflicted\\ copy`], stdout: 'piped', cwd: backupDir }); const output = await p.output(); p.close(); const outputStr = (new TextDecoder()).decode(output); const fileNames = [...new Set(outputStr.split('\n').filter(s => !!s))]; const totalFiles = fileNames.length; let filesSuccess = 0; let fileData: string[] = []; for (let i = 0; i < totalFiles; i += BATCH_SIZE) { fileData = [ ...fileData, ...(await Promise.all(fileNames.slice(i, i + BATCH_SIZE).map(async (relativeConflictedFileName) => { const conflictedFileName = join(backupDir, relativeConflictedFileName); const conflictedNameMatch = conflictedFileName.match(CONFLICTED_COPY_REGEX); if (!conflictedNameMatch) { return `[${conflictedFileName}] encountered regex error`; } const conflictString = conflictedNameMatch[1]; const nonConflictedFileName = join(mainDir, relativeConflictedFileName.replace(conflictString, '')); const conflictedFile = await Deno.open(conflictedFileName, { read: true }); const conflictedFileInfo = await Deno.fstat(conflictedFile.rid); const normalFile = await Deno.open(nonConflictedFileName, { read: true }).catch(() => null); if (!normalFile) { if (!DRY_RUN) { // Only the conflicted file exists, just move it to the non-conflict filename await Deno.rename(conflictedFileName, nonConflictedFileName); } return `[${conflictedFileName}] only conflicted exists. Move to non-conflict filename`; } const normalFileInfo = await Deno.fstat(normalFile.rid); conflictedFile.close(); normalFile.close(); const shaP = Deno.run({ cmd: ['sha256sum', nonConflictedFileName, conflictedFileName], stdout: 'piped', }); const shaOutputStr = (new TextDecoder()).decode(await shaP.output()); const shaOutputs = shaOutputStr.split('\n'); const normalSha = shaOutputs[0].split(' ')[0]; const conflictedSha = shaOutputs[1].split(' ')[0]; shaP.close(); if (normalSha === conflictedSha) { if (!DRY_RUN) { // They're the same, remove the conflicted file await Deno.remove(conflictedFileName); } return `[${conflictedFileName}] conflict and non-conflict have the same contents, remove the conflict`; } const headP = Deno.run({ cmd: ['head', '-c', '64', nonConflictedFileName, conflictedFileName], stdout: 'piped', }); const headOutputStr = (new TextDecoder()).decode(await headP.output()); const headOutputs = headOutputStr.split('\n'); const normalHead = headOutputs[1].split(' ')[0]; const conflictedHead = headOutputs[3].split(' ')[0]; headP.close(); const normalEncrypted = normalHead.startsWith(ENCRYPTED_CONTENT_STRING); const conflictedEncrypted = conflictedHead.startsWith(ENCRYPTED_CONTENT_STRING); if (normalEncrypted && conflictedEncrypted) { console.error("Both files encrypted differently!"); console.error(nonConflictedFileName, conflictedFileName); if (!DRY_RUN) { // Whatever, deleted the conflicted one await Deno.remove(conflictedFileName); } return `[${conflictedFileName}] both are encrypted but with different contents. We'll remove the conflict`; } if (normalEncrypted) { if (!DRY_RUN) { // delete normal await Deno.remove(nonConflictedFileName); await Deno.rename(conflictedFileName, nonConflictedFileName); } return `[${conflictedFileName}] the non-conflict is encrypted. Remove it and move the conflicted file in its place`; } if (conflictedEncrypted) { if (!DRY_RUN) { // delete conflicted await Deno.remove(conflictedFileName); } return `[${conflictedFileName}] the conflict is encrypted. Remove it and keep the normal file`; } if (normalFileInfo.mtime > conflictedFileInfo.mtime) { if (!DRY_RUN) { // delete conflicted await Deno.remove(conflictedFileName); } return `[${conflictedFileName}] conflict is older than the non-conflict. Remove the conflict`; } if (!DRY_RUN) { // delete normal await Deno.remove(nonConflictedFileName); await Deno.rename(conflictedFileName, nonConflictedFileName); } return `[${conflictedFileName}] non-conflict is older than the conflict. Remove the non-conflict`; }).map(p => p.then((msg) => { filesSuccess++; console.log(`[${filesSuccess}/${totalFiles}] ${msg}`); return msg; })))), ]; } // console.log(fileData); })();