|
| 1 | +// Finds duplicates within folders, recursively |
| 2 | + |
| 3 | +const pcloudSdk = require('pcloud-sdk-js'); |
| 4 | +const pMap = require('p-map'); |
| 5 | +const delay = require('delay'); |
| 6 | + |
| 7 | +const {getFoldersRecursive} = require('../lib/iter'); |
| 8 | + |
| 9 | +const client = pcloudSdk.createClient(process.env.ACCESS_TOKEN); |
| 10 | + |
| 11 | +function groupByHash(files) { |
| 12 | + const hashToFilesMap = new Map(); |
| 13 | + for (const file of files) { |
| 14 | + const fileGroup = hashToFilesMap.get(file.hash); |
| 15 | + if (fileGroup) { |
| 16 | + fileGroup.push(file); |
| 17 | + } else { |
| 18 | + hashToFilesMap.set(file.hash, [file]); |
| 19 | + } |
| 20 | + } |
| 21 | + |
| 22 | + // Map<hash, file[]> |
| 23 | + return hashToFilesMap; |
| 24 | +} |
| 25 | + |
| 26 | +function minDate(file) { |
| 27 | + const created = new Date(file.created).getTime(); |
| 28 | + const modified = new Date(file.modified).getTime(); |
| 29 | + return created < modified ? created : modified; |
| 30 | +} |
| 31 | + |
| 32 | +async function cleanFolder(folder) { |
| 33 | + const foldername = folder.name; |
| 34 | + const imageFiles = folder.contents.filter(file => file.category === 1); |
| 35 | + const hashToFilesMap = groupByHash(imageFiles); |
| 36 | + const fileGroups = [...hashToFilesMap.values()].filter( |
| 37 | + files => files.length > 1 |
| 38 | + ); |
| 39 | + fileGroups.forEach(fileGroup => fileGroup.sort((a, b) => minDate(a) - minDate(b))); |
| 40 | + |
| 41 | + const cleanedFileGroups = fileGroups.map( |
| 42 | + fileGroup => fileGroup.map(f => ({name: f.name, fileid: f.fileid})) |
| 43 | + ); |
| 44 | + |
| 45 | + const result = {foldername, fileGroups: cleanedFileGroups}; |
| 46 | + return result; |
| 47 | +} |
| 48 | + |
| 49 | +async function recursiveCleanFolder(folder) { |
| 50 | + const results = await pMap(getFoldersRecursive(folder), f => cleanFolder(f)); |
| 51 | + return results.filter(({fileGroups}) => fileGroups.length > 0); |
| 52 | +} |
| 53 | + |
| 54 | +async function run() { |
| 55 | + const path = process.env.FOLDER_PATH; |
| 56 | + const response = await client.api('listfolder', {params: {path, recursive: 1}}); |
| 57 | + const folder = response.metadata; |
| 58 | + const dupFilesPerFolder = await recursiveCleanFolder(folder); |
| 59 | + |
| 60 | + const deletion = dupFilesPerFolder.map(({foldername, fileGroups}) => { |
| 61 | + const keepDelGroups = fileGroups.map(g => ({ |
| 62 | + keep: g[0], |
| 63 | + del: g.slice(1) |
| 64 | + })); |
| 65 | + return {foldername, keepDelGroups}; |
| 66 | + }); |
| 67 | + |
| 68 | + const allToDelete = deletion |
| 69 | + .flatMap(({keepDelGroups}) => keepDelGroups.map(({del}) => del)) |
| 70 | + .flat(); |
| 71 | + |
| 72 | + await pMap( |
| 73 | + allToDelete, |
| 74 | + async file => { |
| 75 | + const promise = delay(Math.random() * 6000); |
| 76 | + console.log('Deleting', file.name); |
| 77 | + await client.deletefile(file.fileid); |
| 78 | + await promise; |
| 79 | + }, |
| 80 | + {concurrency: 10} |
| 81 | + ); |
| 82 | +} |
| 83 | + |
| 84 | +run().catch(error => { |
| 85 | + console.error(error); |
| 86 | +}); |
0 commit comments