135 lines
5 KiB
TypeScript
135 lines
5 KiB
TypeScript
|
(async () => {
|
||
|
const dir = '/home/ben/Nextcloud';
|
||
|
|
||
|
const DRY_RUN = false;
|
||
|
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 ${dir} -type f | grep conflicted\\ copy`],
|
||
|
stdout: 'piped',
|
||
|
});
|
||
|
|
||
|
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 = [];
|
||
|
|
||
|
for (let i = 0; i < totalFiles; i += BATCH_SIZE) {
|
||
|
fileData = [
|
||
|
...fileData,
|
||
|
...(await Promise.all(fileNames.slice(i, i + BATCH_SIZE).map(async (conflictedFileName, index) => {
|
||
|
const conflictedNameMatch = conflictedFileName.match(CONFLICTED_COPY_REGEX);
|
||
|
if (!conflictedNameMatch) {
|
||
|
return `[${conflictedFileName}] encountered regex error`;
|
||
|
}
|
||
|
const conflictString = conflictedNameMatch[1];
|
||
|
const nonConflictedFileName = conflictedFileName.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);
|
||
|
})();
|