diff --git a/knip.json b/knip.json index bec8d2861..5a1b86597 100644 --- a/knip.json +++ b/knip.json @@ -1,7 +1,7 @@ { "entry": ["src/apps/main/main.ts", "src/apps/renderer/index.tsx"], "exclude": ["devDependencies"], - "ignore": ["**/*.test.{ts,tsx}", "src/apps/shared/HttpClient/schema.ts", "src/migrations/v2.5.6/move-checkpoint-to-lokijs.ts"], + "ignore": ["**/*.test.{ts,tsx}", "src/apps/shared/HttpClient/schema.ts"], "ignoreBinaries": ["powershell", "esbuild", "node-gyp", "sqlite3"], "ignoreExportsUsedInFile": true, "project": ["src/**/*.{ts,tsx}"], diff --git a/package-lock.json b/package-lock.json index 8cf1a30dd..7053b6a6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "regenerator-runtime": "^0.13.9", "socket.io-client": "^4.8.3", "source-map-support": "^0.5.21", - "trash": "^10.1.1", "zustand": "^4.5.7" }, "devDependencies": { @@ -465,9 +464,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "license": "MIT", "engines": { @@ -1199,16 +1198,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", - "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2644,6 +2643,19 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/@electron/rebuild/node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@electron/rebuild/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -4196,6 +4208,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4209,6 +4222,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4218,6 +4232,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4552,7 +4567,7 @@ "node_modules/@packages/addon": { "version": "1.0.0", "resolved": "file:packages/addon/packages-addon-1.0.0.tgz", - "integrity": "sha512-W0HreWwt42cAfTCRnmU0zBfQ5p3gbCmC4/8BVII1mnTpSCXXOfsSWruzHNINxHpd8B+JeZDM7apG4b6UaxTQpw==", + "integrity": "sha512-QwQK+40BNJwyb4F7iBzTB/tQ6/cwvfe6/Pu2smqIogw0EUPVeYh+ijgw7WkTtehM5ipA/3vDQ5jdOwkHki1NLA==", "license": "MIT" }, "node_modules/@peculiar/asn1-cms": { @@ -5798,18 +5813,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/df": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/df/-/df-3.1.1.tgz", - "integrity": "sha512-SME/vtXaJcnQ/HpeV6P82Egy+jThn11IKfwW8+/XVoRD0rmPHVTeKMtww1oWdVnMykzVPjmrDN9S8NBndPEHCQ==", - "license": "MIT", - "dependencies": { - "execa": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -5823,18 +5826,6 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", @@ -5851,15 +5842,6 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "license": "MIT" }, - "node_modules/@stroncium/procfs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@stroncium/procfs/-/procfs-1.2.1.tgz", - "integrity": "sha512-X1Iui3FUNZP18EUvysTHxt+Avu2nlVzyf90YM8OYgP6SGzTzzX/0JgObfO1AQQDzuZtNNz29bVh8h5R97JrjxA==", - "license": "CC0-1.0", - "engines": { - "node": ">=8" - } - }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", @@ -8769,6 +8751,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -9236,18 +9219,6 @@ "dev": true, "license": "MIT" }, - "node_modules/chunkify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chunkify/-/chunkify-5.0.0.tgz", - "integrity": "sha512-G8dj/3/Gm+1yL4oWSdwIxihZWFlgC4V2zYtIApacI0iPIRKBHlBGOGAiDUBZgrj4H8MBA8g8fPFwnJrWF3wl7Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", @@ -11395,6 +11366,7 @@ "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -12603,32 +12575,6 @@ "bare-events": "^2.7.0" } }, - "node_modules/execa": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", - "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^3.0.0", - "onetime": "^5.1.0", - "p-finally": "^2.0.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": "^8.12.0 || >=9.7.0" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/expect": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", @@ -12799,6 +12745,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -12826,9 +12773,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -12855,6 +12802,7 @@ "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12967,6 +12915,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -13448,6 +13397,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, "license": "MIT", "dependencies": { "pump": "^3.0.0" @@ -13514,6 +13464,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -14628,6 +14579,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -14643,6 +14595,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14697,6 +14650,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -14709,6 +14663,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -14776,6 +14731,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -15024,6 +14980,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -16254,12 +16211,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -16279,6 +16238,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -16292,6 +16252,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -16551,41 +16512,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/mount-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mount-point/-/mount-point-3.0.0.tgz", - "integrity": "sha512-jAhfD7ZCG+dbESZjcY1SdFVFqSJkh/yGbdsifHcPkvuLRO5ugK0Ssmd9jdATu29BTd4JiN+vkpMzVvsUgP3SZA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/df": "^1.0.1", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mount-point/node_modules/@sindresorhus/df": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/df/-/df-1.0.1.tgz", - "integrity": "sha512-1Hyp7NQnD/u4DSxR2DGW78TF9k7R0wZ8ev0BpMAIzA6yTQSHqNb5wTuvtcPYf4FWbVse2rW7RgDsyL8ua2vXHw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/move-file": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/move-file/-/move-file-4.1.0.tgz", - "integrity": "sha512-YE06K9XLIvMlqSfoZTl32qvbZLPgL70Za41wS8pEhsSOhy71xz2fn8J07nuz/LEEtPSuUzLUFGAJSx499eKDSw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -16928,18 +16854,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", - "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -17136,6 +17050,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -17338,15 +17253,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -17407,15 +17313,6 @@ "node": ">=8" } }, - "node_modules/p-finally": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", - "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -17448,18 +17345,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", @@ -17743,32 +17628,12 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "license": "MIT", - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -19030,18 +18895,6 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/powershell-utils": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz", - "integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -19336,6 +19189,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -19392,6 +19246,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -20260,6 +20115,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -20412,6 +20268,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -21670,15 +21527,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -22485,6 +22333,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -22549,122 +22398,6 @@ "node": ">=20" } }, - "node_modules/trash": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/trash/-/trash-10.1.1.tgz", - "integrity": "sha512-L/mu8sfblMwaS+exj1MxpmihlIRwVQyB6ieKuTTmBJG0lXWBPfx3pMGQG8i3NT/S8vvNZrflDUOp+j0o7Cnxzw==", - "license": "MIT", - "dependencies": { - "@stroncium/procfs": "^1.2.1", - "chunkify": "^5.0.0", - "globby": "^14.1.0", - "is-path-inside": "^4.0.0", - "move-file": "^4.1.0", - "p-map": "^7.0.3", - "powershell-utils": "^0.2.0", - "wsl-utils": "^0.4.0", - "xdg-trashdir": "^3.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/trash/node_modules/is-path-inside": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/wsl-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.4.0.tgz", - "integrity": "sha512-9YmF+2sFEd+T7TkwlmE337F0IVzfDvDknhtpBQxxXzEOfgPphGlFYpyx0cTuCIFj8/p+sqwBYAeGxOMNSzPPDA==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/trash/node_modules/wsl-utils/node_modules/powershell-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", - "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", @@ -23630,18 +23363,6 @@ "node": ">=4" } }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -23755,18 +23476,6 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==", - "license": "MIT", - "dependencies": { - "os-homedir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -24885,6 +24594,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/ws": { @@ -24925,30 +24635,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/xdg-trashdir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/xdg-trashdir/-/xdg-trashdir-3.1.0.tgz", - "integrity": "sha512-N1XQngeqMBoj9wM4ZFadVV2MymImeiFfYD+fJrNlcVcOHsJFFQe7n3b+aBoTPwARuq2HQxukfzVpQmAk1gN4sQ==", - "license": "MIT", - "dependencies": { - "@sindresorhus/df": "^3.1.1", - "mount-point": "^3.0.0", - "user-home": "^2.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/package.json b/package.json index 2ca6b3ee0..6761b8d1f 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,6 @@ "regenerator-runtime": "^0.13.9", "socket.io-client": "^4.8.3", "source-map-support": "^0.5.21", - "trash": "^10.1.1", "zustand": "^4.5.7" }, "engines": { diff --git a/packages/addon/dist/addon.node b/packages/addon/dist/addon.node index 271e2231b..1833b567d 100644 Binary files a/packages/addon/dist/addon.node and b/packages/addon/dist/addon.node differ diff --git a/packages/addon/include/external.h b/packages/addon/include/external.h index 647803963..2431294a8 100644 --- a/packages/addon/include/external.h +++ b/packages/addon/include/external.h @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include #include #include @@ -16,6 +18,7 @@ #include #include #include +#include #include namespace winrt diff --git a/packages/addon/include/helpers/napi_serializers.h b/packages/addon/include/helpers/napi_serializers.h index faf83b527..7e10dccd0 100644 --- a/packages/addon/include/helpers/napi_serializers.h +++ b/packages/addon/include/helpers/napi_serializers.h @@ -29,6 +29,19 @@ struct NapiSerializer> { } }; +template <> +struct NapiSerializer> { + static napi_value serialize(napi_env env, const std::vector& values) + { + napi_value result; + napi_create_array_with_length(env, values.size(), &result); + for (uint32_t i = 0; i < values.size(); i++) { + napi_set_element(env, result, i, NapiSerializer::serialize(env, values[i])); + } + return result; + } +}; + inline void napiSetString(napi_env env, napi_value obj, const char* key, const std::string& value) { napi_value val; diff --git a/packages/addon/include/internal.h b/packages/addon/include/internal.h index d381b2229..2c910ea49 100644 --- a/packages/addon/include/internal.h +++ b/packages/addon/include/internal.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +36,5 @@ #include #include #include -#include #include #include diff --git a/packages/addon/include/virtual_drive/get_file_explorers.h b/packages/addon/include/virtual_drive/get_file_explorers.h new file mode 100644 index 000000000..19635f4c8 --- /dev/null +++ b/packages/addon/include/virtual_drive/get_file_explorers.h @@ -0,0 +1,72 @@ +#pragma once + +inline std::wstring getPathFromDispatch(IDispatch* dispatch) +{ + CComQIPtr app(dispatch); + if (!app) winrt::check_hresult(E_NOINTERFACE); + + CComQIPtr sp(app); + if (!sp) winrt::check_hresult(E_NOINTERFACE); + + CComPtr sb; + winrt::check_hresult(sp->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, (void**)&sb)); + + CComPtr sv; + winrt::check_hresult(sb->QueryActiveShellView(&sv)); + + CComQIPtr fv(sv); + if (!fv) winrt::check_hresult(E_NOINTERFACE); + + CComPtr pf; + winrt::check_hresult(fv->GetFolder(IID_IPersistFolder2, (void**)&pf)); + + LPITEMIDLIST pidl = nullptr; + winrt::check_hresult(pf->GetCurFolder(&pidl)); + + wchar_t path[MAX_PATH] = {}; + BOOL ok = SHGetPathFromIDListW(pidl, path); + CoTaskMemFree(pidl); + if (!ok) return {}; + + return path; +} + +inline std::vector getFileExplorers() +{ + HRESULT coHr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + winrt::check_hresult(coHr); + + CComPtr shellWindows; + winrt::check_hresult(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, (void**)&shellWindows)); + + long count = 0; + shellWindows->get_Count(&count); + + std::vector paths; + for (long i = 0; i < count; i++) { + try { + CComVariant idx(i); + CComPtr dispatch; + shellWindows->Item(idx, &dispatch); + + std::wstring path = getPathFromDispatch(dispatch); + if (!path.empty()) { + paths.push_back(std::move(path)); + } + } catch (...) { + } + } + + CoUninitialize(); + return paths; +} + +inline napi_value getFileExplorersWrapper(napi_env env, napi_callback_info) +{ + return run_async(env, "GetFileExplorers", getFileExplorers); +} + +inline napi_value GetFileExplorersWrapper(napi_env env, napi_callback_info info) +{ + return NAPI_SAFE_WRAP(env, info, getFileExplorersWrapper); +} diff --git a/packages/addon/include/virtual_drive/watcher/watch_path.h b/packages/addon/include/virtual_drive/watcher/watch_path.h index 9aae86ae1..118312e97 100644 --- a/packages/addon/include/virtual_drive/watcher/watch_path.h +++ b/packages/addon/include/virtual_drive/watcher/watch_path.h @@ -1,6 +1,6 @@ #pragma once -inline void callJsCallback(napi_env env, napi_value jsCallback, void* context, void* data) +inline void callWatcherJsCallback(napi_env env, napi_value jsCallback, void* context, void* data) { WatcherEvent* event = static_cast(data); @@ -125,7 +125,7 @@ inline napi_value watchPathWrapper(napi_env env, napi_callback_info info) { auto [rootPath, onEventCallback] = napi_extract_args(env, info); - auto tsfn = registerThreadsafeCallback("WatchPathCallback", env, onEventCallback, callJsCallback); + auto tsfn = registerThreadsafeCallback("WatchPathCallback", env, onEventCallback, callWatcherJsCallback); auto ctx = new WatcherContext{tsfn, false}; @@ -142,8 +142,7 @@ inline napi_value watchPathWrapper(napi_env env, napi_callback_info info) napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release); delete ctx; } catch (...) { - auto error = format_exception_message("WatchPathThread"); - wprintf(L"Error in watch path thread: %s\n", error.c_str()); + wprintf(L"%S\n", format_exception_message("WatchPathThread").c_str()); } }).detach(); diff --git a/packages/addon/packages-addon-1.0.0.tgz b/packages/addon/packages-addon-1.0.0.tgz index 916119a0f..b3220737d 100644 Binary files a/packages/addon/packages-addon-1.0.0.tgz and b/packages/addon/packages-addon-1.0.0.tgz differ diff --git a/packages/addon/src/main.cpp b/packages/addon/src/main.cpp index bbe302df8..7132132a8 100644 --- a/packages/addon/src/main.cpp +++ b/packages/addon/src/main.cpp @@ -9,6 +9,7 @@ napi_value init(napi_env env, napi_value exports) {"createFolderPlaceholder", nullptr, CreateFolderPlaceholderWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, {"dehydrateFile", nullptr, DehydrateFileWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, {"disconnectSyncRoot", nullptr, DisconnectSyncRootWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"getFileExplorers", nullptr, GetFileExplorersWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getFirstNonPlaceholder", nullptr, getFirstNonPlaceholderWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getPlaceholderState", nullptr, GetPlaceholderStateWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, {"getRegisteredSyncRoots", nullptr, GetRegisteredSyncRootsWrapper, nullptr, nullptr, nullptr, napi_default, nullptr}, diff --git a/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.test.ts b/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.test.ts index 86adc4994..0934705a9 100644 --- a/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.test.ts +++ b/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.test.ts @@ -1,23 +1,20 @@ import { randomUUID } from 'node:crypto'; import { mkdir, rename, rm } from 'node:fs/promises'; -import trash from 'trash'; import { FileUuid } from '@/apps/main/database/entities/DriveFile'; import { abs } from '@/context/local/localFile/infrastructure/AbsolutePath'; import { Addon } from '@/node-win/addon-wrapper'; import { loggerFn, loggerMock } from '@/tests/vitest/mocks.helper.test'; -import { call, calls, deepMocked, partialSpyOn, TestProps } from '@/tests/vitest/utils.helper.test'; +import { call, calls, partialSpyOn, TestProps } from '@/tests/vitest/utils.helper.test'; import { deleteItemPlaceholder } from './delete-item-placeholder'; vi.mock(import('node:fs/promises')); vi.mock(import('node:crypto')); -vi.mock(import('trash')); describe('delete-item-placeholder', () => { - const mkdirMock = deepMocked(mkdir); - const renameMock = deepMocked(rename); - const rmMock = deepMocked(rm); - const trashMock = deepMocked(trash); - const randomUUIDMock = deepMocked(randomUUID); + const mkdirMock = vi.mocked(mkdir); + const renameMock = vi.mocked(rename); + const rmMock = vi.mocked(rm); + const randomUUIDMock = vi.mocked(randomUUID); const getFirstNonPlaceholderMock = partialSpyOn(Addon, 'getFirstNonPlaceholder'); const uuid = 'uuid' as FileUuid; @@ -74,7 +71,7 @@ describe('delete-item-placeholder', () => { { msg: 'Delete placeholder' }, { msg: 'Folder cannot be deleted because it contains a non placeholder item' }, ]); - calls(trashMock).toHaveLength(0); + calls(rmMock).toHaveLength(0); }); it('should delete folder if all folder items are placeholders', async () => { @@ -86,6 +83,6 @@ describe('delete-item-placeholder', () => { calls(loggerFn).toMatchObject([{ msg: 'Delete placeholder' }, { msg: 'Folder can be deleted, all items are placeholders' }]); call(mkdirMock).toStrictEqual([trashDir, { recursive: true }]); call(renameMock).toStrictEqual([localPath, trashPath]); - call(trashMock).toStrictEqual(trashPath); + call(rmMock).toStrictEqual([trashPath, { recursive: true, force: true }]); }); }); diff --git a/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.ts b/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.ts index e0dfdf8c0..d80688222 100644 --- a/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.ts +++ b/src/backend/features/remote-sync/file-explorer/delete-item-placeholder.ts @@ -5,6 +5,7 @@ import { join, parse } from 'node:path'; import { ExtendedDriveFile } from '@/apps/main/database/entities/DriveFile'; import { ExtendedDriveFolder } from '@/apps/main/database/entities/DriveFolder'; import { SyncContext } from '@/apps/sync-engine/config'; +import { measurePerfomance } from '@/core/utils/measure-performance'; import { Addon } from '@/node-win/addon-wrapper'; import { FileExplorerFiles, FileExplorerFolders } from '../sync-items-by-checkpoint/load-in-memory-paths'; @@ -41,13 +42,17 @@ export async function deleteItemPlaceholder({ ctx, type, remote, locals }: Props return; } - const nonPlaceholderItem = await Addon.getFirstNonPlaceholder({ parentPath: local.path }); + let nonPlaceholderItem: string | undefined; + + const time = await measurePerfomance(async () => { + nonPlaceholderItem = await Addon.getFirstNonPlaceholder({ parentPath: local.path }); + }); if (nonPlaceholderItem) { - ctx.logger.debug({ msg: 'Folder cannot be deleted because it contains a non placeholder item', nonPlaceholderItem }); + ctx.logger.debug({ msg: 'Folder cannot be deleted because it contains a non placeholder item', time, nonPlaceholderItem }); return; } else { - ctx.logger.debug({ msg: 'Folder can be deleted, all items are placeholders' }); + ctx.logger.debug({ msg: 'Folder can be deleted, all items are placeholders', time }); } /** @@ -65,9 +70,7 @@ export async function deleteItemPlaceholder({ ctx, type, remote, locals }: Props const trashPath = join(trashDir, randomUUID()); // Move operations are not allowed across different volumes. await rename(local.path, trashPath); - - const { default: trash } = await import('trash'); - await trash(trashPath); + await rm(trashPath, { recursive: true, force: true }); } catch (error) { ctx.logger.error({ msg: 'Error deleting placeholder', path: remote.absolutePath, type, error }); } diff --git a/src/infra/sqlite/services/file.module.ts b/src/infra/sqlite/services/file.module.ts index 7b247f734..30cce61f4 100644 --- a/src/infra/sqlite/services/file.module.ts +++ b/src/infra/sqlite/services/file.module.ts @@ -2,6 +2,7 @@ import { createOrUpdate } from './file/create-or-update'; import { createOrUpdateBatch } from './file/create-or-update-batch'; import { getBetweenUuids } from './file/get-between-uuids'; import { getByName } from './file/get-by-name'; +import { getByParentUuid } from './file/get-by-parent-uuid'; import { getByUuid } from './file/get-by-uuid'; import { getByWorkspaceId } from './file/get-by-workspace-id'; import { updateByUuid } from './file/update-by-uuid'; @@ -10,6 +11,7 @@ export const FileModule = { getByName, getByUuid, getBetweenUuids, + getByParentUuid, getByWorkspaceId, createOrUpdate, createOrUpdateBatch, diff --git a/src/infra/sqlite/services/file/create-or-update-batch.test.ts b/src/infra/sqlite/services/file/create-or-update-batch.test.ts index 38f7ca2df..fe882a2f0 100644 --- a/src/infra/sqlite/services/file/create-or-update-batch.test.ts +++ b/src/infra/sqlite/services/file/create-or-update-batch.test.ts @@ -38,19 +38,19 @@ describe('create-or-update-batch', () => { }; }); - it('should ignore if no files', () => { + it('should ignore if no files', async () => { // Given props.files = []; // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_file').get() }).toStrictEqual({ 'COUNT(*)': 0 }); }); - it('should insert new files', () => { + it('should insert new files', async () => { // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_file').get() }).toStrictEqual({ 'COUNT(*)': 450 }); @@ -72,12 +72,12 @@ describe('create-or-update-batch', () => { }); }); - it('should update existing files', () => { + it('should update existing files', async () => { // Given props.files[1].uuid = 'uuid0'; props.files[1].plainName = 'file'; // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_file').get() }).toStrictEqual({ 'COUNT(*)': 449 }); @@ -85,11 +85,11 @@ describe('create-or-update-batch', () => { expect({ ...db.prepare('SELECT COUNT(*) FROM drive_file WHERE uuid = ?').get('uuid1') }).toStrictEqual({ 'COUNT(*)': 0 }); }); - it('should return UNKNOWN when error is thrown', () => { + it('should return UNKNOWN when error is thrown', async () => { // Given props.files.push({} as any); // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error?.code).toBe('UNKNOWN'); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_file').get() }).toStrictEqual({ 'COUNT(*)': 400 }); diff --git a/src/infra/sqlite/services/file/create-or-update-batch.ts b/src/infra/sqlite/services/file/create-or-update-batch.ts index 5912dd0da..31c57d9a9 100644 --- a/src/infra/sqlite/services/file/create-or-update-batch.ts +++ b/src/infra/sqlite/services/file/create-or-update-batch.ts @@ -6,11 +6,7 @@ import { upsertQuery } from './queries'; const BATCH_SIZE = 100; -type Props = { - files: DriveFile[]; -}; - -export function createOrUpdateBatch({ files }: Props) { +export async function createOrUpdateBatch({ files }: { files: DriveFile[] }) { if (files.length === 0) return; try { @@ -39,15 +35,12 @@ export function createOrUpdateBatch({ files }: Props) { }); } db.exec('COMMIT'); + + await new Promise((resolve) => setImmediate(resolve)); } } catch (error) { db.exec('ROLLBACK'); - logger.error({ - msg: 'Error batch creating or updating files', - count: files.length, - error, - }); - + logger.error({ msg: 'Error batch creating or updating files', count: files.length, error }); return new SqliteError('UNKNOWN', error); } } diff --git a/src/infra/sqlite/services/file/get-by-parent-uuid.test.ts b/src/infra/sqlite/services/file/get-by-parent-uuid.test.ts new file mode 100644 index 000000000..4e7fbecfa --- /dev/null +++ b/src/infra/sqlite/services/file/get-by-parent-uuid.test.ts @@ -0,0 +1,79 @@ +import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; +import { loggerFn } from '@/tests/vitest/mocks.helper.test'; +import { call, TestProps } from '@/tests/vitest/utils.helper.test'; +import { db, runMigrations } from '../../migrations/run-migrations'; +import { DriveFile } from '../../schema'; +import { getByParentUuid } from './get-by-parent-uuid'; +import { upsertQuery } from './queries'; + +describe('get-by-parent-uuid', () => { + const date = new Date().toISOString(); + const file: DriveFile = { + id: 1, + uuid: 'uuid1', + status: 'EXISTS', + fileId: 'fileId', + size: 1024, + folderId: 1, + folderUuid: 'parentUuid', + userUuid: 'userUuid', + workspaceId: 'workspaceId', + createdAt: date, + updatedAt: date, + modificationTime: date, + plainName: 'file', + type: '', + }; + + const props: TestProps = { + parentUuid: 'parentUuid' as FolderUuid, + }; + + beforeAll(() => { + runMigrations(); + }); + + afterAll(() => { + db.close(); + }); + + beforeEach(() => { + db.exec('DELETE FROM drive_file'); + }); + + it('should return empty array when no files exist', () => { + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toStrictEqual([]); + }); + + it('should return all files for a parentUuid', () => { + // Given + db.prepare(upsertQuery).run(file); + db.prepare(upsertQuery).run({ ...file, uuid: 'uuid2', id: 2 }); + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toHaveLength(2); + }); + + it('should not return files from a different parentUuid', () => { + // Given + db.prepare(upsertQuery).run({ ...file, folderUuid: 'parentUuid2' }); + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toStrictEqual([]); + }); + + it('should return UNKNOWN when error is thrown', () => { + // Given + props.parentUuid = (() => null) as any; + // When + const { error } = getByParentUuid(props as any); + // Then + expect(error?.code).toBe('UNKNOWN'); + call(loggerFn).toMatchObject({ error: { message: expect.stringContaining('cannot be bound') } }); + }); +}); diff --git a/src/infra/sqlite/services/file/get-by-parent-uuid.ts b/src/infra/sqlite/services/file/get-by-parent-uuid.ts new file mode 100644 index 000000000..08038fdc3 --- /dev/null +++ b/src/infra/sqlite/services/file/get-by-parent-uuid.ts @@ -0,0 +1,18 @@ +import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; +import { logger } from '@/apps/shared/logger/logger'; +import { db } from '../../migrations/run-migrations'; +import { DriveFile } from '../../schema'; +import { SqliteError } from '../common/sqlite-error'; +import { parseData } from './parse-data'; + +export function getByParentUuid({ parentUuid }: { parentUuid: FolderUuid }) { + try { + const items = db.prepare(`SELECT * FROM drive_file WHERE folderUuid = :parentUuid AND status = 'EXISTS'`).all({ parentUuid }); + + return { data: items.map((item) => parseData({ data: item as DriveFile })) }; + } catch (error) { + logger.error({ msg: 'Error getting files by parentUuid', parentUuid, error }); + + return { error: new SqliteError('UNKNOWN', error) }; + } +} diff --git a/src/infra/sqlite/services/file/get-by-workspace-id.ts b/src/infra/sqlite/services/file/get-by-workspace-id.ts index 361cd91e6..de02a816b 100644 --- a/src/infra/sqlite/services/file/get-by-workspace-id.ts +++ b/src/infra/sqlite/services/file/get-by-workspace-id.ts @@ -18,7 +18,7 @@ export function getByWorkspaceId({ userUuid, workspaceId }: Props) { return { data: items.map((item) => parseData({ data: item as DriveFile })) }; } catch (exc) { logger.error({ - msg: 'Error getting files by workspace id', + msg: 'Error getting files by workspaceId', workspaceId, exc, }); diff --git a/src/infra/sqlite/services/folder.module.ts b/src/infra/sqlite/services/folder.module.ts index 8c14d0ee0..d50e520ba 100644 --- a/src/infra/sqlite/services/folder.module.ts +++ b/src/infra/sqlite/services/folder.module.ts @@ -2,6 +2,7 @@ import { createOrUpdate } from './folder/create-or-update'; import { createOrUpdateBatch } from './folder/create-or-update-batch'; import { getBetweenUuids } from './folder/get-between-uuids'; import { getByName } from './folder/get-by-name'; +import { getByParentUuid } from './folder/get-by-parent-uuid'; import { getByUuid } from './folder/get-by-uuid'; import { getByWorkspaceId } from './folder/get-by-workspace-id'; import { updateByUuid } from './folder/update-by-uuid'; @@ -10,6 +11,7 @@ export const FolderModule = { getByName, getByUuid, getBetweenUuids, + getByParentUuid, getByWorkspaceId, createOrUpdate, createOrUpdateBatch, diff --git a/src/infra/sqlite/services/folder/create-or-update-batch.test.ts b/src/infra/sqlite/services/folder/create-or-update-batch.test.ts index cbdde05d6..a2a987a0e 100644 --- a/src/infra/sqlite/services/folder/create-or-update-batch.test.ts +++ b/src/infra/sqlite/services/folder/create-or-update-batch.test.ts @@ -34,19 +34,19 @@ describe('create-or-update-batch', () => { }; }); - it('should ignore if no folders', () => { + it('should ignore if no folders', async () => { // Given props.folders = []; // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_folder').get() }).toStrictEqual({ 'COUNT(*)': 0 }); }); - it('should insert new folders', () => { + it('should insert new folders', async () => { // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_folder').get() }).toStrictEqual({ 'COUNT(*)': 450 }); @@ -64,23 +64,23 @@ describe('create-or-update-batch', () => { }); }); - it('should update existing folders', () => { + it('should update existing folders', async () => { // Given props.folders[1].uuid = 'uuid0'; props.folders[1].plainName = 'folder'; // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error).toBeUndefined(); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_folder').get() }).toStrictEqual({ 'COUNT(*)': 449 }); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_folder WHERE uuid = ?').get('uuid1') }).toStrictEqual({ 'COUNT(*)': 0 }); }); - it('should return UNKNOWN when error is thrown', () => { + it('should return UNKNOWN when error is thrown', async () => { // Given props.folders.push({} as any); // When - const error = createOrUpdateBatch(props); + const error = await createOrUpdateBatch(props); // Then expect(error?.code).toBe('UNKNOWN'); expect({ ...db.prepare('SELECT COUNT(*) FROM drive_folder').get() }).toStrictEqual({ 'COUNT(*)': 400 }); diff --git a/src/infra/sqlite/services/folder/create-or-update-batch.ts b/src/infra/sqlite/services/folder/create-or-update-batch.ts index bb0ba9d3a..e7af8725e 100644 --- a/src/infra/sqlite/services/folder/create-or-update-batch.ts +++ b/src/infra/sqlite/services/folder/create-or-update-batch.ts @@ -6,11 +6,7 @@ import { upsertQuery } from './queries'; const BATCH_SIZE = 100; -type Props = { - folders: DriveFolder[]; -}; - -export function createOrUpdateBatch({ folders }: Props) { +export async function createOrUpdateBatch({ folders }: { folders: DriveFolder[] }) { if (folders.length === 0) return; try { @@ -35,15 +31,12 @@ export function createOrUpdateBatch({ folders }: Props) { }); } db.exec('COMMIT'); + + await new Promise((resolve) => setImmediate(resolve)); } } catch (error) { db.exec('ROLLBACK'); - logger.error({ - msg: 'Error batch creating or updating folders', - count: folders.length, - error, - }); - + logger.error({ msg: 'Error batch creating or updating folders', count: folders.length, error }); return new SqliteError('UNKNOWN', error); } } diff --git a/src/infra/sqlite/services/folder/get-by-parent-uuid.test.ts b/src/infra/sqlite/services/folder/get-by-parent-uuid.test.ts new file mode 100644 index 000000000..9745c298f --- /dev/null +++ b/src/infra/sqlite/services/folder/get-by-parent-uuid.test.ts @@ -0,0 +1,75 @@ +import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; +import { loggerFn } from '@/tests/vitest/mocks.helper.test'; +import { call, TestProps } from '@/tests/vitest/utils.helper.test'; +import { db, runMigrations } from '../../migrations/run-migrations'; +import { DriveFolder } from '../../schema'; +import { getByParentUuid } from './get-by-parent-uuid'; +import { upsertQuery } from './queries'; + +describe('get-by-parent-uuid', () => { + const date = new Date().toISOString(); + const folder: DriveFolder = { + uuid: 'uuid1', + id: 1, + status: 'EXISTS', + plainName: 'folder', + parentUuid: 'parentUuid', + parentId: 0, + userUuid: 'userUuid', + workspaceId: 'workspaceId', + createdAt: date, + updatedAt: date, + }; + + const props: TestProps = { + parentUuid: 'parentUuid' as FolderUuid, + }; + + beforeAll(() => { + runMigrations(); + }); + + afterAll(() => { + db.close(); + }); + + beforeEach(() => { + db.exec('DELETE FROM drive_folder'); + }); + + it('should return empty array when no folders exist', () => { + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toStrictEqual([]); + }); + + it('should return all folders for a parentUuid', () => { + // Given + db.prepare(upsertQuery).run(folder); + db.prepare(upsertQuery).run({ ...folder, uuid: 'uuid2', id: 2 }); + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toHaveLength(2); + }); + + it('should not return folders from a different parentUuid', () => { + // Given + db.prepare(upsertQuery).run({ ...folder, parentUuid: 'parentUuid2' }); + // When + const { data } = getByParentUuid(props as any); + // Then + expect(data).toStrictEqual([]); + }); + + it('should return UNKNOWN when error is thrown', () => { + // Given + props.parentUuid = (() => null) as any; + // When + const { error } = getByParentUuid(props as any); + // Then + expect(error?.code).toBe('UNKNOWN'); + call(loggerFn).toMatchObject({ error: { message: expect.stringContaining('cannot be bound') } }); + }); +}); diff --git a/src/infra/sqlite/services/folder/get-by-parent-uuid.ts b/src/infra/sqlite/services/folder/get-by-parent-uuid.ts new file mode 100644 index 000000000..ba752724a --- /dev/null +++ b/src/infra/sqlite/services/folder/get-by-parent-uuid.ts @@ -0,0 +1,18 @@ +import { FolderUuid } from '@/apps/main/database/entities/DriveFolder'; +import { logger } from '@/apps/shared/logger/logger'; +import { db } from '../../migrations/run-migrations'; +import { DriveFolder } from '../../schema'; +import { SqliteError } from '../common/sqlite-error'; +import { parseData } from './parse-data'; + +export function getByParentUuid({ parentUuid }: { parentUuid: FolderUuid }) { + try { + const items = db.prepare(`SELECT * FROM drive_folder WHERE parentUuid = :parentUuid AND status = 'EXISTS'`).all({ parentUuid }); + + return { data: items.map((item) => parseData({ data: item as DriveFolder })) }; + } catch (error) { + logger.error({ msg: 'Error getting folders by parentUuid', parentUuid, error }); + + return { error: new SqliteError('UNKNOWN', error) }; + } +} diff --git a/src/infra/sqlite/services/folder/get-by-workspace-id.ts b/src/infra/sqlite/services/folder/get-by-workspace-id.ts index 224f824c8..254755e2d 100644 --- a/src/infra/sqlite/services/folder/get-by-workspace-id.ts +++ b/src/infra/sqlite/services/folder/get-by-workspace-id.ts @@ -18,7 +18,7 @@ export function getByWorkspaceId({ userUuid, workspaceId }: Props) { return { data: items.map((item) => parseData({ data: item as DriveFolder })) }; } catch (exc) { logger.error({ - msg: 'Error getting folders by workspace id', + msg: 'Error getting folders by workspaceId', workspaceId, exc, }); diff --git a/src/node-win/watcher/tests/watcher-on-unlink.test.ts b/src/node-win/watcher/tests/watcher-on-unlink.test.ts index 652889762..e757c2623 100644 --- a/src/node-win/watcher/tests/watcher-on-unlink.test.ts +++ b/src/node-win/watcher/tests/watcher-on-unlink.test.ts @@ -2,7 +2,6 @@ import { AbsolutePath } from '@internxt/drive-desktop-core/build/backend'; import { randomUUID } from 'node:crypto'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { TEST_FILES } from 'tests/vitest/mocks.helper.test'; -import trash from 'trash'; import { sleep } from '@/apps/main/util'; import * as onUnlink from '@/backend/features/local-sync/watcher/events/unlink/on-unlink'; import { join } from '@/context/local/localFile/infrastructure/AbsolutePath'; @@ -45,7 +44,7 @@ describe('watcher-on-unlink', () => { call(onUnlinkMock).toMatchObject({ path: folder, type: 'folder' }); }); - it('should emit delete event when delete folder with file inside using terminal', async () => { + it('should emit delete event when delete folder with file inside', async () => { // Given const parent = join(rootPath, 'parent'); const file = join(parent, 'file'); @@ -63,7 +62,7 @@ describe('watcher-on-unlink', () => { call(onUnlinkMock).toMatchObject({ path: parent, type: 'folder' }); }); - it('should emit delete event when delete folder with folder inside using terminal', async () => { + it('should emit delete event when delete folder with folder inside', async () => { // Given const parent = join(rootPath, 'parent'); const folder = join(parent, 'folder'); @@ -80,21 +79,4 @@ describe('watcher-on-unlink', () => { ]); call(onUnlinkMock).toMatchObject({ path: parent, type: 'folder' }); }); - - it('should emit delete event when delete folder with items inside using trash', async () => { - // Given - const parent = join(rootPath, 'parent'); - const folder = join(parent, 'folder'); - const file = join(parent, 'file'); - await mkdir(parent); - await mkdir(folder); - await writeFile(file, 'content'); - await setupWatcher(rootPath); - // When - await trash(parent); - await sleep(100); - // Then - call(onEventSpy).toMatchObject({ event: { action: 'delete', type: 'folder', size: 0 } }); - call(onUnlinkMock).toMatchObject({ path: parent, type: 'folder' }); - }); });