diff --git a/package.json b/package.json index 45cf24b..4781107 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,17 @@ "dependencies": { "@sveltejs/kit": "^2.5.4", "@tauri-apps/api": "^1", - "adm-zip": "^0.5.12", "bits-ui": "^0.20.0", "bottleneck": "^2.19.5", "clsx": "^2.1.0", - "jszip": "^3.10.1", "lucide-svelte": "^0.359.0", "mode-watcher": "^0.3.0", - "node": "^21.7.1", "svelte-radix": "^1.1.0", + "svelte-sonner": "^0.3.19", "tailwind-merge": "^2.2.2", "tailwind-variants": "^0.2.1", - "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1" + "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1", + "tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1" }, "devDependencies": { "@sveltejs/adapter-auto": "^3.1.1", @@ -32,7 +31,6 @@ "@sveltejs/vite-plugin-svelte": "^3.0.2", "@tauri-apps/cli": "^1", "@tsconfig/svelte": "^5.0.2", - "@types/node": "^20.11.30", "autoprefixer": "^10.4.18", "postcss": "^8.4.36", "svelte": "^4.2.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd418fa..dc9529f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ dependencies: '@tauri-apps/api': specifier: ^1 version: 1.5.3 - adm-zip: - specifier: ^0.5.12 - version: 0.5.12 bits-ui: specifier: ^0.20.0 version: 0.20.0(svelte@4.2.12) @@ -23,21 +20,18 @@ dependencies: clsx: specifier: ^2.1.0 version: 2.1.0 - jszip: - specifier: ^3.10.1 - version: 3.10.1 lucide-svelte: specifier: ^0.359.0 version: 0.359.0(svelte@4.2.12) mode-watcher: specifier: ^0.3.0 version: 0.3.0(svelte@4.2.12) - node: - specifier: ^21.7.1 - version: 21.7.1 svelte-radix: specifier: ^1.1.0 version: 1.1.0(svelte@4.2.12) + svelte-sonner: + specifier: ^0.3.19 + version: 0.3.19(svelte@4.2.12) tailwind-merge: specifier: ^2.2.2 version: 2.2.2 @@ -47,6 +41,9 @@ dependencies: tauri-plugin-store-api: specifier: github:tauri-apps/tauri-plugin-store#v1 version: github.com/tauri-apps/tauri-plugin-store/02243686d0507d2aeeb2924cd889dd0bcb47ecef + tauri-plugin-upload-api: + specifier: github:tauri-apps/tauri-plugin-upload#v1 + version: github.com/tauri-apps/tauri-plugin-upload/56d958697109d854bca19050403b017c9a28b434 devDependencies: '@sveltejs/adapter-auto': @@ -64,9 +61,6 @@ devDependencies: '@tsconfig/svelte': specifier: ^5.0.2 version: 5.0.2 - '@types/node': - specifier: ^20.11.30 - version: 20.11.30 autoprefixer: specifier: ^10.4.18 version: 10.4.18(postcss@8.4.36) @@ -90,7 +84,7 @@ devDependencies: version: 5.4.2 vite: specifier: ^5.0.0 - version: 5.1.6(@types/node@20.11.30) + version: 5.1.6 packages: @@ -528,7 +522,7 @@ packages: sirv: 2.0.4 svelte: 4.2.12 tiny-glob: 0.2.9 - vite: 5.1.6(@types/node@20.11.30) + vite: 5.1.6 /@sveltejs/vite-plugin-svelte-inspector@2.0.0(@sveltejs/vite-plugin-svelte@3.0.2)(svelte@4.2.12)(vite@5.1.6): resolution: {integrity: sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==} @@ -541,7 +535,7 @@ packages: '@sveltejs/vite-plugin-svelte': 3.0.2(svelte@4.2.12)(vite@5.1.6) debug: 4.3.4 svelte: 4.2.12 - vite: 5.1.6(@types/node@20.11.30) + vite: 5.1.6 transitivePeerDependencies: - supports-color @@ -559,7 +553,7 @@ packages: magic-string: 0.30.8 svelte: 4.2.12 svelte-hmr: 0.15.3(svelte@4.2.12) - vite: 5.1.6(@types/node@20.11.30) + vite: 5.1.6 vitefu: 0.2.5(vite@5.1.6) transitivePeerDependencies: - supports-color @@ -692,11 +686,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - /@types/node@20.11.30: - resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} - dependencies: - undici-types: 5.26.5 - /@types/pug@2.0.10: resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} dev: true @@ -706,11 +695,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - /adm-zip@0.5.12: - resolution: {integrity: sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==} - engines: {node: '>=6.0'} - dev: false - /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -885,10 +869,6 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: false - /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1104,10 +1084,6 @@ packages: dependencies: function-bind: 1.1.2 - /immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - dev: false - /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1128,6 +1104,7 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} @@ -1163,10 +1140,6 @@ packages: dependencies: '@types/estree': 1.0.5 - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: false - /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1182,25 +1155,10 @@ packages: resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true - /jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - dev: false - /kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - /lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - dependencies: - immediate: 3.0.6 - dev: false - /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -1316,23 +1274,10 @@ packages: hasBin: true dev: false - /node-bin-setup@1.1.3: - resolution: {integrity: sha512-opgw9iSCAzT2+6wJOETCpeRYAQxSopqQ2z+N6BXwIMsQQ7Zj5M8MaafQY8JMlolRR6R1UXg2WmhKp0p9lSOivg==} - dev: false - /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true - /node@21.7.1: - resolution: {integrity: sha512-j6NFfbKy3DBuaJh2X8lRZmS59oLr2XRtt0rTa9VsFj3ZcGbeK7xp40gZaI92AqNuRv1aL5dRueNqL6YuZFcjJg==} - engines: {npm: '>=5.0.0'} - hasBin: true - requiresBuild: true - dependencies: - node-bin-setup: 1.1.3 - dev: false - /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1356,10 +1301,6 @@ packages: wrappy: 1.0.2 dev: true - /pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - dev: false - /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1471,10 +1412,6 @@ packages: picocolors: 1.0.0 source-map-js: 1.1.0 - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: false - /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1483,18 +1420,6 @@ packages: dependencies: pify: 2.3.0 - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - dev: false - /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -1562,10 +1487,6 @@ packages: dependencies: mri: 1.2.0 - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: false - /sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} dependencies: @@ -1578,10 +1499,6 @@ packages: /set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} - /setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - dev: false - /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1638,12 +1555,6 @@ packages: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - dependencies: - safe-buffer: 5.1.2 - dev: false - /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1771,6 +1682,14 @@ packages: svelte: 4.2.12 dev: false + /svelte-sonner@0.3.19(svelte@4.2.12): + resolution: {integrity: sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==} + peerDependencies: + svelte: '>=3 <5' + dependencies: + svelte: 4.2.12 + dev: false + /svelte@4.2.12: resolution: {integrity: sha512-d8+wsh5TfPwqVzbm4/HCXC783/KPHV60NvwitJnyTA5lWn1elhXMNWhXGCJ7PwPa8qFUnyJNIyuIRt2mT0WMug==} engines: {node: '>=16'} @@ -1879,9 +1798,6 @@ packages: hasBin: true dev: true - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -1896,7 +1812,7 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /vite@5.1.6(@types/node@20.11.30): + /vite@5.1.6: resolution: {integrity: sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1924,7 +1840,6 @@ packages: terser: optional: true dependencies: - '@types/node': 20.11.30 esbuild: 0.19.12 postcss: 8.4.36 rollup: 4.13.0 @@ -1939,7 +1854,7 @@ packages: vite: optional: true dependencies: - vite: 5.1.6(@types/node@20.11.30) + vite: 5.1.6 /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} @@ -1980,3 +1895,11 @@ packages: dependencies: '@tauri-apps/api': 1.5.3 dev: false + + github.com/tauri-apps/tauri-plugin-upload/56d958697109d854bca19050403b017c9a28b434: + resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-upload/tar.gz/56d958697109d854bca19050403b017c9a28b434} + name: tauri-plugin-upload-api + version: 0.0.0 + dependencies: + '@tauri-apps/api': 1.5.3 + dev: false diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 34ad953..9019e3e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -119,6 +130,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -201,6 +218,27 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cairo-rs" version = "0.15.12" @@ -240,6 +278,10 @@ name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +dependencies = [ + "jobserver", + "libc", +] [[package]] name = "cesu8" @@ -296,6 +338,16 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -342,6 +394,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "convert_case" version = "0.4.0" @@ -553,6 +611,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -614,7 +673,7 @@ dependencies = [ "rustc_version", "toml 0.8.11", "vswhom", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -735,6 +794,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -742,6 +816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -778,6 +853,12 @@ dependencies = [ "syn 2.0.52", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" @@ -790,9 +871,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1104,6 +1189,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "h2" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.2.5", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1143,6 +1247,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.26.0" @@ -1168,12 +1281,72 @@ dependencies = [ "itoa 1.0.10", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.10", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1282,6 +1455,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1291,6 +1473,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itoa" version = "0.4.8" @@ -1346,6 +1534,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1508,6 +1705,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1518,6 +1721,35 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.6.0" @@ -1667,6 +1899,50 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1721,6 +1997,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pathdiff" version = "0.2.1" @@ -1736,7 +2023,22 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-store", + "tauri-plugin-upload", "whoami", + "zip", + "zip-extensions", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", ] [[package]] @@ -2096,6 +2398,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "read-progress-stream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8" +dependencies = [ + "bytes", + "futures", + "pin-project-lite", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -2160,6 +2473,48 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2188,6 +2543,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2215,6 +2579,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2227,6 +2600,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -2308,6 +2704,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.10", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.7.0" @@ -2370,6 +2778,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2417,6 +2836,16 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "soup2" version = "0.2.1" @@ -2492,6 +2921,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -2514,6 +2949,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "5.0.0" @@ -2725,7 +3187,7 @@ dependencies = [ [[package]] name = "tauri-plugin-store" version = "0.0.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#42607f0dbd99bd609bdcacb4df05f8c5268f43cb" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#8f857dc4e1cb1f9f6509f62f2c0586b037510e00" dependencies = [ "log", "serde", @@ -2734,6 +3196,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-upload" +version = "0.0.0" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#8f857dc4e1cb1f9f6509f62f2c0586b037510e00" +dependencies = [ + "futures-util", + "log", + "read-progress-stream", + "reqwest", + "serde", + "serde_json", + "tauri", + "thiserror", + "tokio", + "tokio-util", +] + [[package]] name = "tauri-runtime" version = "0.14.2" @@ -2928,8 +3407,36 @@ checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", + "libc", + "mio", "num_cpus", "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", ] [[package]] @@ -3000,6 +3507,12 @@ dependencies = [ "winnow 0.6.5", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -3070,6 +3583,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.17.0" @@ -3136,6 +3655,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.0.11" @@ -3184,6 +3709,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3227,6 +3761,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -3256,6 +3802,19 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.69" @@ -3703,6 +4262,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -3782,3 +4351,61 @@ dependencies = [ "linux-raw-sys", "rustix", ] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zip-extensions" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecf62554c4ff96bce01a7ef123d160c3ffe9180638820f8b4d545c65b221b8c" +dependencies = [ + "zip", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1ddd56d..15f8604 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,7 +15,10 @@ tauri = { version = "1", features = [ "fs-all", "shell-open"] } serde = { version = "1", features = ["derive"] } serde_json = "1" whoami = "1.5.1" +zip = "0.6" +zip-extensions = "0.6" tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } +tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 621fcf3..4b3511b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::fs; +use std::fs::File; #[tauri::command] fn list_dir(path: String) -> Result, String> { @@ -33,14 +34,93 @@ fn read_file(file_path: String) -> Result { // If successful, return the file contents as a string Ok(contents) => Ok(contents), // If an error occurs, return the error - Err(error) => Err(format!("Error reading file: {} for path: {}", error, file_path)) + Err(error) => Err(format!( + "Error reading file: {} for path: {}", + error, file_path + )), + } +} + +#[tauri::command] +fn extract_zip(zip_path: String, extract_path: String) -> Result<(), String> { + let parsed_zip_path = zip_path.replace("%user%", &whoami::username()); + let parsed_extract_path = extract_path.replace("%user%", &whoami::username()); + + // Open the zip file + let zip_file = match File::open(&parsed_zip_path) { + Ok(file) => file, + Err(error) => { + return Err(format!( + "Error opening zip file: {} for path: {}", + error, parsed_zip_path + )) + } + }; + + // Create a zip archive from the file + let mut zip_archive = match zip::ZipArchive::new(zip_file) { + Ok(archive) => archive, + Err(error) => { + return Err(format!( + "Error creating zip archive: {} for path: {}", + error, parsed_zip_path + )) + } + }; + + // Extract the contents of the zip archive + match zip_archive.extract(&parsed_extract_path) { + Ok(_) => Ok(()), + Err(error) => Err(format!( + "Error extracting zip archive: {} for path: {}", + error, parsed_extract_path + )), + } +} + +#[tauri::command] +fn write_text_file(file_path: String, contents: String) -> Result<(), String> { + // Attempt to write the file + let parsed_path = file_path.replace("%user%", &whoami::username()); + + match fs::write(&parsed_path, contents) { + // If successful, return nothing + Ok(_) => Ok(()), + // If an error occurs, return the error + Err(error) => Err(format!( + "Error writing file: {} for path: {}", + error, file_path + )), + } +} + +#[tauri::command] +fn remove_dir(path: String) -> Result<(), String> { + // Attempt to remove the directory + let parsed_path = path.replace("%user%", &whoami::username()); + + match fs::remove_dir_all(&parsed_path) { + // If successful, return nothing + Ok(_) => Ok(()), + // If an error occurs, return the error + Err(error) => Err(format!( + "Error removing directory: {} for path: {}", + error, path + )), } } fn main() { tauri::Builder::default() .plugin(tauri_plugin_store::Builder::default().build()) - .invoke_handler(tauri::generate_handler![list_dir, read_file]) + .plugin(tauri_plugin_upload::init()) + .invoke_handler(tauri::generate_handler![ + list_dir, + read_file, + extract_zip, + write_text_file, + remove_dir + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c1efed5..d502d33 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -6,7 +6,7 @@ "distDir": "../build" }, "package": { - "productName": "pavlov-map-manager", + "productName": "Pavlov Map Manager", "version": "0.0.0" }, "tauri": { diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..7d5b2f1 --- /dev/null +++ b/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/modio-utils.ts b/src/lib/modio-utils.ts new file mode 100644 index 0000000..a85b30b --- /dev/null +++ b/src/lib/modio-utils.ts @@ -0,0 +1,59 @@ +export async function downloadMap(map: string) { + // Set downloading to true to show the progress bar + downloading = true; + status = "Fetching file info"; + + // Get the file info + const fileInfoResponse = await modioRequest( + `https://api.mod.io/v1/games/3959/mods/${map}/files/${mapData[map].latestVersion}`, + "GET" + ); + if (!fileInfoResponse.ok) { + toast.error( + "Error while fetching file info for map: " + mapData[map].title + ); + status = "Error fetching file info"; + downloading = false; + return; + } + + status = "Checking file info"; + + const fileInfo = await fileInfoResponse.json(); + + // Set the total byte size of the file + totalSize = fileInfo.filesize; + + // Check if the file is infected with a virus + if (fileInfo.virus_positive == 1) { + toast.error("Virus detected in map: " + mapData[map].title); + status = "Virus detected"; + downloading = false; + return; + } + + status = "Downloading the file"; + // Download the file + try { + const headers = new Map(); + headers.set("Authorization", `Bearer ${oauthToken}`); + headers.set("Content-Type", "application/x-www-form-urlencoded"); + headers.set("Accept", "application/json"); + headers.set("X-Modio-Platform", "windows"); + + await download( + fileInfo.download.binary_url, + `${map}.zip`, + (progress, total) => { + receivedSize = progress; + totalSize = total; + progress = (progress / total) * 100; + }, + headers + ); + } catch (error) { + toast.error("Error while downloading file"); + status = "Error downloading file"; + downloading = false; + return; + } \ No newline at end of file diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c1c66e7..6e122ad 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,8 +2,6 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; import { cubicOut } from "svelte/easing"; import type { TransitionConfig } from "svelte/transition"; -import type { Store } from "tauri-plugin-store-api"; -import { Variable } from "lucide-svelte"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); @@ -18,7 +16,7 @@ type FlyAndScaleParams = { export function humanFileSize(size) { var i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); - return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; + return (Number((size / Math.pow(1024, i)).toFixed(2)) * 1) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; } export async function setAvatarUrl(oauth_token: string) { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e810456..e0d44ed 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -11,6 +11,7 @@ } from "$components/ui/popover"; import { Avatar, AvatarFallback, AvatarImage } from "$components/ui/avatar"; import { setAvatarUrl } from "$lib/utils"; + import { Toaster } from "$components/ui/sonner"; const config = new Store(".config.dat"); @@ -84,5 +85,6 @@ + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a23bfe6..4cffb53 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -9,14 +9,33 @@ import { Progress } from "$components/ui/progress"; import { Tooltip, TooltipTrigger } from "$components/ui/tooltip"; import TooltipContent from "$components/ui/tooltip/tooltip-content.svelte"; - import { writeBinaryFile } from "@tauri-apps/api/fs"; + import { removeFile } from "@tauri-apps/api/fs"; import { humanFileSize } from "$lib/utils"; import { invoke } from "@tauri-apps/api/tauri"; - import { ArrowDownToLine, RefreshCcw, Star } from "lucide-svelte"; - import { onMount } from "svelte"; + import { open } from "@tauri-apps/api/shell"; + import { + ArrowDownToLine, + RefreshCcw, + Star, + Trash, + LoaderCircle, + } from "lucide-svelte"; import { Store } from "tauri-plugin-store-api"; import Bottleneck from "bottleneck"; - var AdmZip = require("adm-zip"); + import { download } from "tauri-plugin-upload-api"; + import { toast } from "svelte-sonner"; + import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, + } from "$components/ui/alert-dialog"; + import { get } from "svelte/store"; const config = new Store(".config.dat"); const limiter = new Bottleneck({ @@ -25,50 +44,85 @@ let maps: Array = []; let subscriptions: Array = []; - let map_data: { + let mapData: { [key: string]: { - id: string; title: string; - image_url: string; - new_update: boolean; - current_version: string; - latest_version: string; + imageUrl: string; + modUrl: string; + newUpdate: boolean; + currentVersion: string; + latestVersion: string; subscribed: boolean; + installedLocally: boolean; }; - } = {}; - let oauth_token: string; - let mods_path: string; - let all_subscribed: boolean; - let all_updated: boolean; + } = new Proxy( + {}, + { + get(target, prop, receiver) { + if (!(prop in target)) { + target[prop] = { + title: null, + imageUrl: null, + modUrl: null, + newUpdate: false, + currentVersion: null, + latestVersion: null, + subscribed: false, + installedLocally: true, + }; + } + return Reflect.get(target, prop, receiver); + }, + } + ); + + let oauthToken: string; + let modsPath: string; + let allSubscribed: boolean; + let allUpdated: boolean; + + let loading: boolean = true; + let status: string; let downloading: boolean = false; + let currentlyDownloading: string; let receivedSize: number = 0; let totalSize: number = 0; - let progress: number = 0; - - (async () => { - oauth_token = await config.get("oauth_token"); - mods_path = await config.get("mods_path"); - })(); - - async function get_maps() { + let downloadProgress: number = 0; + let queueProgress: number = 0; + let downloadStatus: string; + let queue: Array = []; + let initialQueueLength: number = 0; + let queueDownloaded: number = 0; + + async function getMaps() { const path = await config.get("mods_path"); try { maps = await invoke("list_dir", { path }); - return maps; + + maps = maps.filter((map: string) => map.startsWith("UGC")); + maps = maps.map((map: string) => map.split("UGC")[1]); + + for (const map of subscriptions) { + if (!maps.includes(map.toString())) { + maps.push(map); + } + } + + return; } catch (error) { - console.error("Error:", error); + toast.error("Error while reading maps from path: " + path); return []; } } - async function api_request( + async function modioRequest( url: string, method: string, headers: any = {}, body?: string ) { - headers.Authorization = "Bearer " + oauth_token; + headers.Authorization = "Bearer " + oauthToken; headers.Accept = "application/json"; const response = await limiter.schedule(() => @@ -78,90 +132,108 @@ body: body, }) ); - if (!response.ok) { - console.error("Error while fetching data"); - return; - } + return response; } - async function get_map_data(map: string) { - const response = await api_request( - `https://api.mod.io/v1/games/3959/mods/${map.split("UGC")[1]}`, - "GET" - ); - if (!response.ok) { - console.error("Error while fetching map data for map " + map); - return; - } - const data = await response.json(); + async function getMapData(map: string) { + if (!mapData.hasOwnProperty(map)) { + const response = await modioRequest( + `https://api.mod.io/v1/games/3959/mods/${map}`, + "GET" + ); + if (!response.ok) { + toast.error("Error while fetching map data for map: " + map); + + // Remove the map from the list if it doesn't exist on mod.io + maps = maps.filter((m) => m != map); + mapData = Object.fromEntries( + Object.entries(mapData).filter(([key]) => key != map) + ); + return; + } + const data = await response.json(); - const file_path = mods_path + "\\" + map + "\\taint"; - const current_version = await invoke("read_file", { - filePath: file_path, - }); - let latest_version: string; - let new_update: boolean; + mapData[map].title = data.name; + mapData[map].imageUrl = data.logo.thumb_1280x720; + mapData[map].modUrl = data.profile_url; - const platforms = data.platforms; - for (const platform of platforms) { - if (platform.platform == "windows") { - latest_version = platform.modfile_live; + const platforms = data.platforms; + for (const platform of platforms) { + if (platform.platform == "windows") { + mapData[map].latestVersion = platform.modfile_live; + } } } - if (latest_version != null) { - if (latest_version != current_version) { - new_update = true; + const filePath = `${modsPath}\\UGC${map}\\taint`; + let currentVersion: string; + try { + currentVersion = await invoke("read_file", { + filePath: filePath, + }); + } catch (error) { + mapData[map].installedLocally = false; + console.log("Map not installed locally: " + map); + } + mapData[map].currentVersion = currentVersion as string; + + if (subscriptions.includes(map)) { + mapData[map].subscribed = true; + } + + if (mapData[map].latestVersion != null) { + if (mapData[map].latestVersion != currentVersion) { + mapData[map].newUpdate = true; } else { - new_update = false; + mapData[map].newUpdate = false; } } else { - console.error("Error: No windows version found for map " + map); + maps = maps.filter((m) => m != map); + mapData = Object.fromEntries( + Object.entries(mapData).filter(([key]) => key != map) + ); } - - return { - id: map.split("UGC")[1], - title: data.name, - image_url: data.logo.thumb_1280x720, - new_update: new_update, - current_version: current_version as string, - latest_version: latest_version, - subscribed: false, - }; + return; } - async function get_subscriptions() { - const response = await api_request( - `https://api.mod.io/v1/me/subscribed`, + async function getSubscriptions() { + const response = await modioRequest( + `https://api.mod.io/v1/me/subscribed?game_id=3959`, "GET" ); if (!response.ok) { - console.error("Error while fetching subscribed maps"); + toast.error("Error while fetching subscribed maps"); return; } const data = await response.json(); for (const map of data.data) { - if (map.game_id == 3959) { - subscriptions.push(map.id); - } - } - return subscriptions; - } + subscriptions.push(map.id); + maps.push(map.id); - async function check_subscribed(map: string, subscriptions: Array) { - for (const subscribed of subscriptions) { - if (subscribed == map.split("UGC")[1]) { - return true; + mapData[map.id].title = map.name; + mapData[map.id].imageUrl = map.logo.thumb_1280x720; + mapData[map.id].modUrl = map.profile_url; + + const platforms = map.platforms; + for (const platform of platforms) { + if (platform.platform == "windows") { + mapData[map.id].latestVersion = platform.modfile_live; + } } } - return false; + return; } async function subscribe(map: string) { - const response = await api_request( + if (mapData[map].subscribed) { + toast.error("You are already subscribed to map: " + mapData[map].title); + return; + } + + const response = await modioRequest( `https://api.mod.io/v1/games/3959/mods/${map}/subscribe`, "POST", { @@ -170,13 +242,22 @@ "include_dependencies=false" ); if (!response.ok) { + toast.error("Error while subscribing to map: " + mapData[map].title); + mapData[map].subscribed = false; return; } - location.reload(); + + mapData[map].subscribed = true; + checkAll(); } async function unsubscribe(map: string) { - const response = await api_request( + if (!mapData[map].subscribed) { + toast.error("You are already unsubscribed to map: " + mapData[map].title); + return; + } + + const response = await modioRequest( `https://api.mod.io/v1/games/3959/mods/${map}/subscribe`, "DELETE", { @@ -184,255 +265,469 @@ } ); if (!response.ok) { + toast.error("Error while unsubscribing from map: " + mapData[map].title); + mapData[map].subscribed = true; return; } - location.reload(); + + mapData[map].subscribed = false; + checkAll(); } - async function download_map(map: string) { - downloading = true; + async function downloadMap(map: string) { + // Set downloading to true to show the progress bar + currentlyDownloading = mapData[map].title; + downloadStatus = "Fetching file info"; - try { - const fileInfoResponse = await api_request( - `https://api.mod.io/v1/games/3959/mods/${map}/files/${map_data["UGC" + map].latest_version}`, - "GET" + // Get the file info + const fileInfoResponse = await modioRequest( + `https://api.mod.io/v1/games/3959/mods/${map}/files/${mapData[map].latestVersion}`, + "GET" + ); + if (!fileInfoResponse.ok) { + toast.error( + "Error while fetching file info for map: " + mapData[map].title ); - const fileInfo = await fileInfoResponse.json(); + downloadStatus = "Error fetching file info"; + return; + } - totalSize = fileInfo.filesize; + downloadStatus = "Checking file info"; - if (fileInfo.virus_positive == 1) { - console.error("Error: Virus detected in map " + map); - return; - } - //`https://getsamplefiles.com/download/zip/sample-1.zip` - const fileResponse = await fetch( - `https://getsamplefiles.com/download/zip/sample-1.zip` - ); + const fileInfo = await fileInfoResponse.json(); - const fileReader = fileResponse.body.getReader(); + // Set the total byte size of the file + totalSize = fileInfo.filesize; - receivedSize = 0; // received that many bytes at the moment - let chunks = []; // array of received binary chunks (comprises the body) - while (true) { - const { done, value } = await fileReader.read(); + // Check if the file is infected with a virus + if (fileInfo.virus_positive == 1) { + toast.error("Virus detected in map: " + mapData[map].title); + downloadStatus = "Virus detected"; + return; + } - if (done) { - break; - } + downloadStatus = "Downloading"; + // Download the file + try { + const headers = new Map(); + headers.set("Authorization", `Bearer ${oauthToken}`); + headers.set("Content-Type", "application/x-www-form-urlencoded"); + headers.set("Accept", "application/json"); + headers.set("X-Modio-Platform", "windows"); + + await download( + fileInfo.download.binary_url, + `${map}.zip`, + (progress, total) => { + receivedSize = progress; + totalSize = total; + downloadProgress = (progress / total) * 100; + }, + headers + ); + } catch (error) { + toast.error("Error while downloading file"); + downloadStatus = "Error downloading file"; + return; + } - chunks.push(value); - receivedSize += value.length; + downloadStatus = "Extracting the file"; + // Extract the zip file and remove it + try { + await invoke("extract_zip", { + zipPath: `${map}.zip`, + extractPath: `${modsPath}\\UGC${map}\\Data`, + }); + await removeFile(`${map}.zip`); + } catch (error) { + toast.error("Error while extracting file"); + await removeFile(`${map}.zip`); + downloadStatus = "Error extracting file"; + return; + } - progress = (receivedSize / totalSize) * 100; - } + downloadStatus = "Saving the new version number"; + // Save the new version number to the taint file + await invoke("write_text_file", { + filePath: `${modsPath}\\UGC${map}\\taint`, + contents: mapData[map].latestVersion.toString(), + }); - let chunksAll = new Uint8Array(receivedSize); // (4.1) - let position = 0; - for (let chunk of chunks) { - chunksAll.set(chunk, position); // (4.2) - position += chunk.length; - } + downloadStatus = "Done"; + mapData[map].newUpdate = false; + return; + } - const zippedContent = AdmZip(chunksAll); + async function downloadQueue() { + downloading = true; + await downloadMap(queue[0]); + queue.shift(); - // Create an instance of AdmZip - const zip = new AdmZip(zippedContent); + if (queue.length > 0) { + queueDownloaded++; + queueProgress = (queueDownloaded / initialQueueLength) * 100; + downloadQueue(); + return; + } else { + queueProgress = 0; + queueDownloaded = 0; + initialQueueLength = 0; + checkAll(); + downloading = false; + return; + } + } - // Extract the contents of the zip file - const zipEntries = zip.getEntries(); + async function addDownloadQueue(maps: Array) { + for (const map of maps) { + if (mapData[map].newUpdate && !queue.includes(map)) { + queue.push(map); + } else { + } + } + if (initialQueueLength == 0) { + downloadQueue(); + } + initialQueueLength = initialQueueLength + maps.length; - zipEntries.forEach(async (entry) => { - // Check if entry is a file - if (!entry.isDirectory) { - // Extract file content - const fileContent = entry.getData(); + return; + } - // Write file content to disk - await writeBinaryFile({ - path: `${map}\\${entry.entryName}`, - contents: fileContent, - }); - console.log(`File ${entry.entryName} extracted and saved.`); - } - }); - } catch (error) { - console.error("Error downloading and saving file:", error); + async function deleteMod(map: string) { + if (mapData[map].subscribed) { + await unsubscribe(map); } - } - async function load() { - maps = await get_maps(); - subscriptions = await get_subscriptions(); - await Promise.all( - maps.map(async (map) => { - map_data[map] = await get_map_data(map); - map_data[map].subscribed = await check_subscribed(map, subscriptions); - }) + maps = maps.filter((m) => m != map); + mapData = Object.fromEntries( + Object.entries(mapData).filter(([key, value]) => key != map) ); + + checkAll(); + + await invoke("remove_dir", { + path: `${modsPath}\\UGC${map}`, + }); + + return; } - async function check_all() { - all_updated = true; - all_subscribed = true; + function checkAll() { + allUpdated = true; + allSubscribed = true; for (const map of maps) { - if (map_data[map].new_update) { - all_updated = false; + if (mapData[map].newUpdate) { + allUpdated = false; } } for (const map of maps) { - if (!map_data[map].subscribed) { - all_subscribed = false; + if (!mapData[map].subscribed) { + allSubscribed = false; + console.log("Map not subscribed: " + map); } } return; } - onMount(async () => { + async function load() { + status = "Finding maps"; + + try { + await getMaps(); + } catch (error) { + console.log("Error while reading maps"); + return; + } + + status = "Fetching subscriptions"; + await getSubscriptions(); + + let i: number = 0; + for (const map of maps) { + status = `Fetching map data: ${i}/${maps.length}`; + try { + await getMapData(map); + } catch (error) { + console.log("Error while getting map data for map: " + map); + i--; + } + i++; + } + } + + (async () => { + loading = true; + oauthToken = await config.get("oauth_token"); + modsPath = await config.get("mods_path"); + await load(); - await check_all(); - }); + checkAll(); + + status = "Done"; + loading = false; + })();
- {#if maps.length > 0} -
-
- + {#if oauthToken != ("" && null)} + {#if maps.length > 0} + {#if loading}
-
- {humanFileSize(receivedSize)}/{humanFileSize(totalSize)} -
-
- - - - - - {#if all_updated} - All maps are up to date - {:else} - Update all maps - {/if} - - - - - - - - {#if all_subscribed} - You are already subscribed to all maps - {:else} - Subscribe to all maps - {/if} - - - - - - - Refresh - -
-
-
-
-
-

New update available

+ + {status}
-
- {#each Object.entries(map_data) as [_, map]} - {#if map.new_update} - - - {map.title} - - - {map.title} -
- {#if map.new_update} - - {/if} - {#if map.subscribed} - + {:else} +
+
+

+ Downloading: {currentlyDownloading} +

+ +
+ {queueDownloaded}/{initialQueueLength} +
+ +
+
+ {humanFileSize(receivedSize)}/{humanFileSize(totalSize)} +
+
+ {downloadStatus} +
+
+ + + + + + {#if allUpdated} + All maps are up to date {:else} - + Update all maps {/if} -
- - - {/if} - {/each} -
-
-
-

Up to date

-
- {#each Object.entries(map_data) as [_, map]} - {#if map.new_update != true} - - - {map.title} - - - {map.title} -
- -
-
-
- {/if} - {/each} -
+ + + {#if allSubscribed} + You are already subscribed to all maps + {:else} + Subscribe to all maps + {/if} + + + + + + + Refresh + +
+
+
+
+

New update available

+
+ {#each Object.keys(mapData) as map} + {#if mapData[map].newUpdate} + + + {mapData[map].title} + + + +
+ {#if mapData[map].newUpdate} + + {/if} + {#if mapData[map].subscribed} + + {:else} + + {/if} + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will + permanently delete the map: {mapData[map] + .title}. You can get it back by downloading it + again. + + + + Cancel + deleteMod(map)} + >Confirm + + + +
+
+
+ {/if} + {/each} +
+
+
+

Up to date

+
+ {#each Object.keys(mapData) as map} + {#if mapData[map].newUpdate != true} + + + {mapData[map].title} + + + +
+ {#if mapData[map].subscribed} + + {:else} + + {/if} + + + + + + + Are you absolutely sure? + + This action cannot be undone. This will + permanently delete the map: {mapData[map] + .title}. You can get it back by downloading it + again. + + + + Cancel + deleteMod(map)} + >Confirm + + + +
+
+
+ {/if} + {/each} +
+
+
{/if} + {:else} +
+

No maps found

+
+

Is the path correct?

+

{modsPath}

-
- {:else} -
-

No maps found

-

Is the path correct?

-

{mods_path}

-
+ {/if} {/if}