@@ -24,6 +25,9 @@
diff --git a/pages/error/index.vue b/pages/error/index.vue
new file mode 100644
index 0000000..d50e70c
--- /dev/null
+++ b/pages/error/index.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+ Unrecoverable error
+
+
+ Drop encountered an error that it couldn't handle. Please restart the
+ application and file a bug report.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/error/serverunavailable.vue b/pages/error/serverunavailable.vue
new file mode 100644
index 0000000..cfbb178
--- /dev/null
+++ b/pages/error/serverunavailable.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ Server is unavailable
+
+
+ We were unable to contact your Drop instance. See if you can open it
+ in your web browser, or contact your server admin for help.
+
+
+
retry()"
+ class="inline-flex gap-x-2 items-center text-sm text-left font-semibold leading-7 text-white"
+ >
+ Retry
+
+
+ Connect to different instance →
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index.vue b/pages/index.vue
index cc340bc..52bc1df 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -1 +1,7 @@
-
+
+
+
diff --git a/pages/library.vue b/pages/library.vue
new file mode 100644
index 0000000..ab5079a
--- /dev/null
+++ b/pages/library.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+ {{ nav.label }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/library/[id]/index.vue b/pages/library/[id]/index.vue
new file mode 100644
index 0000000..539ec61
--- /dev/null
+++ b/pages/library/[id]/index.vue
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+ {{ game.mName }}
+
+
+
+
+
+
+
+
installFlow()"
+ @launch="() => launch()"
+ @queue="() => queue()"
+ @uninstall="() => uninstall()"
+ @kill="() => kill()"
+ :status="status"
+ />
+
+
+
+ Store
+
+
+
+
+
+
+
+
+
+
Install {{ game.mName }}?
+
+
+
+ Drop will add {{ game.mName }} to the queue to be downloaded.
+ While downloading, Drop may use up a large amount of resources,
+ particularly network bandwidth and CPU utilisation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ installError }}
+
+
+
+
+
+
+ install()"
+ :disabled="
+ !(versionOptions && versionOptions.length > 0 && !installDir)
+ "
+ :loading="installLoading"
+ type="submit"
+ class="ml-2 w-full sm:w-fit"
+ >
+ Install
+
+
+ Cancel
+
+
+
+
+
+
diff --git a/pages/library/index.vue b/pages/library/index.vue
new file mode 100644
index 0000000..dd245b6
--- /dev/null
+++ b/pages/library/index.vue
@@ -0,0 +1,9 @@
+
+
+
+ Library Failed to update
+
+
+
\ No newline at end of file
diff --git a/pages/queue.vue b/pages/queue.vue
new file mode 100644
index 0000000..8d46bea
--- /dev/null
+++ b/pages/queue.vue
@@ -0,0 +1,207 @@
+
+
+
+
+ {{ formatKilobytes(stats.speed) }}/s
+ {{ formatTime(stats.time) }} left
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ games[element.meta.id].game.mName }}
+
+
+
+ {{ games[element.meta.id].game.mShortDescription }}
+
+
+
+
+
+
+ {{ element.status }}
+
+
+
{{
+ formatKilobytes(element.current / 1000)
+ }}
+ /
+ {{ formatKilobytes(element.max / 1000) }}
+
+
cancelGame(element.meta)" class="group">
+
+
+
+
+ Loading...
+
+
+
+ No items in the queue
+
+
+
+
+
diff --git a/pages/quit.vue b/pages/quit.vue
new file mode 100644
index 0000000..808a5ec
--- /dev/null
+++ b/pages/quit.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/pages/settings.vue b/pages/settings.vue
new file mode 100644
index 0000000..3c39c65
--- /dev/null
+++ b/pages/settings.vue
@@ -0,0 +1,121 @@
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/settings/debug.vue b/pages/settings/debug.vue
new file mode 100644
index 0000000..7a549ca
--- /dev/null
+++ b/pages/settings/debug.vue
@@ -0,0 +1,136 @@
+
+
+
+
+ Debug Information
+
+
+ Technical information about your Drop client installation, helpful for
+ debugging.
+
+
+
+
+
+
+
+ Client ID
+
+
+
+ {{ clientId || "Not signed in" }}
+
+
+
+
+
+
+
+ Platform
+
+
+
+ {{ platformInfo }}
+
+
+
+
+
+
+
+ Server URL
+
+
+
+ {{ baseUrl || "Not connected" }}
+
+
+
+
+
+
+
+ Data Directory
+
+
+
+ {{ dataDir || "Unknown" }}
+
+
+
+
+ openDataDir()"
+ type="button"
+ class="inline-flex items-center gap-x-2 rounded-md bg-blue-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
+ >
+
+ Open Data Directory
+
+ openLogFile()"
+ type="button"
+ class="inline-flex items-center gap-x-2 rounded-md bg-blue-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
+ >
+
+ Open Log File
+
+
+
+
+
+
+
+
diff --git a/pages/settings/downloads.vue b/pages/settings/downloads.vue
new file mode 100644
index 0000000..c03fa78
--- /dev/null
+++ b/pages/settings/downloads.vue
@@ -0,0 +1,333 @@
+
+
+
+
+
+
+ Install directories
+
+
+ This is where Drop will download game files to, and store them
+ indefinitely while you play. Drop and games may store other
+ information elsewhere, like saves or mods.
+
+
+
+ (open = true)"
+ type="button"
+ class="relative inline-flex items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
+ >
+ Add new directory
+
+
+
+
+
+
+
+
+ deleteDirectory(dirIdx)"
+ :disabled="dirs.length <= 1"
+ :class="[
+ dirs.length <= 1
+ ? 'text-zinc-700'
+ : 'text-zinc-400 hover:text-zinc-100',
+ '-m-2.5 block p-2.5',
+ ]"
+ >
+ Open options
+
+
+
+
+
+
+
+ Download Settings
+
+
+ Configure how Drop downloads games and other content.
+
+
+
+
+ Maximum Download Threads
+
+
+
+
+
+ The maximum number of concurrent download threads. Higher values may
+ download faster but use more system resources. Default is 4.
+
+
+
+
+
+ {{ saveState.success ? 'Saved' : 'Save Changes' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select game directory
+
+ selectDirectory()"
+ class="block text-left w-full rounded-md border-0 px-3 py-1.5 text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 bg-zinc-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm/6"
+ >
+ {{
+ currentDirectory ?? "Click to select a directory..."
+ }}
+
+
+
+ Select an empty directory to add.
+
+
+
+
+
+ submitDirectory()"
+ :class="[
+ 'inline-flex w-full shadow-sm sm:ml-3 sm:w-auto',
+ currentDirectory === undefined
+ ? 'text-zinc-400 bg-blue-600/10 hover:bg-blue-600/10'
+ : 'text-white bg-blue-600 hover:bg-blue-500',
+ ]"
+ >
+ Add
+
+ cancelDirectory()"
+ ref="cancelButtonRef"
+ >
+ Cancel
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/settings/index.vue b/pages/settings/index.vue
new file mode 100644
index 0000000..241c840
--- /dev/null
+++ b/pages/settings/index.vue
@@ -0,0 +1,60 @@
+
+
+
+
General
+
+ Configure basic application settings
+
+
+
+
+
+
Start with system
+
+ Drop will automatically start when you log into your computer
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/settings/interface.vue b/pages/settings/interface.vue
new file mode 100644
index 0000000..3df236b
--- /dev/null
+++ b/pages/settings/interface.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/pages/setup/index.vue b/pages/setup/index.vue
index 83ae5de..1b7eacb 100644
--- a/pages/setup/index.vue
+++ b/pages/setup/index.vue
@@ -12,8 +12,7 @@
Let's get you set up
- Anim aute id magna aliqua ad ad non deserunt sunt. Qui irure qui lorem
- cupidatat commodo. Elit sunt amet fugiat veniam occaecat fugiat aliqua.
+ You'll need to have set up a Drop instance and created an account on it. If you're connecting to another person's instance, you'll need the url and an account.
https://
@@ -60,7 +60,7 @@
-
+
Don't have one?
{{ " " }}
{
+ const prefixes = ["http://", "https://"];
+
+ const doesntHavePrefix = prefixes.findIndex((e) => url.value.startsWith(e.slice(0, url.value.length))) == -1;
+
+ return doesntHavePrefix;
+})
async function connect() {
const newUrl = url.value.startsWith("http")
diff --git a/pages/store/index.vue b/pages/store/index.vue
new file mode 100644
index 0000000..64d8a3e
--- /dev/null
+++ b/pages/store/index.vue
@@ -0,0 +1,2 @@
+
+
diff --git a/plugins/global-error-handler.ts b/plugins/global-error-handler.ts
new file mode 100644
index 0000000..b7bcd45
--- /dev/null
+++ b/plugins/global-error-handler.ts
@@ -0,0 +1,8 @@
+export default defineNuxtPlugin((nuxtApp) => {
+ // Also possible
+ nuxtApp.hook("vue:error", (error, instance, info) => {
+ console.error(error, info);
+ const router = useRouter();
+ router.replace(`/error`);
+ });
+});
diff --git a/plugins/vuedraggable.ts b/plugins/vuedraggable.ts
new file mode 100644
index 0000000..58d50a0
--- /dev/null
+++ b/plugins/vuedraggable.ts
@@ -0,0 +1,5 @@
+import draggable from "vuedraggable";
+
+export default defineNuxtPlugin((nuxtApp) => {
+ nuxtApp.vueApp.component("draggable", draggable);
+});
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 2083da5..22b5365 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -58,15 +58,42 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.89"
+version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
+
+[[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
+name = "ashpd"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d43c03d9e36dd40cab48435be0b09646da362c278223ca535493877b2c1dee9"
+dependencies = [
+ "enumflags2",
+ "futures-channel",
+ "futures-util",
+ "rand 0.8.5",
+ "raw-window-handle",
+ "serde",
+ "serde_repr",
+ "tokio",
+ "url",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "zbus",
+]
[[package]]
name = "async-broadcast"
-version = "0.7.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
dependencies = [
"event-listener",
"event-listener-strategy",
@@ -112,9 +139,9 @@ dependencies = [
[[package]]
name = "async-io"
-version = "2.3.4"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
+checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
dependencies = [
"async-lock",
"cfg-if",
@@ -167,7 +194,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -202,14 +229,14 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
name = "atk"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4"
+checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
dependencies = [
"atk-sys",
"glib",
@@ -218,9 +245,9 @@ dependencies = [
[[package]]
name = "atk-sys"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009"
+checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -228,12 +255,29 @@ dependencies = [
"system-deps",
]
+[[package]]
+name = "atomic-instant-full"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6541700e074cda41b1c6f98c2cae6cde819967bf142078f069cad85387cdbe"
+
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+[[package]]
+name = "auto-launch"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f012b8cc0c850f34117ec8252a44418f2e34a2cf501de89e29b241ae5f79471"
+dependencies = [
+ "dirs 4.0.0",
+ "thiserror 1.0.69",
+ "winreg 0.10.1",
+]
+
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -255,12 +299,6 @@ dependencies = [
"windows-targets 0.52.6",
]
-[[package]]
-name = "base64"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
-
[[package]]
name = "base64"
version = "0.21.7"
@@ -274,12 +312,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
-name = "bincode"
-version = "1.3.3"
+name = "binary-stream"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+checksum = "f4ef03ef225ea9a0b680a5926a58cb45d8eb56abf23d8a8b5c5dbc61235e2dac"
dependencies = [
- "serde",
+ "thiserror 1.0.69",
]
[[package]]
@@ -335,10 +373,16 @@ dependencies = [
]
[[package]]
-name = "brotli"
-version = "6.0.0"
+name = "boxcar"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
+checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed"
+
+[[package]]
+name = "brotli"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
@@ -363,9 +407,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
-version = "1.18.0"
+version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
+checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
@@ -375,9 +419,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.7.2"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
dependencies = [
"serde",
]
@@ -393,7 +437,7 @@ dependencies = [
"glib",
"libc",
"once_cell",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -418,9 +462,9 @@ dependencies = [
[[package]]
name = "cargo-platform"
-version = "0.1.8"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
@@ -436,7 +480,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -451,12 +495,10 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.1.25"
+version = "1.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8"
+checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
dependencies = [
- "jobserver",
- "libc",
"shlex",
]
@@ -501,44 +543,19 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
-version = "0.4.38"
+version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
dependencies = [
"android-tzdata",
"iana-time-zone",
+ "js-sys",
"num-traits",
"serde",
+ "wasm-bindgen",
"windows-targets 0.52.6",
]
-[[package]]
-name = "ciborium"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
-dependencies = [
- "ciborium-io",
- "ciborium-ll",
- "serde",
-]
-
-[[package]]
-name = "ciborium-io"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
-
-[[package]]
-name = "ciborium-ll"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
-dependencies = [
- "ciborium-io",
- "half",
-]
-
[[package]]
name = "cocoa"
version = "0.26.0"
@@ -614,6 +631,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "time",
+ "version_check",
+]
+
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -666,9 +693,9 @@ dependencies = [
[[package]]
name = "cpufeatures"
-version = "0.2.14"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
@@ -684,18 +711,18 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.13"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
+checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@@ -712,9 +739,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
@@ -756,17 +783,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
name = "ctor"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
+checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501"
dependencies = [
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -790,7 +817,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -801,7 +828,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -835,9 +862,15 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
- "syn 2.0.79",
+ "syn 2.0.91",
]
+[[package]]
+name = "destructure_traitobject"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -854,7 +887,16 @@ version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
dependencies = [
- "dirs-sys",
+ "dirs-sys 0.4.1",
+]
+
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys 0.3.7",
]
[[package]]
@@ -863,7 +905,18 @@ version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
- "dirs-sys",
+ "dirs-sys 0.4.1",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
]
[[package]]
@@ -884,6 +937,26 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading 0.8.6",
+]
+
[[package]]
name = "dlopen2"
version = "0.7.0"
@@ -904,7 +977,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -916,6 +989,12 @@ dependencies = [
"const-random",
]
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
[[package]]
name = "dpi"
version = "0.1.1"
@@ -927,26 +1006,44 @@ dependencies = [
[[package]]
name = "drop-app"
-version = "0.1.0"
+version = "0.2.0-beta-prerelease-1"
dependencies = [
- "ciborium",
+ "atomic-instant-full",
+ "boxcar",
+ "chrono",
"directories",
+ "hex",
+ "http",
"log",
- "os_info",
+ "log4rs",
+ "md5",
+ "openssl",
+ "parking_lot 0.12.3",
"rayon",
"reqwest",
"rustbreak",
+ "rustix",
"serde",
+ "serde-binary",
"serde_json",
- "structured-logger",
+ "serde_with",
+ "shared_child",
+ "slice-deque",
"tauri",
"tauri-build",
+ "tauri-plugin-autostart",
"tauri-plugin-deep-link",
+ "tauri-plugin-dialog",
+ "tauri-plugin-os",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
+ "throttle_my_fn",
+ "tokio",
+ "umu-wrapper-lib",
"url",
+ "urlencoding",
+ "uuid",
"webbrowser",
- "zstd",
]
[[package]]
@@ -984,16 +1081,16 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "embed-resource"
-version = "2.5.0"
+version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4e24052d7be71f0efb50c201557f6fe7d237cfd5a64fd5bcd7fd8fe32dbbffa"
+checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379"
dependencies = [
"cc",
"memchr",
"rustc_version",
"toml 0.8.2",
"vswhom",
- "winreg",
+ "winreg 0.52.0",
]
[[package]]
@@ -1004,9 +1101,9 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
-version = "0.8.34"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
@@ -1035,7 +1132,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -1056,12 +1153,12 @@ dependencies = [
[[package]]
name = "errno"
-version = "0.3.9"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1077,9 +1174,9 @@ dependencies = [
[[package]]
name = "event-listener-strategy"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2"
dependencies = [
"event-listener",
"pin-project-lite",
@@ -1087,15 +1184,15 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fdeflate"
-version = "0.3.5"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
@@ -1112,23 +1209,14 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.0.34"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
-[[package]]
-name = "fluent-uri"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
-dependencies = [
- "bitflags 1.3.2",
-]
-
[[package]]
name = "fnv"
version = "1.0.7"
@@ -1162,7 +1250,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -1231,9 +1319,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
-version = "2.3.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
+checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
dependencies = [
"fastrand",
"futures-core",
@@ -1250,7 +1338,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -1293,9 +1381,9 @@ dependencies = [
[[package]]
name = "gdk"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646"
+checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
dependencies = [
"cairo-rs",
"gdk-pixbuf",
@@ -1334,9 +1422,9 @@ dependencies = [
[[package]]
name = "gdk-sys"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2"
+checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -1351,9 +1439,9 @@ dependencies = [
[[package]]
name = "gdkwayland-sys"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a"
+checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
dependencies = [
"gdk-sys",
"glib-sys",
@@ -1365,9 +1453,9 @@ dependencies = [
[[package]]
name = "gdkx11"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db2ea8a4909d530f79921290389cbd7c34cb9d623bfe970eaae65ca5f9cd9cce"
+checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
dependencies = [
"gdk",
"gdkx11-sys",
@@ -1379,9 +1467,9 @@ dependencies = [
[[package]]
name = "gdkx11-sys"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043"
+checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
dependencies = [
"gdk-sys",
"glib-sys",
@@ -1400,6 +1488,16 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "gethostname"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30"
+dependencies = [
+ "rustix",
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "getrandom"
version = "0.1.16"
@@ -1444,7 +1542,7 @@ dependencies = [
"once_cell",
"pin-project-lite",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1480,7 +1578,7 @@ dependencies = [
"memchr",
"once_cell",
"smallvec",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -1494,7 +1592,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -1526,9 +1624,9 @@ dependencies = [
[[package]]
name = "gtk"
-version = "0.18.1"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c"
+checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
dependencies = [
"atk",
"cairo-rs",
@@ -1547,9 +1645,9 @@ dependencies = [
[[package]]
name = "gtk-sys"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722"
+checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
dependencies = [
"atk-sys",
"cairo-sys-rs",
@@ -1565,22 +1663,22 @@ dependencies = [
[[package]]
name = "gtk3-macros"
-version = "0.18.0"
+version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e"
+checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro-error",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
name = "h2"
-version = "0.4.6"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
+checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
dependencies = [
"atomic-waker",
"bytes",
@@ -1588,23 +1686,13 @@ dependencies = [
"futures-core",
"futures-sink",
"http",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
-[[package]]
-name = "half"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
-dependencies = [
- "cfg-if",
- "crunchy",
-]
-
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1619,9 +1707,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
-version = "0.15.0"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@@ -1635,12 +1723,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
[[package]]
name = "hermit-abi"
version = "0.4.0"
@@ -1655,11 +1737,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "home"
-version = "0.5.9"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
+checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -1678,13 +1760,13 @@ dependencies = [
[[package]]
name = "http"
-version = "1.1.0"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
- "itoa 1.0.11",
+ "itoa 1.0.14",
]
[[package]]
@@ -1717,10 +1799,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
-name = "hyper"
-version = "1.4.1"
+name = "humantime"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
dependencies = [
"bytes",
"futures-channel",
@@ -1729,7 +1817,7 @@ dependencies = [
"http",
"http-body",
"httparse",
- "itoa 1.0.11",
+ "itoa 1.0.14",
"pin-project-lite",
"smallvec",
"tokio",
@@ -1738,9 +1826,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.27.3"
+version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
+checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [
"futures-util",
"http",
@@ -1771,9 +1859,9 @@ dependencies = [
[[package]]
name = "hyper-util"
-version = "0.1.9"
+version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
+checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
dependencies = [
"bytes",
"futures-channel",
@@ -1821,6 +1909,124 @@ dependencies = [
"png",
]
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+]
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1829,12 +2035,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
-version = "0.5.0"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
- "unicode-bidi",
- "unicode-normalization",
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
]
[[package]]
@@ -1850,12 +2067,12 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.6.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
- "hashbrown 0.15.0",
+ "hashbrown 0.15.2",
"serde",
]
@@ -1910,9 +2127,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "javascriptcore-rs"
@@ -1948,7 +2165,7 @@ dependencies = [
"combine",
"jni-sys",
"log",
- "thiserror",
+ "thiserror 1.0.69",
"walkdir",
"windows-sys 0.45.0",
]
@@ -1959,43 +2176,34 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
-[[package]]
-name = "jobserver"
-version = "0.1.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "js-sys"
-version = "0.3.70"
+version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
name = "json-patch"
-version = "2.0.0"
+version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc"
+checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
dependencies = [
"jsonptr",
"serde",
"serde_json",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
name = "jsonptr"
-version = "0.4.7"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627"
+checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70"
dependencies = [
- "fluent-uri",
"serde",
"serde_json",
]
@@ -2050,15 +2258,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
dependencies = [
"gtk-sys",
- "libloading",
+ "libloading 0.7.4",
"once_cell",
]
[[package]]
name = "libc"
-version = "0.2.159"
+version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libloading"
@@ -2070,6 +2278,16 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "libloading"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
+dependencies = [
+ "cfg-if",
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "libredox"
version = "0.1.3"
@@ -2086,6 +2304,12 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+[[package]]
+name = "litemap"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
+
[[package]]
name = "lock_api"
version = "0.4.12"
@@ -2103,7 +2327,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
dependencies = [
"serde",
- "value-bag",
+]
+
+[[package]]
+name = "log-mdc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7"
+
+[[package]]
+name = "log4rs"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6"
+dependencies = [
+ "anyhow",
+ "arc-swap",
+ "chrono",
+ "derivative",
+ "fnv",
+ "humantime",
+ "libc",
+ "log",
+ "log-mdc",
+ "once_cell",
+ "parking_lot 0.12.3",
+ "rand 0.8.5",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "serde_yaml",
+ "thiserror 1.0.69",
+ "thread-id",
+ "typemap-ors",
+ "winapi",
]
[[package]]
@@ -2112,6 +2369,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+[[package]]
+name = "mach"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -2141,6 +2407,12 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
+[[package]]
+name = "md5"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+
[[package]]
name = "memchr"
version = "2.7.4"
@@ -2164,9 +2436,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "miniz_oxide"
-version = "0.8.0"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
"simd-adler32",
@@ -2174,11 +2446,10 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi 0.3.9",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
@@ -2186,9 +2457,9 @@ dependencies = [
[[package]]
name = "muda"
-version = "0.15.1"
+version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8123dfd4996055ac9b15a60ad263b44b01e539007523ad7a4a533a3d93b0591"
+checksum = "fdae9c00e61cc0579bcac625e8ad22104c60548a025bfc972dc83868a28e1484"
dependencies = [
"crossbeam-channel",
"dpi",
@@ -2200,7 +2471,7 @@ dependencies = [
"once_cell",
"png",
"serde",
- "thiserror",
+ "thiserror 1.0.69",
"windows-sys 0.59.0",
]
@@ -2233,7 +2504,7 @@ dependencies = [
"ndk-sys",
"num_enum",
"raw-window-handle",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -2308,7 +2579,7 @@ dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -2318,7 +2589,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
- "objc_exception",
]
[[package]]
@@ -2326,6 +2596,9 @@ name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
+dependencies = [
+ "cc",
+]
[[package]]
name = "objc2"
@@ -2353,6 +2626,30 @@ dependencies = [
"objc2-quartz-core",
]
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
+dependencies = [
+ "bitflags 2.6.0",
+ "block2",
+ "objc2",
+ "objc2-core-location",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-contacts"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
+dependencies = [
+ "block2",
+ "objc2",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-core-data"
version = "0.2.2"
@@ -2377,6 +2674,18 @@ dependencies = [
"objc2-metal",
]
+[[package]]
+name = "objc2-core-location"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
+dependencies = [
+ "block2",
+ "objc2",
+ "objc2-contacts",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-encode"
version = "4.0.3"
@@ -2391,10 +2700,23 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.6.0",
"block2",
+ "dispatch",
"libc",
"objc2",
]
+[[package]]
+name = "objc2-link-presentation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
+dependencies = [
+ "block2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+]
+
[[package]]
name = "objc2-metal"
version = "0.2.2"
@@ -2421,46 +2743,93 @@ dependencies = [
]
[[package]]
-name = "objc_exception"
-version = "0.1.2"
+name = "objc2-symbols"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
dependencies = [
- "cc",
+ "objc2",
+ "objc2-foundation",
]
[[package]]
-name = "objc_id"
-version = "0.1.1"
+name = "objc2-ui-kit"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
+checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
- "objc",
+ "bitflags 2.6.0",
+ "block2",
+ "objc2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-foundation",
+ "objc2-link-presentation",
+ "objc2-quartz-core",
+ "objc2-symbols",
+ "objc2-uniform-type-identifiers",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-uniform-type-identifiers"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
+dependencies = [
+ "block2",
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
+dependencies = [
+ "bitflags 2.6.0",
+ "block2",
+ "objc2",
+ "objc2-core-location",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-web-kit"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
+dependencies = [
+ "bitflags 2.6.0",
+ "block2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
]
[[package]]
name = "object"
-version = "0.36.5"
+version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
-version = "1.20.1"
+version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
-dependencies = [
- "portable-atomic",
-]
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "open"
-version = "5.3.0"
+version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3"
+checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c"
dependencies = [
"is-wsl",
"libc",
@@ -2469,9 +2838,9 @@ dependencies = [
[[package]]
name = "openssl"
-version = "0.10.66"
+version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
+checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
@@ -2490,7 +2859,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -2500,13 +2869,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
-name = "openssl-sys"
-version = "0.9.103"
+name = "openssl-src"
+version = "300.4.1+3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
+checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
dependencies = [
"cc",
"libc",
+ "openssl-src",
"pkg-config",
"vcpkg",
]
@@ -2517,6 +2896,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ordered-multimap"
version = "0.7.3"
@@ -2539,9 +2927,9 @@ dependencies = [
[[package]]
name = "os_info"
-version = "3.8.2"
+version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092"
+checksum = "eb6651f4be5e39563c4fe5cc8326349eb99a25d805a3493f791d5bfd0269e430"
dependencies = [
"log",
"serde",
@@ -2589,6 +2977,17 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core 0.8.6",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.3"
@@ -2596,7 +2995,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
- "parking_lot_core",
+ "parking_lot_core 0.9.10",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall 0.2.16",
+ "smallvec",
+ "winapi",
]
[[package]]
@@ -2607,16 +3020,16 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
- "redox_syscall",
+ "redox_syscall 0.5.8",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "pathdiff"
-version = "0.2.1"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "percent-encoding"
@@ -2728,7 +3141,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -2760,9 +3173,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pin-utils"
@@ -2794,17 +3207,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64 0.22.1",
- "indexmap 2.6.0",
- "quick-xml",
+ "indexmap 2.7.0",
+ "quick-xml 0.32.0",
"serde",
"time",
]
[[package]]
name = "png"
-version = "0.17.14"
+version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
@@ -2815,25 +3228,19 @@ dependencies = [
[[package]]
name = "polling"
-version = "3.7.3"
+version = "3.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
+checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
dependencies = [
"cfg-if",
"concurrent-queue",
- "hermit-abi 0.4.0",
+ "hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys 0.59.0",
]
-[[package]]
-name = "portable-atomic"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
-
[[package]]
name = "powerfmt"
version = "0.2.0"
@@ -2907,9 +3314,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
-version = "1.0.86"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@@ -2923,6 +3330,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "quick-xml"
+version = "0.36.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.37"
@@ -3041,9 +3457,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.5.7"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags 2.6.0",
]
@@ -3056,14 +3481,14 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"libredox",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
name = "regex"
-version = "1.11.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@@ -3073,9 +3498,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.8"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@@ -3090,9 +3515,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
-version = "0.12.8"
+version = "0.12.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b"
+checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -3134,6 +3559,29 @@ dependencies = [
"windows-registry 0.2.0",
]
+[[package]]
+name = "rfd"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e"
+dependencies = [
+ "ashpd",
+ "block2",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "js-sys",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "raw-window-handle",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "ring"
version = "0.17.8"
@@ -3166,11 +3614,9 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460d97902465327d69ecfe8cefdb5972c6f94d6127ac9e992acdb51458bebc27"
dependencies = [
- "base64 0.12.3",
- "bincode",
"serde",
"tempfile",
- "thiserror",
+ "thiserror 1.0.69",
]
[[package]]
@@ -3190,22 +3636,22 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.37"
+version = "0.38.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
dependencies = [
"bitflags 2.6.0",
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
name = "rustls"
-version = "0.23.13"
+version = "0.23.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
+checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
dependencies = [
"once_cell",
"rustls-pki-types",
@@ -3225,9 +3671,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.9.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55"
+checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
[[package]]
name = "rustls-webpki"
@@ -3257,9 +3703,9 @@ dependencies = [
[[package]]
name = "schannel"
-version = "0.1.24"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
@@ -3288,9 +3734,15 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
- "syn 2.0.79",
+ "syn 2.0.91",
]
+[[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -3312,9 +3764,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.12.0"
+version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
+checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
dependencies = [
"core-foundation-sys",
"libc",
@@ -3342,22 +3794,33 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.23"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba"
dependencies = [
"serde",
]
[[package]]
name = "serde"
-version = "1.0.210"
+version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
+[[package]]
+name = "serde-binary"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b550db407b83ed53a4f76f888bfd7441b685abc2c086e20fb47781a286940506"
+dependencies = [
+ "binary-stream",
+ "serde",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "serde-untagged"
version = "0.1.6"
@@ -3370,14 +3833,24 @@ dependencies = [
]
[[package]]
-name = "serde_derive"
-version = "1.0.210"
+name = "serde-value"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.216"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -3388,25 +3861,16 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
-]
-
-[[package]]
-name = "serde_fmt"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4"
-dependencies = [
- "serde",
+ "syn 2.0.91",
]
[[package]]
name = "serde_json"
-version = "1.0.128"
+version = "1.0.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
+checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
dependencies = [
- "itoa 1.0.11",
+ "itoa 1.0.14",
"memchr",
"ryu",
"serde",
@@ -3420,7 +3884,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -3439,22 +3903,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
- "itoa 1.0.11",
+ "itoa 1.0.14",
"ryu",
"serde",
]
[[package]]
name = "serde_with"
-version = "3.10.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc"
+checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
"serde",
"serde_derive",
"serde_json",
@@ -3464,14 +3928,27 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.10.0"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec"
+checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e"
dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.9.34+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
+dependencies = [
+ "indexmap 2.7.0",
+ "itoa 1.0.14",
+ "ryu",
+ "serde",
+ "unsafe-libyaml",
]
[[package]]
@@ -3574,6 +4051,17 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "slice-deque"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25"
+dependencies = [
+ "libc",
+ "mach",
+ "winapi",
+]
+
[[package]]
name = "smallvec"
version = "1.13.2"
@@ -3582,9 +4070,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -3606,7 +4094,7 @@ dependencies = [
"objc2-foundation",
"objc2-quartz-core",
"raw-window-handle",
- "redox_syscall",
+ "redox_syscall 0.5.8",
"wasm-bindgen",
"web-sys",
"windows-sys 0.59.0",
@@ -3664,7 +4152,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
dependencies = [
"new_debug_unreachable",
"once_cell",
- "parking_lot",
+ "parking_lot 0.12.3",
"phf_shared 0.10.0",
"precomputed-hash",
"serde",
@@ -3688,103 +4176,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-[[package]]
-name = "structured-logger"
-version = "1.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16524b1ef57fd2e253216ab20ec44f0dc32b29155a4b3e6bef0a857d8c9f5f08"
-dependencies = [
- "log",
- "parking_lot",
- "serde",
- "serde_json",
- "tokio",
-]
-
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-[[package]]
-name = "sval"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf38d1fa2ce984086ea42fb856a9f374d94680a4f796831a7fc868d7f2af1b9"
-
-[[package]]
-name = "sval_buffer"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81682ff859964ca1d7cf3d3d0f9ec7204ea04c2c32acb8cc2cf68ecbd3127354"
-dependencies = [
- "sval",
- "sval_ref",
-]
-
-[[package]]
-name = "sval_dynamic"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a213b93bb4c6f4c9f9b17f2e740e077fd18746bbf7c80c72bbadcac68fa7ee4"
-dependencies = [
- "sval",
-]
-
-[[package]]
-name = "sval_fmt"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6902c6d3fb52c89206fe0dc93546c0123f7d48b5997fd14e61c9e64ff0b63275"
-dependencies = [
- "itoa 1.0.11",
- "ryu",
- "sval",
-]
-
-[[package]]
-name = "sval_json"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11a28041ea78cdc394b930ae6b897d36246dc240a29a6edf82d76562487fb0b4"
-dependencies = [
- "itoa 1.0.11",
- "ryu",
- "sval",
-]
-
-[[package]]
-name = "sval_nested"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "850346e4b0742a7f2fd2697d703ff80084d0b658f0f2e336d71b8a06abf9b68e"
-dependencies = [
- "sval",
- "sval_buffer",
- "sval_ref",
-]
-
-[[package]]
-name = "sval_ref"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824afd97a8919f28a35b0fdea979845cc2ae461a8a3aaa129455cb89c88bb77a"
-dependencies = [
- "sval",
-]
-
-[[package]]
-name = "sval_serde"
-version = "2.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ada7520dd719ed672c786c7db7de4f5230f4d504b0821bd8305cd30ca442315"
-dependencies = [
- "serde",
- "sval",
- "sval_nested",
-]
-
[[package]]
name = "swift-rs"
version = "1.0.7"
@@ -3809,9 +4206,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.79"
+version = "2.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
dependencies = [
"proc-macro2",
"quote",
@@ -3820,13 +4217,33 @@ dependencies = [
[[package]]
name = "sync_wrapper"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "sys-locale"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "system-configuration"
version = "0.6.1"
@@ -3863,9 +4280,9 @@ dependencies = [
[[package]]
name = "tao"
-version = "0.30.3"
+version = "0.30.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0dbbebe82d02044dfa481adca1550d6dd7bd16e086bc34fa0fbecceb5a63751"
+checksum = "6682a07cf5bab0b8a2bd20d0a542917ab928b5edb75ebd4eda6b05cbaab872da"
dependencies = [
"bitflags 2.6.0",
"cocoa",
@@ -3888,7 +4305,7 @@ dependencies = [
"ndk-sys",
"objc",
"once_cell",
- "parking_lot",
+ "parking_lot 0.12.3",
"raw-window-handle",
"scopeguard",
"tao-macros",
@@ -3908,7 +4325,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -3919,13 +4336,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
-version = "2.0.1"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3fad474c02a3bcd4a304afff97159a31b9bab84e29563f7109c7b0ce8cd774e"
+checksum = "e545de0a2dfe296fa67db208266cd397c5a55ae782da77973ef4c4fac90e9f2c"
dependencies = [
"anyhow",
"bytes",
- "dirs",
+ "dirs 5.0.1",
"dunce",
"embed_plist",
"futures-util",
@@ -3956,7 +4373,7 @@ dependencies = [
"tauri-runtime",
"tauri-runtime-wry",
"tauri-utils",
- "thiserror",
+ "thiserror 2.0.9",
"tokio",
"tray-icon",
"url",
@@ -3969,13 +4386,13 @@ dependencies = [
[[package]]
name = "tauri-build"
-version = "2.0.1"
+version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "935f9b3c49b22b3e2e485a57f46d61cd1ae07b1cbb2ba87387a387caf2d8c4e7"
+checksum = "7bd2a4bcfaf5fb9f4be72520eefcb61ae565038f8ccba2a497d8c28f463b8c01"
dependencies = [
"anyhow",
"cargo_toml",
- "dirs",
+ "dirs 5.0.1",
"glob",
"heck 0.5.0",
"json-patch",
@@ -3991,9 +4408,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
-version = "2.0.1"
+version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95d7443dd4f0b597704b6a14b964ee2ed16e99928d8e6292ae9825f09fbcd30e"
+checksum = "bf79faeecf301d3e969b1fae977039edb77a4c1f25cc0a961be298b54bff97cf"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -4007,9 +4424,9 @@ dependencies = [
"serde",
"serde_json",
"sha2",
- "syn 2.0.79",
+ "syn 2.0.91",
"tauri-utils",
- "thiserror",
+ "thiserror 2.0.9",
"time",
"url",
"uuid",
@@ -4018,23 +4435,23 @@ dependencies = [
[[package]]
name = "tauri-macros"
-version = "2.0.1"
+version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d2c0963ccfc3f5194415f2cce7acc975942a8797fbabfb0aa1ed6f59326ae7f"
+checksum = "c52027c8c5afb83166dacddc092ee8fff50772f9646d461d8c33ee887e447a03"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
"tauri-codegen",
"tauri-utils",
]
[[package]]
name = "tauri-plugin"
-version = "2.0.1"
+version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2e6660a409963e4d57b9bfab4addd141eeff41bd3a7fb14e13004a832cf7ef6"
+checksum = "e753f2a30933a9bbf0a202fa47d7cc4a3401f06e8d6dcc53b79aa62954828c79"
dependencies = [
"anyhow",
"glob",
@@ -4048,30 +4465,103 @@ dependencies = [
]
[[package]]
-name = "tauri-plugin-deep-link"
-version = "2.0.1"
+name = "tauri-plugin-autostart"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31a9b5725027c6e9e075b06cb2d5c2cd3b5c29daa8012b404e1ff755cc56082f"
+checksum = "a9c13f843e5e5df3eed270fc42b02923cc1a6b5c7e56b0f3ac1d858ab2c8b5fb"
+dependencies = [
+ "auto-launch",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.9",
+]
+
+[[package]]
+name = "tauri-plugin-deep-link"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35d51ffd286073414d26353bcfc9e83e3cd63f96fa7f7a912f92f2118e5de5a6"
dependencies = [
"dunce",
- "log",
"rust-ini",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"tauri-utils",
- "thiserror",
+ "thiserror 2.0.9",
+ "tracing",
"url",
"windows-registry 0.3.0",
"windows-result",
]
[[package]]
-name = "tauri-plugin-shell"
-version = "2.0.1"
+name = "tauri-plugin-dialog"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "371fb9aca2823990a2d0db7970573be5fdf07881fcaa2b835b29631feb84aec1"
+checksum = "8b59fd750551b1066744ab956a1cd6b1ea3e1b3763b0b9153ac27a044d596426"
+dependencies = [
+ "log",
+ "raw-window-handle",
+ "rfd",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-plugin",
+ "tauri-plugin-fs",
+ "thiserror 2.0.9",
+ "url",
+]
+
+[[package]]
+name = "tauri-plugin-fs"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a1edf18000f02903a7c2e5997fb89aca455ecbc0acc15c6535afbb883be223"
+dependencies = [
+ "anyhow",
+ "dunce",
+ "glob",
+ "percent-encoding",
+ "schemars",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "tauri",
+ "tauri-plugin",
+ "tauri-utils",
+ "thiserror 2.0.9",
+ "toml 0.8.2",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "tauri-plugin-os"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dda2d571a9baf0664c1f2088db227e3072f9028602fafa885deade7547c3b738"
+dependencies = [
+ "gethostname",
+ "log",
+ "os_info",
+ "serde",
+ "serde_json",
+ "serialize-to-javascript",
+ "sys-locale",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.9",
+]
+
+[[package]]
+name = "tauri-plugin-shell"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2c50a63e60fb8925956cc5b7569f4b750ac197a4d39f13b8dd46ea8e2bad79"
dependencies = [
"encoding_rs",
"log",
@@ -4084,31 +4574,31 @@ dependencies = [
"shared_child",
"tauri",
"tauri-plugin",
- "thiserror",
+ "thiserror 2.0.9",
"tokio",
]
[[package]]
name = "tauri-plugin-single-instance"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a25ac834491d089699a2bc9266a662faf373c9f779f05a2235bc6e4d9e61769a"
+checksum = "0f36019ee9832dc99e4450bb55a21cfad8633b19c2c18bd17c7741939b070ede"
dependencies = [
- "log",
"serde",
"serde_json",
"tauri",
"tauri-plugin-deep-link",
- "thiserror",
+ "thiserror 2.0.9",
+ "tracing",
"windows-sys 0.59.0",
"zbus",
]
[[package]]
name = "tauri-runtime"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af12ad1af974b274ef1d32a94e6eba27a312b429ef28fcb98abc710df7f9151d"
+checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958"
dependencies = [
"dpi",
"gtk",
@@ -4118,16 +4608,16 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
- "thiserror",
+ "thiserror 2.0.9",
"url",
"windows",
]
[[package]]
name = "tauri-runtime-wry"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e45e88aa0b11b302d836e6ea3e507a6359044c4a8bc86b865ba99868c695753d"
+checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2"
dependencies = [
"gtk",
"http",
@@ -4151,9 +4641,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
-version = "2.0.1"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c38b0230d6880cf6dd07b6d7dd7789a0869f98ac12146e0d18d1c1049215a045"
+checksum = "9271a88f99b4adea0dc71d0baca4505475a0bbd139fb135f62958721aaa8fe54"
dependencies = [
"brotli",
"cargo_metadata",
@@ -4161,6 +4651,7 @@ dependencies = [
"dunce",
"glob",
"html5ever",
+ "http",
"infer",
"json-patch",
"kuchikiki",
@@ -4177,7 +4668,7 @@ dependencies = [
"serde_json",
"serde_with",
"swift-rs",
- "thiserror",
+ "thiserror 2.0.9",
"toml 0.8.2",
"url",
"urlpattern",
@@ -4197,9 +4688,9 @@ dependencies = [
[[package]]
name = "tempfile"
-version = "3.13.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
+checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if",
"fastrand",
@@ -4227,32 +4718,74 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
-version = "1.0.64"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
+dependencies = [
+ "thiserror-impl 2.0.9",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.64"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "thread-id"
+version = "4.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "throttle_my_fn"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "482c185e5675626c9a130b3a8f362c322a239338c882f745a1d9a85838b987f0"
+dependencies = [
+ "parking_lot 0.11.2",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
]
[[package]]
name = "time"
-version = "0.3.36"
+version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
- "itoa 1.0.11",
+ "itoa 1.0.14",
"num-conv",
"powerfmt",
"serde",
@@ -4268,9 +4801,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
-version = "0.2.18"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
@@ -4286,36 +4819,44 @@ dependencies = [
]
[[package]]
-name = "tinyvec"
-version = "1.8.0"
+name = "tinystr"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
- "tinyvec_macros",
+ "displaydoc",
+ "zerovec",
]
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
-
[[package]]
name = "tokio"
-version = "1.40.0"
+version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
- "parking_lot",
"pin-project-lite",
+ "signal-hook-registry",
"socket2",
+ "tokio-macros",
+ "tracing",
"windows-sys 0.52.0",
]
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+]
+
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
@@ -4328,20 +4869,19 @@ dependencies = [
[[package]]
name = "tokio-rustls"
-version = "0.26.0"
+version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
+checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
dependencies = [
"rustls",
- "rustls-pki-types",
"tokio",
]
[[package]]
name = "tokio-util"
-version = "0.7.12"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
@@ -4389,7 +4929,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -4402,7 +4942,7 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
- "indexmap 2.6.0",
+ "indexmap 2.7.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -4417,9 +4957,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@@ -4428,33 +4968,33 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
]
[[package]]
name = "tray-icon"
-version = "0.19.0"
+version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "533fc2d4105e0e3d96ce1c71f2d308c9fbbe2ef9c587cab63dd627ab5bde218f"
+checksum = "d48a05076dd272615d03033bf04f480199f7d1b66a8ac64d75c625fc4a70c06b"
dependencies = [
"core-graphics",
"crossbeam-channel",
- "dirs",
+ "dirs 5.0.1",
"libappindicator",
"muda",
"objc2",
@@ -4463,7 +5003,7 @@ dependencies = [
"once_cell",
"png",
"serde",
- "thiserror",
+ "thiserror 1.0.69",
"windows-sys 0.59.0",
]
@@ -4485,6 +5025,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e"
+[[package]]
+name = "typemap-ors"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867"
+dependencies = [
+ "unsafe-any-ors",
+]
+
[[package]]
name = "typenum"
version = "1.17.0"
@@ -4502,6 +5051,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "umu-wrapper-lib"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baa36636bef667cee9eb4f497c24279182b8b9f098fd04b0b8c5d2ebc4e451f1"
+
[[package]]
name = "unic-char-property"
version = "0.9.0"
@@ -4543,26 +5098,11 @@ dependencies = [
"unic-common",
]
-[[package]]
-name = "unicode-bidi"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
-
[[package]]
name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
-
-[[package]]
-name = "unicode-normalization"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
-dependencies = [
- "tinyvec",
-]
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
@@ -4570,6 +5110,21 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+[[package]]
+name = "unsafe-any-ors"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad"
+dependencies = [
+ "destructure_traitobject",
+]
+
+[[package]]
+name = "unsafe-libyaml"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
+
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -4578,9 +5133,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
-version = "2.5.2"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
@@ -4588,6 +5143,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
[[package]]
name = "urlpattern"
version = "0.3.0"
@@ -4607,49 +5168,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
-name = "uuid"
-version = "1.10.0"
+name = "utf16_iter"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom 0.2.15",
+ "rand 0.8.5",
"serde",
+ "uuid-macro-internal",
]
[[package]]
-name = "value-bag"
-version = "1.9.0"
+name = "uuid-macro-internal"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
+checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08"
dependencies = [
- "value-bag-serde1",
- "value-bag-sval2",
-]
-
-[[package]]
-name = "value-bag-serde1"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b"
-dependencies = [
- "erased-serde",
- "serde",
- "serde_fmt",
-]
-
-[[package]]
-name = "value-bag-sval2"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b"
-dependencies = [
- "sval",
- "sval_buffer",
- "sval_dynamic",
- "sval_fmt",
- "sval_json",
- "sval_ref",
- "sval_serde",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
]
[[package]]
@@ -4723,9 +5273,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@@ -4734,36 +5284,36 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.43"
+version = "0.4.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
dependencies = [
"cfg-if",
"js-sys",
+ "once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -4771,28 +5321,28 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "wasm-streams"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd"
+checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
@@ -4802,10 +5352,70 @@ dependencies = [
]
[[package]]
-name = "web-sys"
-version = "0.3.70"
+name = "wayland-backend"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix",
+ "scoped-tls",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280"
+dependencies = [
+ "bitflags 2.6.0",
+ "rustix",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
+dependencies = [
+ "bitflags 2.6.0",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
+dependencies = [
+ "proc-macro2",
+ "quick-xml 0.36.2",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09"
+dependencies = [
+ "dlib",
+ "log",
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -4813,9 +5423,9 @@ dependencies = [
[[package]]
name = "webbrowser"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e5f07fb9bc8de2ddfe6b24a71a75430673fd679e568c48b52716cef1cfae923"
+checksum = "ea9fe1ebb156110ff855242c1101df158b822487e4957b0556d9ffce9db0f535"
dependencies = [
"block2",
"core-foundation 0.10.0",
@@ -4895,7 +5505,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -4904,7 +5514,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886"
dependencies = [
- "thiserror",
+ "thiserror 1.0.69",
"windows",
"windows-core 0.58.0",
]
@@ -4994,7 +5604,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -5005,7 +5615,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
]
[[package]]
@@ -5290,6 +5900,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "winreg"
version = "0.52.0"
@@ -5301,15 +5920,26 @@ dependencies = [
]
[[package]]
-name = "wry"
-version = "0.44.1"
+name = "write16"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "440600584cfbd8b0d28eace95c1f2c253db05dae43780b79380aa1e868f04c73"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
+name = "wry"
+version = "0.47.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ce51277d65170f6379d8cda935c80e3c2d1f0ff712a123c8bddb11b31a4b73"
dependencies = [
"base64 0.22.1",
- "block",
- "cocoa",
- "core-graphics",
+ "block2",
+ "cookie",
"crossbeam-channel",
"dpi",
"dunce",
@@ -5322,15 +5952,19 @@ dependencies = [
"kuchikiki",
"libc",
"ndk",
- "objc",
- "objc_id",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "objc2-web-kit",
"once_cell",
"percent-encoding",
"raw-window-handle",
"sha2",
"soup3",
"tao-macros",
- "thiserror",
+ "thiserror 1.0.69",
+ "url",
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
@@ -5371,6 +6005,30 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+ "synstructure",
+]
+
[[package]]
name = "zbus"
version = "4.0.1"
@@ -5401,6 +6059,7 @@ dependencies = [
"serde_repr",
"sha1",
"static_assertions",
+ "tokio",
"tracing",
"uds_windows",
"windows-sys 0.52.0",
@@ -5453,7 +6112,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn 2.0.91",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
+ "synstructure",
]
[[package]]
@@ -5463,31 +6143,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
-name = "zstd"
-version = "0.13.2"
+name = "zerovec"
+version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
- "zstd-safe",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
]
[[package]]
-name = "zstd-safe"
-version = "7.2.1"
+name = "zerovec-derive"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
- "zstd-sys",
-]
-
-[[package]]
-name = "zstd-sys"
-version = "2.0.13+zstd.1.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
-dependencies = [
- "cc",
- "pkg-config",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.91",
]
[[package]]
@@ -5500,6 +6174,7 @@ dependencies = [
"enumflags2",
"serde",
"static_assertions",
+ "url",
"zvariant_derive",
]
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index a26e11a..f9f24c3 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,8 +1,8 @@
[package]
name = "drop-app"
-version = "0.1.0"
-description = "A Tauri App"
-authors = ["you"]
+version = "0.2.0-beta-prerelease-1"
+description = "The client application for the open-source, self-hosted game distribution platform Drop"
+authors = ["Drop OSS"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,30 +17,82 @@ tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
name = "drop_app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
+[build]
+rustflags = ["-C", "target-feature=+aes,+sse2"]
+
+
[build-dependencies]
tauri-build = { version = "2.0.0", features = [] }
[dependencies]
-tauri = { version = "2.0.0", features = [] }
tauri-plugin-shell = "2.0.0"
-serde = { version = "1", features = ["derive"] }
serde_json = "1"
-ciborium = "0.2.2"
-zstd = "0.13.2"
+serde-binary = "0.5.0"
rayon = "1.10.0"
directories = "5.0.1"
webbrowser = "1.0.2"
url = "2.5.2"
-os_info = "3.8.2"
tauri-plugin-deep-link = "2"
log = "0.4.22"
-structured-logger = "1.0.3"
+hex = "0.4.3"
+tauri-plugin-dialog = "2"
+http = "1.1.0"
+urlencoding = "2.1.3"
+md5 = "0.7.0"
+chrono = "0.4.38"
+tauri-plugin-os = "2"
+boxcar = "0.2.7"
+umu-wrapper-lib = "0.1.0"
+tauri-plugin-autostart = "2.0.0"
+shared_child = "1.0.1"
+serde_with = "3.12.0"
+slice-deque = "0.3.0"
+throttle_my_fn = "0.2.6"
+parking_lot = "0.12.3"
+atomic-instant-full = "0.1.0"
+
+[dependencies.tauri]
+version = "2.1.1"
+features = ["tray-icon"]
+
+
+[dependencies.tokio]
+version = "1.40.0"
+features = ["rt", "tokio-macros", "signal"]
+
+[dependencies.log4rs]
+version = "1.3.0"
+features = ["console_appender", "file_appender"]
+
+[dependencies.rustix]
+version = "0.38.37"
+features = ["fs"]
+
+[dependencies.uuid]
+version = "1.10.0"
+features = [
+ "v4", # Lets you generate random UUIDs
+ "fast-rng", # Use a faster (but still sufficiently random) RNG
+ "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
+]
+
+[dependencies.openssl]
+version = "0.10.66"
+features = ["vendored"]
[dependencies.rustbreak]
version = "2"
-features = ["bin_enc"] # You can also use "yaml_enc" or "bin_enc"
+features = [] # You can also use "yaml_enc" or "bin_enc"
[dependencies.reqwest]
version = "0.12"
features = ["json", "blocking"]
+[dependencies.serde]
+version = "1"
+features = ["derive", "rc"]
+
+[profile.release]
+lto = true
+codegen-units = 1
+panic = 'abort'
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 722d748..1b818b3 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -12,6 +12,8 @@
"core:window:allow-minimize",
"core:window:allow-maximize",
"core:window:allow-close",
- "deep-link:default"
+ "deep-link:default",
+ "dialog:default",
+ "os:default"
]
}
\ No newline at end of file
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
index fb86454..2051e64 100644
Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
index eaa53e0..2c92711 100644
Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
index 30c706b..c29398b 100644
Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
index 8853b75..aea2aaa 100644
Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
index 64bfab1..bb8388c 100644
Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
index 681070b..121eec2 100644
Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
index c060843..2b8cb36 100644
Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
index e1b3ae4..333ce99 100644
Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
index b234739..a8c7aee 100644
Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
index 67e8901..0f8ab94 100644
Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
index 504810a..ba17126 100644
Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
index 4518d3d..0ffe2af 100644
Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
index 8999952..d81b635 100644
Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
index 2d44179..76692cd 100644
Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
index 570ff15..cff0bbd 100644
Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
index 2d44179..76692cd 100644
Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
index 6b3c98a..33731b4 100644
Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
index 6cc3e55..6992d79 100644
Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
index 6b3c98a..33731b4 100644
Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
index 4242bb9..9d65352 100644
Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
index 6416231..2aa6b6a 100644
Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
index 4242bb9..9d65352 100644
Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
index 63def73..cb13713 100644
Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
index 124b568..6ec9ccb 100644
Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
index 63def73..cb13713 100644
Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
index 19cd0a6..ccf1fdd 100644
Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
index e93dc96..0118205 100644
Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
index 19cd0a6..ccf1fdd 100644
Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
index 8c4bd77..85c17d8 100644
Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
index 9eb674f..35d3949 100644
Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
index 67597fc..195b297 100644
Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png
index 6074471..b7009ad 100644
Binary files a/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
index 751e0c7..c3803aa 100644
Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png
index 751e0c7..c3803aa 100644
Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png
index c6c4597..e1b8caf 100644
Binary files a/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png
index 61f711f..d1fc939 100644
Binary files a/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
index 8083495..a525bb6 100644
Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png
index 8083495..a525bb6 100644
Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png
index bff5105..2e2fd4e 100644
Binary files a/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png
index 751e0c7..c3803aa 100644
Binary files a/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
index e2ff037..ccecedb 100644
Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png
index e2ff037..ccecedb 100644
Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png
index 7a9622b..66db4b3 100644
Binary files a/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png
index fda7e2d..94a6016 100644
Binary files a/src-tauri/icons/ios/AppIcon-512@2x.png and b/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png
index 7a9622b..66db4b3 100644
Binary files a/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png
index b73ae03..d042860 100644
Binary files a/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png
index e9af983..881e0ff 100644
Binary files a/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png
index 46e239e..fa3795b 100644
Binary files a/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
index 25de944..ac54eb4 100644
Binary files a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/src-tauri/rust-toolchain.toml b/src-tauri/rust-toolchain.toml
new file mode 100644
index 0000000..271800c
--- /dev/null
+++ b/src-tauri/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
\ No newline at end of file
diff --git a/src-tauri/src/auth.rs b/src-tauri/src/auth.rs
deleted file mode 100644
index 1c7403b..0000000
--- a/src-tauri/src/auth.rs
+++ /dev/null
@@ -1,136 +0,0 @@
-use std::{
- borrow::{Borrow, BorrowMut},
- sync::Mutex,
-};
-
-use log::info;
-use serde::{Deserialize, Serialize};
-use tauri::{App, AppHandle, Emitter, Error, EventLoopMessage, Manager, Wry};
-use url::Url;
-
-use crate::{data::DatabaseCerts, AppState, AppStatus, User, DB};
-
-#[derive(Serialize)]
-struct InitiateRequestBody {
- name: String,
- platform: String,
-}
-
-#[derive(Serialize)]
-struct HandshakeRequestBody {
- clientId: String,
- token: String,
-}
-
-#[derive(Deserialize)]
-struct HandshakeResponse {
- private: String,
- certificate: String,
- id: String,
-}
-
-macro_rules! unwrap_or_return {
- ( $e:expr, $app:expr ) => {
- match $e {
- Ok(x) => x,
- Err(_) => {
- $app.emit("auth/failed", ()).unwrap();
- return;
- }
- }
- };
-}
-
-pub fn recieve_handshake(app: AppHandle, path: String) {
- // Tell the app we're processing
- app.emit("auth/processing", ()).unwrap();
-
- let path_chunks: Vec<&str> = path.split("/").collect();
- if path_chunks.len() != 3 {
- app.emit("auth/failed", ()).unwrap();
- return;
- }
-
- let base_url = {
- let handle = DB.borrow_data().unwrap();
- Url::parse(handle.base_url.as_str()).unwrap()
- };
-
- let client_id = path_chunks.get(1).unwrap();
- let token = path_chunks.get(2).unwrap();
- let body = HandshakeRequestBody {
- clientId: client_id.to_string(),
- token: token.to_string(),
- };
-
- let endpoint = unwrap_or_return!(base_url.join("/api/v1/client/handshake"), app);
- let client = reqwest::blocking::Client::new();
- let response = unwrap_or_return!(client.post(endpoint).json(&body).send(), app);
- info!("server responded with {}", response.status());
- let response_struct = unwrap_or_return!(response.json::(), app);
-
- {
- let mut handle = DB.borrow_data_mut().unwrap();
- handle.certs = Some(DatabaseCerts {
- private: response_struct.private,
- cert: response_struct.certificate,
- });
- drop(handle);
- DB.save().unwrap();
- }
-
- {
- let app_state = app.state::>();
- let mut app_state_handle = app_state.lock().unwrap();
- app_state_handle.status = AppStatus::SignedIn;
- }
-
- app.emit("auth/finished", ()).unwrap();
-}
-
-#[tauri::command]
-pub async fn auth_initiate<'a>() -> Result<(), String> {
- let base_url = {
- let db_lock = DB.borrow_data().unwrap();
- Url::parse(&db_lock.base_url.clone()).unwrap()
- };
-
- let current_os_info = os_info::get();
-
- let endpoint = base_url.join("/api/v1/client/initiate").unwrap();
- let body = InitiateRequestBody {
- name: format!("Drop Desktop Client"),
- platform: current_os_info.os_type().to_string(),
- };
-
- let client = reqwest::Client::new();
- let response = client
- .post(endpoint.to_string())
- .json(&body)
- .send()
- .await
- .unwrap();
-
- let redir_url = response.text().await.unwrap();
- let complete_redir_url = base_url.join(&redir_url).unwrap();
-
- info!("opening web browser to continue authentication");
- webbrowser::open(&complete_redir_url.to_string()).unwrap();
-
- return Ok(());
-}
-
-pub fn setup() -> Result<(AppStatus, Option), Error> {
- let data = DB.borrow_data().unwrap();
-
- // If we have certs, exit for now
- if data.certs.is_some() {
- // TODO: check if it's still valid, and fetch user information
- info!("have existing certs, assuming logged in...");
- return Ok((AppStatus::SignedInNeedsReauth, None));
- }
-
- drop(data);
-
- return Ok((AppStatus::SignedOut, None));
-}
diff --git a/src-tauri/src/autostart.rs b/src-tauri/src/autostart.rs
new file mode 100644
index 0000000..dec9148
--- /dev/null
+++ b/src-tauri/src/autostart.rs
@@ -0,0 +1,76 @@
+use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db};
+use log::debug;
+use tauri::AppHandle;
+use tauri_plugin_autostart::ManagerExt;
+
+pub fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(), String> {
+ let manager = app.autolaunch();
+ if enabled {
+ manager.enable().map_err(|e| e.to_string())?;
+ debug!("enabled autostart");
+ } else {
+ manager.disable().map_err(|e| e.to_string())?;
+ debug!("eisabled autostart");
+ }
+
+ // Store the state in DB
+ let mut db_handle = borrow_db_mut_checked();
+ db_handle.settings.autostart = enabled;
+ drop(db_handle);
+ save_db();
+
+ Ok(())
+}
+
+pub fn get_autostart_enabled_logic(app: AppHandle) -> Result {
+ // First check DB state
+ let db_handle = borrow_db_checked();
+ let db_state = db_handle.settings.autostart;
+ drop(db_handle);
+
+ // Get actual system state
+ let manager = app.autolaunch();
+ let system_state = manager.is_enabled()?;
+
+ // If they don't match, sync to DB state
+ if db_state != system_state {
+ if db_state {
+ manager.enable()?;
+ } else {
+ manager.disable()?;
+ }
+ }
+
+ Ok(db_state)
+}
+
+// New function to sync state on startup
+pub fn sync_autostart_on_startup(app: &AppHandle) -> Result<(), String> {
+ let db_handle = borrow_db_checked();
+ let should_be_enabled = db_handle.settings.autostart;
+ drop(db_handle);
+
+ let manager = app.autolaunch();
+ let current_state = manager.is_enabled().map_err(|e| e.to_string())?;
+
+ if current_state != should_be_enabled {
+ if should_be_enabled {
+ manager.enable().map_err(|e| e.to_string())?;
+ debug!("synced autostart: enabled");
+ } else {
+ manager.disable().map_err(|e| e.to_string())?;
+ debug!("synced autostart: disabled");
+ }
+ }
+
+ Ok(())
+}
+#[tauri::command]
+pub fn toggle_autostart(app: AppHandle, enabled: bool) -> Result<(), String> {
+ toggle_autostart_logic(app, enabled)
+}
+
+#[tauri::command]
+pub fn get_autostart_enabled(app: AppHandle) -> Result {
+ get_autostart_enabled_logic(app)
+}
diff --git a/src-tauri/src/cleanup.rs b/src-tauri/src/cleanup.rs
new file mode 100644
index 0000000..de0e4e6
--- /dev/null
+++ b/src-tauri/src/cleanup.rs
@@ -0,0 +1,23 @@
+use log::{debug, error};
+use tauri::AppHandle;
+
+use crate::AppState;
+
+#[tauri::command]
+pub fn quit(app: tauri::AppHandle, state: tauri::State<'_, std::sync::Mutex>>) {
+ cleanup_and_exit(&app, &state);
+}
+
+pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mutex>>) {
+ debug!("cleaning up and exiting application");
+ let download_manager = state.lock().unwrap().download_manager.clone();
+ match download_manager.ensure_terminated() {
+ Ok(res) => match res {
+ Ok(_) => debug!("download manager terminated correctly"),
+ Err(_) => error!("download manager failed to terminate correctly"),
+ },
+ Err(e) => panic!("{:?}", e),
+ }
+
+ app.exit(0);
+}
diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
new file mode 100644
index 0000000..b47a348
--- /dev/null
+++ b/src-tauri/src/commands.rs
@@ -0,0 +1,11 @@
+use crate::AppState;
+
+#[tauri::command]
+pub fn fetch_state(
+ state: tauri::State<'_, std::sync::Mutex>>,
+) -> Result {
+ let guard = state.lock().unwrap();
+ let cloned_state = serde_json::to_string(&guard.clone()).map_err(|e| e.to_string())?;
+ drop(guard);
+ Ok(cloned_state)
+}
diff --git a/src-tauri/src/data.rs b/src-tauri/src/data.rs
deleted file mode 100644
index 4e52be4..0000000
--- a/src-tauri/src/data.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-use std::fs;
-
-use directories::BaseDirs;
-use rustbreak::{deser::Bincode, PathDatabase};
-use serde::Deserialize;
-
-use crate::DB;
-
-#[derive(serde::Serialize, Clone, Deserialize)]
-pub struct DatabaseCerts {
- pub private: String,
- pub cert: String,
-}
-
-#[derive(serde::Serialize, Clone, Deserialize)]
-pub struct Database {
- pub certs: Option,
- pub base_url: String,
-}
-
-pub type DatabaseInterface =
- rustbreak::Database;
-
-pub fn setup() -> DatabaseInterface {
- let db_path = BaseDirs::new().unwrap().data_dir().join("drop");
- let default = Database {
- certs: None,
- base_url: "".to_string(),
- };
- let db = match fs::exists(db_path.clone()).unwrap() {
- true => PathDatabase::load_from_path(db_path).expect("Database loading failed"),
- false => PathDatabase::create_at_path(db_path, default).unwrap(),
- };
-
- return db;
-}
-
-pub fn is_set_up() -> bool {
- return !DB.borrow_data().unwrap().base_url.is_empty();
-}
diff --git a/src-tauri/src/database/commands.rs b/src-tauri/src/database/commands.rs
new file mode 100644
index 0000000..18befb8
--- /dev/null
+++ b/src-tauri/src/database/commands.rs
@@ -0,0 +1,92 @@
+use std::{
+ fs::create_dir_all,
+ io::{Error, ErrorKind},
+ path::{Path, PathBuf},
+};
+
+use serde_json::Value;
+
+use crate::{
+ database::{db::borrow_db_mut_checked, settings::Settings},
+ download_manager::internal_error::InternalError,
+};
+
+use super::{
+ db::{borrow_db_checked, save_db, DATA_ROOT_DIR},
+ debug::SystemData,
+};
+
+// Will, in future, return disk/remaining size
+// Just returns the directories that have been set up
+#[tauri::command]
+pub fn fetch_download_dir_stats() -> Vec {
+ let lock = borrow_db_checked();
+ lock.applications.install_dirs.clone()
+}
+
+#[tauri::command]
+pub fn delete_download_dir(index: usize) {
+ let mut lock = borrow_db_mut_checked();
+ lock.applications.install_dirs.remove(index);
+ drop(lock);
+ save_db();
+}
+
+#[tauri::command]
+pub fn add_download_dir(new_dir: PathBuf) -> Result<(), InternalError<()>> {
+ // Check the new directory is all good
+ let new_dir_path = Path::new(&new_dir);
+ if new_dir_path.exists() {
+ let dir_contents = new_dir_path.read_dir()?;
+ if dir_contents.count() != 0 {
+ return Err(Error::new(
+ ErrorKind::DirectoryNotEmpty,
+ "Selected directory cannot contain any existing files",
+ )
+ .into());
+ }
+ } else {
+ create_dir_all(new_dir_path)?;
+ }
+
+ // Add it to the dictionary
+ let mut lock = borrow_db_mut_checked();
+ if lock.applications.install_dirs.contains(&new_dir) {
+ return Err(Error::new(
+ ErrorKind::AlreadyExists,
+ "Selected directory already exists in database",
+ )
+ .into());
+ }
+ lock.applications.install_dirs.push(new_dir);
+ drop(lock);
+ save_db();
+
+ Ok(())
+}
+
+#[tauri::command]
+pub fn update_settings(new_settings: Value) {
+ let mut db_lock = borrow_db_mut_checked();
+ let mut current_settings = serde_json::to_value(db_lock.settings.clone()).unwrap();
+ for (key, value) in new_settings.as_object().unwrap() {
+ current_settings[key] = value.clone();
+ }
+ let new_settings: Settings = serde_json::from_value(current_settings).unwrap();
+ db_lock.settings = new_settings;
+ println!("new Settings: {:?}", db_lock.settings);
+}
+#[tauri::command]
+pub fn fetch_settings() -> Settings {
+ borrow_db_checked().settings.clone()
+}
+#[tauri::command]
+pub fn fetch_system_data() -> SystemData {
+ let db_handle = borrow_db_checked();
+ SystemData::new(
+ db_handle.auth.as_ref().unwrap().client_id.clone(),
+ db_handle.base_url.clone(),
+ DATA_ROOT_DIR.lock().unwrap().to_string_lossy().to_string(),
+ std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
+ )
+}
diff --git a/src-tauri/src/database/db.rs b/src-tauri/src/database/db.rs
new file mode 100644
index 0000000..b92de3d
--- /dev/null
+++ b/src-tauri/src/database/db.rs
@@ -0,0 +1,256 @@
+use std::{
+ collections::HashMap,
+ fs::{self, create_dir_all},
+ hash::Hash,
+ path::{Path, PathBuf},
+ sync::{LazyLock, Mutex, RwLockReadGuard, RwLockWriteGuard},
+};
+
+use chrono::Utc;
+use directories::BaseDirs;
+use log::{debug, error, info};
+use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use serde_with::serde_as;
+use tauri::AppHandle;
+use url::Url;
+
+use crate::{
+ database::settings::Settings,
+ download_manager::downloadable_metadata::DownloadableMetadata,
+ games::{library::push_game_update, state::GameStatusManager},
+ process::process_manager::Platform,
+ DB,
+};
+
+#[derive(serde::Serialize, Clone, Deserialize)]
+pub struct DatabaseAuth {
+ pub private: String,
+ pub cert: String,
+ pub client_id: String,
+}
+
+// Strings are version names for a particular game
+#[derive(Serialize, Clone, Deserialize)]
+#[serde(tag = "type")]
+pub enum GameDownloadStatus {
+ Remote {},
+ SetupRequired {
+ version_name: String,
+ install_dir: String,
+ },
+ Installed {
+ version_name: String,
+ install_dir: String,
+ },
+}
+
+// Stuff that shouldn't be synced to disk
+#[derive(Clone, Serialize)]
+pub enum ApplicationTransientStatus {
+ Downloading { version_name: String },
+ Uninstalling {},
+ Updating { version_name: String },
+ Running {},
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct GameVersion {
+ pub game_id: String,
+ pub version_name: String,
+
+ pub platform: Platform,
+
+ pub launch_command: String,
+ pub launch_args: Vec,
+
+ pub setup_command: String,
+ pub setup_args: Vec,
+
+ pub only_setup: bool,
+
+ pub version_index: usize,
+ pub delta: bool,
+
+ pub umu_id_override: Option,
+}
+
+#[serde_as]
+#[derive(Serialize, Clone, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct DatabaseApplications {
+ pub install_dirs: Vec,
+ // Guaranteed to exist if the game also exists in the app state map
+ pub game_statuses: HashMap,
+ pub game_versions: HashMap>,
+ pub installed_game_version: HashMap,
+
+ #[serde(skip)]
+ pub transient_statuses: HashMap,
+}
+
+#[derive(Serialize, Deserialize, Clone, Default)]
+pub struct Database {
+ #[serde(default)]
+ pub settings: Settings,
+ pub auth: Option,
+ pub base_url: String,
+ pub applications: DatabaseApplications,
+ pub prev_database: Option,
+}
+impl Database {
+ fn new>(games_base_dir: T, prev_database: Option) -> Self {
+ Self {
+ applications: DatabaseApplications {
+ install_dirs: vec![games_base_dir.into()],
+ game_statuses: HashMap::new(),
+ game_versions: HashMap::new(),
+ installed_game_version: HashMap::new(),
+ transient_statuses: HashMap::new(),
+ },
+ prev_database,
+ base_url: "".to_owned(),
+ auth: None,
+ settings: Settings {
+ autostart: false,
+ max_download_threads: 4,
+ },
+ }
+ }
+}
+pub static DATA_ROOT_DIR: LazyLock> =
+ LazyLock::new(|| Mutex::new(BaseDirs::new().unwrap().data_dir().join("drop")));
+
+// Custom JSON serializer to support everything we need
+#[derive(Debug, Default, Clone)]
+pub struct DropDatabaseSerializer;
+
+impl DeSerializer for DropDatabaseSerializer {
+ fn serialize(&self, val: &T) -> rustbreak::error::DeSerResult> {
+ serde_json::to_vec(val).map_err(|e| DeSerError::Internal(e.to_string()))
+ }
+
+ fn deserialize(&self, s: R) -> rustbreak::error::DeSerResult {
+ serde_json::from_reader(s).map_err(|e| DeSerError::Internal(e.to_string()))
+ }
+}
+
+pub type DatabaseInterface =
+ rustbreak::Database;
+
+pub trait DatabaseImpls {
+ fn set_up_database() -> DatabaseInterface;
+ fn database_is_set_up(&self) -> bool;
+ fn fetch_base_url(&self) -> Url;
+}
+impl DatabaseImpls for DatabaseInterface {
+ fn set_up_database() -> DatabaseInterface {
+ let data_root_dir = DATA_ROOT_DIR.lock().unwrap();
+ let db_path = data_root_dir.join("drop.db");
+ let games_base_dir = data_root_dir.join("games");
+ let logs_root_dir = data_root_dir.join("logs");
+
+ debug!("creating data directory at {:?}", data_root_dir);
+ create_dir_all(data_root_dir.clone()).unwrap();
+ create_dir_all(games_base_dir.clone()).unwrap();
+ create_dir_all(logs_root_dir.clone()).unwrap();
+
+ let exists = fs::exists(db_path.clone()).unwrap();
+
+ match exists {
+ true => match PathDatabase::load_from_path(db_path.clone()) {
+ Ok(db) => db,
+ Err(e) => handle_invalid_database(e, db_path, games_base_dir),
+ },
+ false => {
+ let default = Database::new(games_base_dir, None);
+ debug!(
+ "Creating database at path {}",
+ db_path.as_os_str().to_str().unwrap()
+ );
+ PathDatabase::create_at_path(db_path, default)
+ .expect("Database could not be created")
+ }
+ }
+ }
+
+ fn database_is_set_up(&self) -> bool {
+ !self.borrow_data().unwrap().base_url.is_empty()
+ }
+
+ fn fetch_base_url(&self) -> Url {
+ let handle = self.borrow_data().unwrap();
+ Url::parse(&handle.base_url).unwrap()
+ }
+}
+
+pub fn set_game_status, &DownloadableMetadata)>(
+ app_handle: &AppHandle,
+ meta: DownloadableMetadata,
+ setter: F,
+) {
+ let mut db_handle = borrow_db_mut_checked();
+ setter(&mut db_handle, &meta);
+ drop(db_handle);
+ save_db();
+
+ let status = GameStatusManager::fetch_state(&meta.id);
+
+ push_game_update(app_handle, &meta.id, status);
+}
+// TODO: Make the error relelvant rather than just assume that it's a Deserialize error
+fn handle_invalid_database(
+ _e: RustbreakError,
+ db_path: PathBuf,
+ games_base_dir: PathBuf,
+) -> rustbreak::Database {
+ let new_path = {
+ let time = Utc::now().timestamp();
+ let mut base = db_path.clone();
+ base.set_file_name(format!("drop.db.backup-{}", time));
+ base
+ };
+ info!(
+ "old database stored at: {}",
+ new_path.to_string_lossy().to_string()
+ );
+ fs::rename(&db_path, &new_path).unwrap();
+
+ let db = Database::new(
+ games_base_dir.into_os_string().into_string().unwrap(),
+ Some(new_path),
+ );
+
+ PathDatabase::create_at_path(db_path, db).expect("Database could not be created")
+}
+
+pub fn borrow_db_checked<'a>() -> RwLockReadGuard<'a, Database> {
+ match DB.borrow_data() {
+ Ok(data) => data,
+ Err(e) => {
+ error!("database borrow failed with error {}", e);
+ panic!("database borrow failed with error {}", e);
+ }
+ }
+}
+
+pub fn borrow_db_mut_checked<'a>() -> RwLockWriteGuard<'a, Database> {
+ match DB.borrow_data_mut() {
+ Ok(data) => data,
+ Err(e) => {
+ error!("database borrow mut failed with error {}", e);
+ panic!("database borrow mut failed with error {}", e);
+ }
+ }
+}
+
+pub fn save_db() {
+ match DB.save() {
+ Ok(_) => {}
+ Err(e) => {
+ error!("database failed to save with error {}", e);
+ panic!("database failed to save with error {}", e)
+ }
+ }
+}
diff --git a/src-tauri/src/database/debug.rs b/src-tauri/src/database/debug.rs
new file mode 100644
index 0000000..45d2034
--- /dev/null
+++ b/src-tauri/src/database/debug.rs
@@ -0,0 +1,21 @@
+use serde::Serialize;
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SystemData {
+ client_id: String,
+ base_url: String,
+ data_dir: String,
+ log_level: String,
+}
+
+impl SystemData {
+ pub fn new(client_id: String, base_url: String, data_dir: String, log_level: String) -> Self {
+ Self {
+ client_id,
+ base_url,
+ data_dir,
+ log_level,
+ }
+ }
+}
diff --git a/src-tauri/src/database/mod.rs b/src-tauri/src/database/mod.rs
new file mode 100644
index 0000000..2a4255b
--- /dev/null
+++ b/src-tauri/src/database/mod.rs
@@ -0,0 +1,4 @@
+pub mod commands;
+pub mod db;
+pub mod debug;
+pub mod settings;
diff --git a/src-tauri/src/database/settings.rs b/src-tauri/src/database/settings.rs
new file mode 100644
index 0000000..b8c1742
--- /dev/null
+++ b/src-tauri/src/database/settings.rs
@@ -0,0 +1,24 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Settings {
+ pub autostart: bool,
+ pub max_download_threads: usize,
+ // ... other settings ...
+}
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ autostart: false,
+ max_download_threads: 4,
+ }
+ }
+}
+// Ideally use pointers instead of a macro to assign the settings
+// fn deserialize_into(v: serde_json::Value, t: &mut T) -> Result<(), serde_json::Error>
+// where T: for<'a> Deserialize<'a>
+// {
+// *t = serde_json::from_value(v)?;
+// Ok(())
+// }
diff --git a/src-tauri/src/download_manager/commands.rs b/src-tauri/src/download_manager/commands.rs
new file mode 100644
index 0000000..0a65c0d
--- /dev/null
+++ b/src-tauri/src/download_manager/commands.rs
@@ -0,0 +1,31 @@
+use std::sync::Mutex;
+
+use crate::{download_manager::downloadable_metadata::DownloadableMetadata, AppState};
+
+#[tauri::command]
+pub fn pause_downloads(state: tauri::State<'_, Mutex>) {
+ state.lock().unwrap().download_manager.pause_downloads()
+}
+
+#[tauri::command]
+pub fn resume_downloads(state: tauri::State<'_, Mutex>) {
+ state.lock().unwrap().download_manager.resume_downloads()
+}
+
+#[tauri::command]
+pub fn move_download_in_queue(
+ state: tauri::State<'_, Mutex>,
+ old_index: usize,
+ new_index: usize,
+) {
+ state
+ .lock()
+ .unwrap()
+ .download_manager
+ .rearrange(old_index, new_index)
+}
+
+#[tauri::command]
+pub fn cancel_game(state: tauri::State<'_, Mutex>, meta: DownloadableMetadata) {
+ state.lock().unwrap().download_manager.cancel(meta)
+}
diff --git a/src-tauri/src/download_manager/download_manager.rs b/src-tauri/src/download_manager/download_manager.rs
new file mode 100644
index 0000000..b3dee68
--- /dev/null
+++ b/src-tauri/src/download_manager/download_manager.rs
@@ -0,0 +1,209 @@
+use std::{
+ any::Any,
+ collections::VecDeque,
+ fmt::Debug,
+ sync::{
+ mpsc::{SendError, Sender},
+ Mutex, MutexGuard,
+ },
+ thread::JoinHandle,
+};
+
+use log::{debug, info};
+use serde::Serialize;
+
+use crate::error::application_download_error::ApplicationDownloadError;
+
+use super::{
+ download_manager_builder::{CurrentProgressObject, DownloadAgent},
+ downloadable_metadata::DownloadableMetadata,
+ queue::Queue,
+};
+
+pub enum DownloadManagerSignal {
+ /// Resumes (or starts) the DownloadManager
+ Go,
+ /// Pauses the DownloadManager
+ Stop,
+ /// Called when a DownloadAgent has fully completed a download.
+ Completed(DownloadableMetadata),
+ /// Generates and appends a DownloadAgent
+ /// to the registry and queue
+ Queue(DownloadAgent),
+ /// Tells the Manager to stop the current
+ /// download, sync everything to disk, and
+ /// then exit
+ Finish,
+ /// Stops, removes, and tells a download to cleanup
+ Cancel(DownloadableMetadata),
+ /// Removes a given application
+ Remove(DownloadableMetadata),
+ /// Any error which occurs in the agent
+ Error(ApplicationDownloadError),
+ /// Pushes UI update
+ UpdateUIQueue,
+ UpdateUIStats(usize, usize), //kb/s and seconds
+ /// Uninstall download
+ /// Takes download ID
+ Uninstall(DownloadableMetadata),
+}
+
+#[derive(Debug, Clone)]
+pub enum DownloadManagerStatus {
+ Downloading,
+ Paused,
+ Empty,
+ Error(ApplicationDownloadError),
+ Finished,
+}
+
+impl Serialize for DownloadManagerStatus {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_str(&format!["{:?}", self])
+ }
+}
+
+#[derive(Serialize, Clone, Debug)]
+pub enum DownloadStatus {
+ Queued,
+ Downloading,
+ Error,
+}
+
+/// Accessible front-end for the DownloadManager
+///
+/// The system works entirely through signals, both internally and externally,
+/// all of which are accessible through the DownloadManagerSignal type, but
+/// should not be used directly. Rather, signals are abstracted through this
+/// interface.
+///
+/// The actual download queue may be accessed through the .edit() function,
+/// which provides raw access to the underlying queue.
+/// THIS EDITING IS BLOCKING!!!
+pub struct DownloadManager {
+ terminator: Mutex>>>,
+ download_queue: Queue,
+ progress: CurrentProgressObject,
+ command_sender: Sender,
+}
+
+#[allow(dead_code)]
+impl DownloadManager {
+ pub fn new(
+ terminator: JoinHandle>,
+ download_queue: Queue,
+ progress: CurrentProgressObject,
+ command_sender: Sender,
+ ) -> Self {
+ Self {
+ terminator: Mutex::new(Some(terminator)),
+ download_queue,
+ progress,
+ command_sender,
+ }
+ }
+
+ pub fn queue_download(
+ &self,
+ download: DownloadAgent,
+ ) -> Result<(), SendError> {
+ info!("creating download with meta {:?}", download.metadata());
+ self.command_sender
+ .send(DownloadManagerSignal::Queue(download))?;
+ self.command_sender.send(DownloadManagerSignal::Go)
+ }
+ pub fn edit(&self) -> MutexGuard<'_, VecDeque> {
+ self.download_queue.edit()
+ }
+ pub fn read_queue(&self) -> VecDeque {
+ self.download_queue.read()
+ }
+ pub fn get_current_download_progress(&self) -> Option {
+ let progress_object = (*self.progress.lock().unwrap()).clone()?;
+ Some(progress_object.get_progress())
+ }
+ pub fn rearrange_string(&self, meta: &DownloadableMetadata, new_index: usize) {
+ let mut queue = self.edit();
+ let current_index = get_index_from_id(&mut queue, meta).unwrap();
+ let to_move = queue.remove(current_index).unwrap();
+ queue.insert(new_index, to_move);
+ self.command_sender
+ .send(DownloadManagerSignal::UpdateUIQueue)
+ .unwrap();
+ }
+ pub fn cancel(&self, meta: DownloadableMetadata) {
+ self.command_sender
+ .send(DownloadManagerSignal::Cancel(meta))
+ .unwrap();
+ }
+ pub fn rearrange(&self, current_index: usize, new_index: usize) {
+ if current_index == new_index {
+ return;
+ };
+
+ let needs_pause = current_index == 0 || new_index == 0;
+ if needs_pause {
+ self.command_sender
+ .send(DownloadManagerSignal::Stop)
+ .unwrap();
+ }
+
+ debug!(
+ "moving download at index {} to index {}",
+ current_index, new_index
+ );
+
+ let mut queue = self.edit();
+ let to_move = queue.remove(current_index).unwrap();
+ queue.insert(new_index, to_move);
+ drop(queue);
+
+ if needs_pause {
+ self.command_sender.send(DownloadManagerSignal::Go).unwrap();
+ }
+ self.command_sender
+ .send(DownloadManagerSignal::UpdateUIQueue)
+ .unwrap();
+ self.command_sender
+ .send(DownloadManagerSignal::Go)
+ .unwrap();
+
+ }
+ pub fn pause_downloads(&self) {
+ self.command_sender
+ .send(DownloadManagerSignal::Stop)
+ .unwrap();
+ }
+ pub fn resume_downloads(&self) {
+ self.command_sender.send(DownloadManagerSignal::Go).unwrap();
+ }
+ pub fn ensure_terminated(&self) -> Result, Box> {
+ self.command_sender
+ .send(DownloadManagerSignal::Finish)
+ .unwrap();
+ let terminator = self.terminator.lock().unwrap().take();
+ terminator.unwrap().join()
+ }
+ pub fn uninstall_application(&self, meta: DownloadableMetadata) {
+ self.command_sender
+ .send(DownloadManagerSignal::Uninstall(meta))
+ .unwrap();
+ }
+ pub fn get_sender(&self) -> Sender {
+ self.command_sender.clone()
+ }
+}
+
+/// Takes in the locked value from .edit() and attempts to
+/// get the index of whatever id is passed in
+fn get_index_from_id(
+ queue: &mut MutexGuard<'_, VecDeque>,
+ meta: &DownloadableMetadata,
+) -> Option {
+ queue
+ .iter()
+ .position(|download_agent| download_agent == meta)
+}
diff --git a/src-tauri/src/download_manager/download_manager_builder.rs b/src-tauri/src/download_manager/download_manager_builder.rs
new file mode 100644
index 0000000..dd43dc2
--- /dev/null
+++ b/src-tauri/src/download_manager/download_manager_builder.rs
@@ -0,0 +1,364 @@
+use std::{
+ collections::HashMap,
+ sync::{
+ mpsc::{channel, Receiver, Sender},
+ Arc, Mutex,
+ },
+ thread::{spawn, JoinHandle},
+};
+
+use log::{debug, error, info, warn};
+use tauri::{AppHandle, Emitter};
+
+use crate::{
+ error::application_download_error::ApplicationDownloadError,
+ games::library::{QueueUpdateEvent, QueueUpdateEventQueueData, StatsUpdateEvent},
+};
+
+use super::{
+ download_manager::{DownloadManager, DownloadManagerSignal, DownloadManagerStatus},
+ download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
+ downloadable::Downloadable,
+ downloadable_metadata::DownloadableMetadata,
+ progress_object::ProgressObject,
+ queue::Queue,
+};
+
+pub type DownloadAgent = Arc>;
+pub type CurrentProgressObject = Arc>>>;
+
+/*
+
+Welcome to the download manager, the most overengineered, glorious piece of bullshit.
+
+The download manager takes a queue of ids and their associated
+DownloadAgents, and then, one-by-one, executes them. It provides an interface
+to interact with the currently downloading agent, and manage the queue.
+
+When the DownloadManager is initialised, it is designed to provide a reference
+which can be used to provide some instructions (the DownloadManagerInterface),
+but other than that, it runs without any sort of interruptions.
+
+It does this by opening up two data structures. Primarily is the command_receiver,
+and mpsc (multi-channel-single-producer) which allows commands to be sent from
+the Interface, and queued up for the Manager to process.
+
+These have been mapped in the DownloadManagerSignal docs.
+
+The other way to interact with the DownloadManager is via the donwload_queue,
+which is just a collection of ids which may be rearranged to suit
+whichever download queue order is required.
+
++----------------------------------------------------------------------------+
+| DO NOT ATTEMPT TO ADD OR REMOVE FROM THE QUEUE WITHOUT USING SIGNALS!! |
+| THIS WILL CAUSE A DESYNC BETWEEN THE DOWNLOAD AGENT REGISTRY AND THE QUEUE |
+| WHICH HAS NOT BEEN ACCOUNTED FOR |
++----------------------------------------------------------------------------+
+
+This download queue does not actually own any of the DownloadAgents. It is
+simply an id-based reference system. The actual Agents are stored in the
+download_agent_registry HashMap, as ordering is no issue here. This is why
+appending or removing from the download_queue must be done via signals.
+
+Behold, my madness - quexeky
+
+*/
+
+pub struct DownloadManagerBuilder {
+ download_agent_registry: HashMap,
+ download_queue: Queue,
+ command_receiver: Receiver,
+ sender: Sender,
+ progress: CurrentProgressObject,
+ status: Arc>,
+ app_handle: AppHandle,
+
+ current_download_agent: Option, // Should be the only download agent in the map with the "Go" flag
+ current_download_thread: Mutex>>,
+ active_control_flag: Option,
+}
+impl DownloadManagerBuilder {
+ pub fn build(app_handle: AppHandle) -> DownloadManager {
+ let queue = Queue::new();
+ let (command_sender, command_receiver) = channel();
+ let active_progress = Arc::new(Mutex::new(None));
+ let status = Arc::new(Mutex::new(DownloadManagerStatus::Empty));
+
+ let manager = Self {
+ download_agent_registry: HashMap::new(),
+ download_queue: queue.clone(),
+ command_receiver,
+ status: status.clone(),
+ sender: command_sender.clone(),
+ progress: active_progress.clone(),
+ app_handle,
+
+ current_download_agent: None,
+ current_download_thread: Mutex::new(None),
+ active_control_flag: None,
+ };
+
+ let terminator = spawn(|| manager.manage_queue());
+
+ DownloadManager::new(terminator, queue, active_progress, command_sender)
+ }
+
+ fn set_status(&self, status: DownloadManagerStatus) {
+ *self.status.lock().unwrap() = status;
+ }
+
+ fn remove_and_cleanup_front_download(&mut self, meta: &DownloadableMetadata) -> DownloadAgent {
+ self.download_queue.pop_front();
+ let download_agent = self.download_agent_registry.remove(meta).unwrap();
+ self.cleanup_current_download();
+ download_agent
+ }
+
+ // CAREFUL WITH THIS FUNCTION
+ // Make sure the download thread is terminated
+ fn cleanup_current_download(&mut self) {
+ self.active_control_flag = None;
+ *self.progress.lock().unwrap() = None;
+ self.current_download_agent = None;
+
+ let mut download_thread_lock = self.current_download_thread.lock().unwrap();
+ *download_thread_lock = None;
+ drop(download_thread_lock);
+ }
+
+ fn stop_and_wait_current_download(&self) {
+ self.set_status(DownloadManagerStatus::Paused);
+ if let Some(current_flag) = &self.active_control_flag {
+ current_flag.set(DownloadThreadControlFlag::Stop);
+ }
+
+ let mut download_thread_lock = self.current_download_thread.lock().unwrap();
+ if let Some(current_download_thread) = download_thread_lock.take() {
+ current_download_thread.join().unwrap();
+ }
+ }
+
+ fn manage_queue(mut self) -> Result<(), ()> {
+ loop {
+ let signal = match self.command_receiver.recv() {
+ Ok(signal) => signal,
+ Err(_) => return Err(()),
+ };
+
+ match signal {
+ DownloadManagerSignal::Go => {
+ self.manage_go_signal();
+ }
+ DownloadManagerSignal::Stop => {
+ self.manage_stop_signal();
+ }
+ DownloadManagerSignal::Completed(meta) => {
+ self.manage_completed_signal(meta);
+ }
+ DownloadManagerSignal::Queue(download_agent) => {
+ self.manage_queue_signal(download_agent);
+ }
+ DownloadManagerSignal::Error(e) => {
+ self.manage_error_signal(e);
+ }
+ DownloadManagerSignal::UpdateUIQueue => {
+ self.push_ui_queue_update();
+ }
+ DownloadManagerSignal::UpdateUIStats(kbs, time) => {
+ self.push_ui_stats_update(kbs, time);
+ }
+ DownloadManagerSignal::Finish => {
+ self.stop_and_wait_current_download();
+ return Ok(());
+ }
+ DownloadManagerSignal::Cancel(meta) => {
+ self.manage_cancel_signal(&meta);
+ }
+ _ => {}
+ };
+ }
+ }
+ fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
+ debug!("got signal Queue");
+ let meta = download_agent.metadata();
+
+ debug!("queue metadata: {:?}", meta);
+
+ if self.download_queue.exists(meta.clone()) {
+ warn!("download with same ID already exists");
+ return;
+ }
+
+ download_agent.on_initialised(&self.app_handle);
+ self.download_queue.append(meta.clone());
+ self.download_agent_registry.insert(meta, download_agent);
+
+ self.sender
+ .send(DownloadManagerSignal::UpdateUIQueue)
+ .unwrap();
+ }
+
+ fn manage_go_signal(&mut self) {
+ debug!("got signal Go");
+ if self.download_agent_registry.is_empty() {
+ debug!(
+ "Download agent registry: {:?}",
+ self.download_agent_registry.len()
+ );
+ return;
+ }
+
+ if self.current_download_agent.is_some() {
+ if self.download_queue.read().front().unwrap() == &self.current_download_agent.as_ref().unwrap().metadata() {
+ debug!(
+ "Current download agent: {:?}",
+ self.current_download_agent.as_ref().unwrap().metadata()
+ );
+ return;
+ }
+ }
+
+ debug!("current download queue: {:?}", self.download_queue.read());
+
+ // Should always be Some if the above two statements keep going
+ let agent_data = self.download_queue.read().front().unwrap().clone();
+
+ info!("starting download for {:?}", agent_data);
+
+ let download_agent = self
+ .download_agent_registry
+ .get(&agent_data)
+ .unwrap()
+ .clone();
+
+ self.active_control_flag = Some(download_agent.control_flag());
+ self.current_download_agent = Some(download_agent.clone());
+
+ let sender = self.sender.clone();
+
+ let mut download_thread_lock = self.current_download_thread.lock().unwrap();
+ let app_handle = self.app_handle.clone();
+
+ *download_thread_lock = Some(spawn(move || {
+ match download_agent.download(&app_handle) {
+ // Ok(true) is for completed and exited properly
+ Ok(true) => {
+ debug!("download {:?} has completed", download_agent.metadata());
+ download_agent.on_complete(&app_handle);
+ sender
+ .send(DownloadManagerSignal::Completed(download_agent.metadata()))
+ .unwrap();
+ }
+ // Ok(false) is for incomplete but exited properly
+ Ok(false) => {
+ download_agent.on_incomplete(&app_handle);
+ }
+ Err(e) => {
+ error!("download {:?} has error {}", download_agent.metadata(), &e);
+ download_agent.on_error(&app_handle, e.clone());
+ sender.send(DownloadManagerSignal::Error(e)).unwrap();
+ }
+ }
+ sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
+ }));
+
+ self.set_status(DownloadManagerStatus::Downloading);
+ let active_control_flag = self.active_control_flag.clone().unwrap();
+ active_control_flag.set(DownloadThreadControlFlag::Go);
+ }
+ fn manage_stop_signal(&mut self) {
+ debug!("got signal Stop");
+
+ if let Some(active_control_flag) = self.active_control_flag.clone() {
+ self.set_status(DownloadManagerStatus::Paused);
+ active_control_flag.set(DownloadThreadControlFlag::Stop);
+ }
+ }
+ fn manage_completed_signal(&mut self, meta: DownloadableMetadata) {
+ debug!("got signal Completed");
+ if let Some(interface) = &self.current_download_agent {
+ if interface.metadata() == meta {
+ self.remove_and_cleanup_front_download(&meta);
+ }
+ }
+ self.push_ui_queue_update();
+ self.sender.send(DownloadManagerSignal::Go).unwrap();
+ }
+ fn manage_error_signal(&mut self, error: ApplicationDownloadError) {
+ debug!("got signal Error");
+ if let Some(current_agent) = self.current_download_agent.clone() {
+ current_agent.on_error(&self.app_handle, error.clone());
+
+ self.stop_and_wait_current_download();
+ self.remove_and_cleanup_front_download(¤t_agent.metadata());
+ }
+ self.set_status(DownloadManagerStatus::Error(error));
+ }
+ fn manage_cancel_signal(&mut self, meta: &DownloadableMetadata) {
+ debug!("got signal Cancel");
+
+ if let Some(current_download) = &self.current_download_agent {
+ if ¤t_download.metadata() == meta {
+ self.set_status(DownloadManagerStatus::Paused);
+ current_download.on_cancelled(&self.app_handle);
+ self.stop_and_wait_current_download();
+
+ self.download_queue.pop_front();
+
+ self.cleanup_current_download();
+ debug!("current download queue: {:?}", self.download_queue.read());
+ }
+ // TODO: Collapse these two into a single if statement somehow
+ else if let Some(download_agent) = self.download_agent_registry.get(meta) {
+ let index = self.download_queue.get_by_meta(meta);
+ if let Some(index) = index {
+ download_agent.on_cancelled(&self.app_handle);
+ let _ = self.download_queue.edit().remove(index).unwrap();
+ let removed = self.download_agent_registry.remove(meta);
+ debug!(
+ "removed {:?} from queue {:?}",
+ removed.map(|x| x.metadata()),
+ self.download_queue.read()
+ );
+ }
+ }
+ } else if let Some(download_agent) = self.download_agent_registry.get(meta) {
+ let index = self.download_queue.get_by_meta(meta);
+ if let Some(index) = index {
+ download_agent.on_cancelled(&self.app_handle);
+ let _ = self.download_queue.edit().remove(index).unwrap();
+ let removed = self.download_agent_registry.remove(meta);
+ debug!(
+ "removed {:?} from queue {:?}",
+ removed.map(|x| x.metadata()),
+ self.download_queue.read()
+ );
+ }
+ }
+ self.push_ui_queue_update();
+ }
+ fn push_ui_stats_update(&self, kbs: usize, time: usize) {
+ let event_data = StatsUpdateEvent { speed: kbs, time };
+
+ self.app_handle.emit("update_stats", event_data).unwrap();
+ }
+ fn push_ui_queue_update(&self) {
+ let queue = &self.download_queue.read();
+ let queue_objs = queue
+ .iter()
+ .map(|key| {
+ let val = self.download_agent_registry.get(key).unwrap();
+ QueueUpdateEventQueueData {
+ meta: DownloadableMetadata::clone(key),
+ status: val.status(),
+ progress: val.progress().get_progress(),
+ current: val.progress().sum(),
+ max: val.progress().get_max(),
+ }
+ })
+ .collect();
+
+ let event_data = QueueUpdateEvent { queue: queue_objs };
+ self.app_handle.emit("update_queue", event_data).unwrap();
+ }
+}
diff --git a/src-tauri/src/download_manager/download_thread_control_flag.rs b/src-tauri/src/download_manager/download_thread_control_flag.rs
new file mode 100644
index 0000000..154fcfc
--- /dev/null
+++ b/src-tauri/src/download_manager/download_thread_control_flag.rs
@@ -0,0 +1,49 @@
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+};
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub enum DownloadThreadControlFlag {
+ Stop,
+ Go,
+}
+/// Go => true
+/// Stop => false
+impl From for bool {
+ fn from(value: DownloadThreadControlFlag) -> Self {
+ match value {
+ DownloadThreadControlFlag::Go => true,
+ DownloadThreadControlFlag::Stop => false,
+ }
+ }
+}
+/// true => Go
+/// false => Stop
+impl From for DownloadThreadControlFlag {
+ fn from(value: bool) -> Self {
+ match value {
+ true => DownloadThreadControlFlag::Go,
+ false => DownloadThreadControlFlag::Stop,
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct DownloadThreadControl {
+ inner: Arc,
+}
+
+impl DownloadThreadControl {
+ pub fn new(flag: DownloadThreadControlFlag) -> Self {
+ Self {
+ inner: Arc::new(AtomicBool::new(flag.into())),
+ }
+ }
+ pub fn get(&self) -> DownloadThreadControlFlag {
+ self.inner.load(Ordering::Relaxed).into()
+ }
+ pub fn set(&self, flag: DownloadThreadControlFlag) {
+ self.inner.store(flag.into(), Ordering::Relaxed);
+ }
+}
diff --git a/src-tauri/src/download_manager/downloadable.rs b/src-tauri/src/download_manager/downloadable.rs
new file mode 100644
index 0000000..181b329
--- /dev/null
+++ b/src-tauri/src/download_manager/downloadable.rs
@@ -0,0 +1,23 @@
+use std::sync::Arc;
+
+use tauri::AppHandle;
+
+use crate::error::application_download_error::ApplicationDownloadError;
+
+use super::{
+ download_manager::DownloadStatus, download_thread_control_flag::DownloadThreadControl,
+ downloadable_metadata::DownloadableMetadata, progress_object::ProgressObject,
+};
+
+pub trait Downloadable: Send + Sync {
+ fn download(&self, app_handle: &AppHandle) -> Result;
+ fn progress(&self) -> Arc;
+ fn control_flag(&self) -> DownloadThreadControl;
+ fn status(&self) -> DownloadStatus;
+ fn metadata(&self) -> DownloadableMetadata;
+ fn on_initialised(&self, app_handle: &AppHandle);
+ fn on_error(&self, app_handle: &AppHandle, error: ApplicationDownloadError);
+ fn on_complete(&self, app_handle: &AppHandle);
+ fn on_incomplete(&self, app_handle: &AppHandle);
+ fn on_cancelled(&self, app_handle: &AppHandle);
+}
diff --git a/src-tauri/src/download_manager/downloadable_metadata.rs b/src-tauri/src/download_manager/downloadable_metadata.rs
new file mode 100644
index 0000000..790b669
--- /dev/null
+++ b/src-tauri/src/download_manager/downloadable_metadata.rs
@@ -0,0 +1,26 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy)]
+pub enum DownloadType {
+ Game,
+ Tool,
+ DLC,
+ Mod,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct DownloadableMetadata {
+ pub id: String,
+ pub version: Option,
+ pub download_type: DownloadType,
+}
+impl DownloadableMetadata {
+ pub fn new(id: String, version: Option, download_type: DownloadType) -> Self {
+ Self {
+ id,
+ version,
+ download_type,
+ }
+ }
+}
diff --git a/src-tauri/src/download_manager/internal_error.rs b/src-tauri/src/download_manager/internal_error.rs
new file mode 100644
index 0000000..4864599
--- /dev/null
+++ b/src-tauri/src/download_manager/internal_error.rs
@@ -0,0 +1,27 @@
+use std::{fmt::Display, io, sync::mpsc::SendError};
+
+use serde_with::SerializeDisplay;
+
+#[derive(SerializeDisplay)]
+pub enum InternalError {
+ IOError(io::Error),
+ SignalError(SendError),
+}
+impl Display for InternalError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ InternalError::IOError(error) => write!(f, "{}", error),
+ InternalError::SignalError(send_error) => write!(f, "{}", send_error),
+ }
+ }
+}
+impl From> for InternalError {
+ fn from(value: SendError) -> Self {
+ InternalError::SignalError(value)
+ }
+}
+impl From for InternalError {
+ fn from(value: io::Error) -> Self {
+ InternalError::IOError(value)
+ }
+}
diff --git a/src-tauri/src/download_manager/mod.rs b/src-tauri/src/download_manager/mod.rs
new file mode 100644
index 0000000..0bac198
--- /dev/null
+++ b/src-tauri/src/download_manager/mod.rs
@@ -0,0 +1,10 @@
+pub mod commands;
+pub mod download_manager;
+pub mod download_manager_builder;
+pub mod download_thread_control_flag;
+pub mod downloadable;
+pub mod downloadable_metadata;
+pub mod internal_error;
+pub mod progress_object;
+pub mod queue;
+pub mod rolling_progress_updates;
diff --git a/src-tauri/src/download_manager/progress_object.rs b/src-tauri/src/download_manager/progress_object.rs
new file mode 100644
index 0000000..fc0907b
--- /dev/null
+++ b/src-tauri/src/download_manager/progress_object.rs
@@ -0,0 +1,155 @@
+use std::{
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ mpsc::Sender,
+ Arc, Mutex,
+ },
+ time::{Duration, Instant},
+};
+
+use atomic_instant_full::AtomicInstant;
+use throttle_my_fn::throttle;
+
+use super::{
+ download_manager::DownloadManagerSignal, rolling_progress_updates::RollingProgressWindow,
+};
+
+#[derive(Clone)]
+pub struct ProgressObject {
+ max: Arc>,
+ progress_instances: Arc>>>,
+ start: Arc>,
+ sender: Sender,
+ //last_update: Arc>,
+ last_update_time: Arc,
+ bytes_last_update: Arc,
+ rolling: RollingProgressWindow<250>,
+}
+
+pub struct ProgressHandle {
+ progress: Arc,
+ progress_object: Arc,
+}
+
+impl ProgressHandle {
+ pub fn new(progress: Arc, progress_object: Arc) -> Self {
+ Self {
+ progress,
+ progress_object,
+ }
+ }
+ pub fn set(&self, amount: usize) {
+ self.progress.store(amount, Ordering::Relaxed);
+ }
+ pub fn add(&self, amount: usize) {
+ self.progress
+ .fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
+ calculate_update(&self.progress_object);
+ }
+ pub fn skip(&self, amount: usize) {
+ self.progress
+ .fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
+ // Offset the bytes at last offset by this amount
+ self.progress_object
+ .bytes_last_update
+ .fetch_add(amount, Ordering::Relaxed);
+ // Dont' fire update
+ }
+}
+
+impl ProgressObject {
+ pub fn new(max: usize, length: usize, sender: Sender) -> Self {
+ let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
+ // TODO: consolidate this calculation with the set_max function below
+ Self {
+ max: Arc::new(Mutex::new(max)),
+ progress_instances: Arc::new(arr),
+ start: Arc::new(Mutex::new(Instant::now())),
+ sender,
+
+ last_update_time: Arc::new(AtomicInstant::now()),
+ bytes_last_update: Arc::new(AtomicUsize::new(0)),
+ rolling: RollingProgressWindow::new(),
+ }
+ }
+
+ pub fn set_time_now(&self) {
+ *self.start.lock().unwrap() = Instant::now();
+ }
+ pub fn sum(&self) -> usize {
+ self.progress_instances
+ .lock()
+ .unwrap()
+ .iter()
+ .map(|instance| instance.load(Ordering::Relaxed))
+ .sum()
+ }
+ pub fn get_max(&self) -> usize {
+ *self.max.lock().unwrap()
+ }
+ pub fn set_max(&self, new_max: usize) {
+ *self.max.lock().unwrap() = new_max;
+ }
+ pub fn set_size(&self, length: usize) {
+ *self.progress_instances.lock().unwrap() =
+ (0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect();
+ }
+ pub fn get_progress(&self) -> f64 {
+ self.sum() as f64 / self.get_max() as f64
+ }
+ pub fn get(&self, index: usize) -> Arc {
+ self.progress_instances.lock().unwrap()[index].clone()
+ }
+ fn update_window(&self, kilobytes_per_second: usize) {
+ self.rolling.update(kilobytes_per_second);
+ }
+}
+
+#[throttle(1, Duration::from_millis(20))]
+pub fn calculate_update(progress: &ProgressObject) {
+ let last_update_time = progress
+ .last_update_time
+ .swap(Instant::now(), Ordering::SeqCst);
+ let time_since_last_update = Instant::now().duration_since(last_update_time).as_millis();
+
+ let current_bytes_downloaded = progress.sum();
+ let max = progress.get_max();
+ let bytes_at_last_update = progress
+ .bytes_last_update
+ .swap(current_bytes_downloaded, Ordering::Relaxed);
+
+ let bytes_since_last_update = current_bytes_downloaded - bytes_at_last_update;
+
+ let kilobytes_per_second = bytes_since_last_update / (time_since_last_update as usize).max(1);
+
+ let bytes_remaining = max - current_bytes_downloaded; // bytes
+
+ progress.update_window(kilobytes_per_second);
+ push_update(progress, bytes_remaining);
+}
+
+#[throttle(1, Duration::from_millis(500))]
+pub fn push_update(progress: &ProgressObject, bytes_remaining: usize) {
+ let average_speed = progress.rolling.get_average();
+ let time_remaining = (bytes_remaining / 1000) / average_speed.max(1);
+
+ update_ui(progress, average_speed, time_remaining);
+ update_queue(progress);
+}
+
+fn update_ui(progress_object: &ProgressObject, kilobytes_per_second: usize, time_remaining: usize) {
+ progress_object
+ .sender
+ .send(DownloadManagerSignal::UpdateUIStats(
+ kilobytes_per_second,
+ time_remaining,
+ ))
+ .unwrap();
+}
+
+fn update_queue(progress: &ProgressObject) {
+ progress
+ .sender
+ .send(DownloadManagerSignal::UpdateUIQueue)
+ .unwrap();
+}
diff --git a/src-tauri/src/download_manager/queue.rs b/src-tauri/src/download_manager/queue.rs
new file mode 100644
index 0000000..f3e9493
--- /dev/null
+++ b/src-tauri/src/download_manager/queue.rs
@@ -0,0 +1,80 @@
+use std::{
+ collections::VecDeque,
+ sync::{Arc, Mutex, MutexGuard},
+};
+
+use super::downloadable_metadata::DownloadableMetadata;
+
+#[derive(Clone)]
+pub struct Queue {
+ inner: Arc>>,
+}
+
+#[allow(dead_code)]
+impl Default for Queue {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl Queue {
+ pub fn new() -> Self {
+ Self {
+ inner: Arc::new(Mutex::new(VecDeque::new())),
+ }
+ }
+ pub fn read(&self) -> VecDeque {
+ self.inner.lock().unwrap().clone()
+ }
+ pub fn edit(&self) -> MutexGuard<'_, VecDeque> {
+ self.inner.lock().unwrap()
+ }
+ pub fn pop_front(&self) -> Option {
+ self.edit().pop_front()
+ }
+ pub fn is_empty(&self) -> bool {
+ self.inner.lock().unwrap().len() == 0
+ }
+ pub fn exists(&self, meta: DownloadableMetadata) -> bool {
+ self.read().contains(&meta)
+ }
+ /// Either inserts `interface` at the specified index, or appends to
+ /// the back of the deque if index is greater than the length of the deque
+ pub fn insert(&self, interface: DownloadableMetadata, index: usize) {
+ if self.read().len() > index {
+ self.append(interface);
+ } else {
+ self.edit().insert(index, interface);
+ }
+ }
+ pub fn append(&self, interface: DownloadableMetadata) {
+ self.edit().push_back(interface);
+ }
+ pub fn pop_front_if_equal(&self, meta: &DownloadableMetadata) -> Option {
+ let mut queue = self.edit();
+ let front = queue.front()?;
+ if front == meta {
+ return queue.pop_front();
+ }
+ None
+ }
+ pub fn get_by_meta(&self, meta: &DownloadableMetadata) -> Option {
+ self.read().iter().position(|data| data == meta)
+ }
+ pub fn move_to_index_by_meta(
+ &self,
+ meta: &DownloadableMetadata,
+ new_index: usize,
+ ) -> Result<(), ()> {
+ let index = match self.get_by_meta(meta) {
+ Some(index) => index,
+ None => return Err(()),
+ };
+ let existing = match self.edit().remove(index) {
+ Some(existing) => existing,
+ None => return Err(()),
+ };
+ self.edit().insert(new_index, existing);
+ Ok(())
+ }
+}
diff --git a/src-tauri/src/download_manager/rolling_progress_updates.rs b/src-tauri/src/download_manager/rolling_progress_updates.rs
new file mode 100644
index 0000000..2239b9a
--- /dev/null
+++ b/src-tauri/src/download_manager/rolling_progress_updates.rs
@@ -0,0 +1,33 @@
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+};
+
+#[derive(Clone)]
+pub struct RollingProgressWindow {
+ window: Arc<[AtomicUsize; S]>,
+ current: Arc,
+}
+impl RollingProgressWindow {
+ pub fn new() -> Self {
+ Self {
+ window: Arc::new([(); S].map(|_| AtomicUsize::new(0))),
+ current: Arc::new(AtomicUsize::new(0)),
+ }
+ }
+ pub fn update(&self, kilobytes_per_second: usize) {
+ let index = self.current.fetch_add(1, Ordering::SeqCst);
+ let current = &self.window[index % S];
+ current.store(kilobytes_per_second, Ordering::SeqCst);
+ }
+ pub fn get_average(&self) -> usize {
+ let current = self.current.load(Ordering::SeqCst);
+ self.window
+ .iter()
+ .enumerate()
+ .filter(|(i, _)| i < ¤t)
+ .map(|(_, x)| x.load(Ordering::Relaxed))
+ .sum::()
+ / S
+ }
+}
diff --git a/src-tauri/src/error/application_download_error.rs b/src-tauri/src/error/application_download_error.rs
new file mode 100644
index 0000000..d68bd71
--- /dev/null
+++ b/src-tauri/src/error/application_download_error.rs
@@ -0,0 +1,32 @@
+use std::{
+ fmt::{Display, Formatter},
+ io,
+};
+
+use serde_with::SerializeDisplay;
+
+use super::{remote_access_error::RemoteAccessError, setup_error::SetupError};
+
+// TODO: Rename / separate from downloads
+#[derive(Debug, Clone, SerializeDisplay)]
+pub enum ApplicationDownloadError {
+ Communication(RemoteAccessError),
+ Checksum,
+ Setup(SetupError),
+ Lock,
+ IoError(io::ErrorKind),
+ DownloadError,
+}
+
+impl Display for ApplicationDownloadError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ApplicationDownloadError::Communication(error) => write!(f, "{}", error),
+ ApplicationDownloadError::Setup(error) => write!(f, "an error occurred while setting up the download: {}", error),
+ ApplicationDownloadError::Lock => write!(f, "failed to acquire lock. Something has gone very wrong internally. Please restart the application"),
+ ApplicationDownloadError::Checksum => write!(f, "checksum failed to validate for download"),
+ ApplicationDownloadError::IoError(error) => write!(f, "{}", error),
+ ApplicationDownloadError::DownloadError => write!(f, "download failed. See Download Manager status for specific error"),
+ }
+ }
+}
diff --git a/src-tauri/src/error/drop_server_error.rs b/src-tauri/src/error/drop_server_error.rs
new file mode 100644
index 0000000..ab42263
--- /dev/null
+++ b/src-tauri/src/error/drop_server_error.rs
@@ -0,0 +1,10 @@
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct DropServerError {
+ pub status_code: usize,
+ pub status_message: String,
+ pub message: String,
+ pub url: String,
+}
diff --git a/src-tauri/src/error/library_error.rs b/src-tauri/src/error/library_error.rs
new file mode 100644
index 0000000..c13dd23
--- /dev/null
+++ b/src-tauri/src/error/library_error.rs
@@ -0,0 +1,19 @@
+use std::fmt::Display;
+
+use serde_with::SerializeDisplay;
+
+#[derive(SerializeDisplay)]
+pub enum LibraryError {
+ MetaNotFound(String),
+}
+impl Display for LibraryError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ LibraryError::MetaNotFound(id) => write!(
+ f,
+ "Could not locate any installed version of game ID {} in the database",
+ id
+ ),
+ }
+ }
+}
diff --git a/src-tauri/src/error/mod.rs b/src-tauri/src/error/mod.rs
new file mode 100644
index 0000000..89b74ae
--- /dev/null
+++ b/src-tauri/src/error/mod.rs
@@ -0,0 +1,6 @@
+pub mod application_download_error;
+pub mod drop_server_error;
+pub mod library_error;
+pub mod process_error;
+pub mod remote_access_error;
+pub mod setup_error;
diff --git a/src-tauri/src/error/process_error.rs b/src-tauri/src/error/process_error.rs
new file mode 100644
index 0000000..8afc9dc
--- /dev/null
+++ b/src-tauri/src/error/process_error.rs
@@ -0,0 +1,31 @@
+use std::{fmt::Display, io::Error};
+
+use serde_with::SerializeDisplay;
+
+#[derive(SerializeDisplay)]
+pub enum ProcessError {
+ SetupRequired,
+ NotInstalled,
+ AlreadyRunning,
+ NotDownloaded,
+ InvalidID,
+ InvalidVersion,
+ IOError(Error),
+ InvalidPlatform,
+}
+
+impl Display for ProcessError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let s = match self {
+ ProcessError::SetupRequired => "Game not set up",
+ ProcessError::NotInstalled => "Game not installed",
+ ProcessError::AlreadyRunning => "Game already running",
+ ProcessError::NotDownloaded => "Game not downloaded",
+ ProcessError::InvalidID => "Invalid Game ID",
+ ProcessError::InvalidVersion => "Invalid Game version",
+ ProcessError::IOError(error) => &error.to_string(),
+ ProcessError::InvalidPlatform => "This Game cannot be played on the current platform",
+ };
+ write!(f, "{}", s)
+ }
+}
diff --git a/src-tauri/src/error/remote_access_error.rs b/src-tauri/src/error/remote_access_error.rs
new file mode 100644
index 0000000..32572b2
--- /dev/null
+++ b/src-tauri/src/error/remote_access_error.rs
@@ -0,0 +1,69 @@
+use std::{
+ error::Error,
+ fmt::{Display, Formatter},
+ sync::Arc,
+};
+
+use http::StatusCode;
+use serde_with::SerializeDisplay;
+use url::ParseError;
+
+use super::drop_server_error::DropServerError;
+
+#[derive(Debug, Clone, SerializeDisplay)]
+pub enum RemoteAccessError {
+ FetchError(Arc),
+ ParsingError(ParseError),
+ InvalidEndpoint,
+ HandshakeFailed(String),
+ GameNotFound,
+ InvalidResponse(DropServerError),
+ InvalidRedirect,
+ ManifestDownloadFailed(StatusCode, String),
+ OutOfSync,
+ Generic(String),
+}
+
+impl Display for RemoteAccessError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ RemoteAccessError::FetchError(error) => write!(
+ f,
+ "{}: {}",
+ error,
+ error
+ .source()
+ .map(|e| e.to_string())
+ .or_else(|| Some("Unknown error".to_string()))
+ .unwrap()
+ ),
+ RemoteAccessError::ParsingError(parse_error) => {
+ write!(f, "{}", parse_error)
+ }
+ RemoteAccessError::InvalidEndpoint => write!(f, "invalid drop endpoint"),
+ RemoteAccessError::HandshakeFailed(message) => write!(f, "failed to complete handshake: {}", message),
+ RemoteAccessError::GameNotFound => write!(f, "could not find game on server"),
+ RemoteAccessError::InvalidResponse(error) => write!(f, "server returned an invalid response: {} {}", error.status_code, error.status_message),
+ RemoteAccessError::InvalidRedirect => write!(f, "server redirect was invalid"),
+ RemoteAccessError::ManifestDownloadFailed(status, response) => write!(
+ f,
+ "failed to download game manifest: {} {}",
+ status, response
+ ),
+ RemoteAccessError::OutOfSync => write!(f, "server's and client's time are out of sync. Please ensure they are within at least 30 seconds of each other"),
+ RemoteAccessError::Generic(message) => write!(f, "{}", message),
+ }
+ }
+}
+
+impl From for RemoteAccessError {
+ fn from(err: reqwest::Error) -> Self {
+ RemoteAccessError::FetchError(Arc::new(err))
+ }
+}
+impl From for RemoteAccessError {
+ fn from(err: ParseError) -> Self {
+ RemoteAccessError::ParsingError(err)
+ }
+}
+impl std::error::Error for RemoteAccessError {}
diff --git a/src-tauri/src/error/setup_error.rs b/src-tauri/src/error/setup_error.rs
new file mode 100644
index 0000000..bd76ce5
--- /dev/null
+++ b/src-tauri/src/error/setup_error.rs
@@ -0,0 +1,14 @@
+use std::fmt::{Display, Formatter};
+
+#[derive(Debug, Clone)]
+pub enum SetupError {
+ Context,
+}
+
+impl Display for SetupError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ match self {
+ SetupError::Context => write!(f, "failed to generate contexts for download"),
+ }
+ }
+}
diff --git a/src-tauri/src/games/commands.rs b/src-tauri/src/games/commands.rs
new file mode 100644
index 0000000..8f1d652
--- /dev/null
+++ b/src-tauri/src/games/commands.rs
@@ -0,0 +1,53 @@
+use std::sync::Mutex;
+
+use tauri::AppHandle;
+
+use crate::{
+ database::db::GameVersion, error::{library_error::LibraryError, remote_access_error::RemoteAccessError}, games::library::{get_current_meta, uninstall_game_logic}, AppState
+};
+
+use super::{
+ library::{
+ fetch_game_logic, fetch_game_verion_options_logic, fetch_library_logic, FetchGameStruct,
+ Game,
+ },
+ state::{GameStatusManager, GameStatusWithTransient},
+};
+
+#[tauri::command]
+pub fn fetch_library(app: AppHandle) -> Result, RemoteAccessError> {
+ fetch_library_logic(app)
+}
+
+#[tauri::command]
+pub fn fetch_game(
+ game_id: String,
+ app: tauri::AppHandle,
+) -> Result {
+ fetch_game_logic(game_id, app)
+}
+
+#[tauri::command]
+pub fn fetch_game_status(id: String) -> GameStatusWithTransient {
+ GameStatusManager::fetch_state(&id)
+}
+
+#[tauri::command]
+pub fn uninstall_game(game_id: String, app_handle: AppHandle) -> Result<(), LibraryError> {
+ let meta = match get_current_meta(&game_id) {
+ Some(data) => data,
+ None => return Err(LibraryError::MetaNotFound(game_id)),
+ };
+ println!("{:?}", meta);
+ uninstall_game_logic(meta, &app_handle);
+
+ Ok(())
+}
+
+#[tauri::command]
+pub fn fetch_game_verion_options(
+ game_id: String,
+ state: tauri::State<'_, Mutex>,
+) -> Result, RemoteAccessError> {
+ fetch_game_verion_options_logic(game_id, state)
+}
diff --git a/src-tauri/src/games/downloads/commands.rs b/src-tauri/src/games/downloads/commands.rs
new file mode 100644
index 0000000..67b1359
--- /dev/null
+++ b/src-tauri/src/games/downloads/commands.rs
@@ -0,0 +1,32 @@
+use std::sync::{Arc, Mutex};
+
+use crate::{
+ download_manager::{
+ download_manager::DownloadManagerSignal, downloadable::Downloadable,
+ internal_error::InternalError,
+ },
+ AppState,
+};
+
+use super::download_agent::GameDownloadAgent;
+
+#[tauri::command]
+pub fn download_game(
+ game_id: String,
+ game_version: String,
+ install_dir: usize,
+ state: tauri::State<'_, Mutex>,
+) -> Result<(), InternalError> {
+ let sender = state.lock().unwrap().download_manager.get_sender();
+ let game_download_agent = Arc::new(Box::new(GameDownloadAgent::new(
+ game_id,
+ game_version,
+ install_dir,
+ sender,
+ )) as Box);
+ Ok(state
+ .lock()
+ .unwrap()
+ .download_manager
+ .queue_download(game_download_agent)?)
+}
diff --git a/src-tauri/src/games/downloads/download_agent.rs b/src-tauri/src/games/downloads/download_agent.rs
new file mode 100644
index 0000000..d4fb1cd
--- /dev/null
+++ b/src-tauri/src/games/downloads/download_agent.rs
@@ -0,0 +1,407 @@
+use crate::auth::generate_authorization_header;
+use crate::database::db::{
+ borrow_db_checked, set_game_status, ApplicationTransientStatus, DatabaseImpls,
+ GameDownloadStatus,
+};
+use crate::download_manager::download_manager::{DownloadManagerSignal, DownloadStatus};
+use crate::download_manager::download_thread_control_flag::{
+ DownloadThreadControl, DownloadThreadControlFlag,
+};
+use crate::download_manager::downloadable::Downloadable;
+use crate::download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata};
+use crate::download_manager::progress_object::{ProgressHandle, ProgressObject};
+use crate::error::application_download_error::ApplicationDownloadError;
+use crate::error::remote_access_error::RemoteAccessError;
+use crate::games::downloads::manifest::{DropDownloadContext, DropManifest};
+use crate::games::library::{on_game_complete, push_game_update, GameUpdateEvent};
+use crate::remote::requests::make_request;
+use crate::DB;
+use log::{debug, error, info};
+use rayon::ThreadPoolBuilder;
+use slice_deque::SliceDeque;
+use std::fs::{create_dir_all, File};
+use std::path::Path;
+use std::sync::mpsc::Sender;
+use std::sync::{Arc, Mutex};
+use std::time::Instant;
+use tauri::{AppHandle, Emitter};
+use urlencoding::encode;
+
+#[cfg(target_os = "linux")]
+use rustix::fs::{fallocate, FallocateFlags};
+
+use super::download_logic::download_game_chunk;
+use super::stored_manifest::StoredManifest;
+
+pub struct GameDownloadAgent {
+ pub id: String,
+ pub version: String,
+ pub control_flag: DownloadThreadControl,
+ contexts: Mutex>,
+ completed_contexts: Mutex>,
+ pub manifest: Mutex>,
+ pub progress: Arc,
+ sender: Sender,
+ pub stored_manifest: StoredManifest,
+ status: Mutex,
+}
+
+impl GameDownloadAgent {
+ pub fn new(
+ id: String,
+ version: String,
+ target_download_dir: usize,
+ sender: Sender,
+ ) -> Self {
+ // Don't run by default
+ let control_flag = DownloadThreadControl::new(DownloadThreadControlFlag::Stop);
+
+ let db_lock = borrow_db_checked();
+ let base_dir = db_lock.applications.install_dirs[target_download_dir].clone();
+ drop(db_lock);
+
+ let base_dir_path = Path::new(&base_dir);
+ let data_base_dir_path = base_dir_path.join(id.clone());
+
+ let stored_manifest =
+ StoredManifest::generate(id.clone(), version.clone(), data_base_dir_path.clone());
+
+ Self {
+ id,
+ version,
+ control_flag,
+ manifest: Mutex::new(None),
+ contexts: Mutex::new(Vec::new()),
+ completed_contexts: Mutex::new(SliceDeque::new()),
+ progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
+ sender,
+ stored_manifest,
+ status: Mutex::new(DownloadStatus::Queued),
+ }
+ }
+
+ // Blocking
+ pub fn setup_download(&self) -> Result<(), ApplicationDownloadError> {
+ self.ensure_manifest_exists()?;
+
+ self.ensure_contexts()?;
+
+ self.control_flag.set(DownloadThreadControlFlag::Go);
+
+ Ok(())
+ }
+
+ // Blocking
+ pub fn download(&self, app_handle: &AppHandle) -> Result {
+ self.setup_download()?;
+ self.set_progress_object_params();
+ let timer = Instant::now();
+ push_game_update(
+ app_handle,
+ &self.metadata().id,
+ (
+ None,
+ Some(ApplicationTransientStatus::Downloading {
+ version_name: self.version.clone(),
+ }),
+ ),
+ );
+ let res = self
+ .run()
+ .map_err(|_| ApplicationDownloadError::DownloadError);
+
+ debug!(
+ "{} took {}ms to download",
+ self.id,
+ timer.elapsed().as_millis()
+ );
+ res
+ }
+
+ pub fn ensure_manifest_exists(&self) -> Result<(), ApplicationDownloadError> {
+ if self.manifest.lock().unwrap().is_some() {
+ return Ok(());
+ }
+
+ self.download_manifest()
+ }
+
+ fn download_manifest(&self) -> Result<(), ApplicationDownloadError> {
+ let header = generate_authorization_header();
+ let client = reqwest::blocking::Client::new();
+ let response = make_request(
+ &client,
+ &["/api/v1/client/game/manifest"],
+ &[("id", &self.id), ("version", &self.version)],
+ |f| f.header("Authorization", header),
+ )
+ .map_err(|e| ApplicationDownloadError::Communication(e))?
+ .send()
+ .map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
+
+ if response.status() != 200 {
+ return Err(ApplicationDownloadError::Communication(
+ RemoteAccessError::ManifestDownloadFailed(
+ response.status(),
+ response.text().unwrap(),
+ ),
+ ));
+ }
+
+ let manifest_download: DropManifest = response.json().unwrap();
+
+ if let Ok(mut manifest) = self.manifest.lock() {
+ *manifest = Some(manifest_download);
+ return Ok(());
+ }
+
+ Err(ApplicationDownloadError::Lock)
+ }
+
+ fn set_progress_object_params(&self) {
+ // Avoid re-setting it
+ if self.progress.get_max() != 0 {
+ return;
+ }
+
+ let contexts = self.contexts.lock().unwrap();
+
+ let length = contexts.len();
+
+ let chunk_count = contexts.iter().map(|chunk| chunk.length).sum();
+
+ self.progress.set_max(chunk_count);
+ self.progress.set_size(length);
+ self.progress.set_time_now();
+ }
+
+ pub fn ensure_contexts(&self) -> Result<(), ApplicationDownloadError> {
+ if !self.contexts.lock().unwrap().is_empty() {
+ return Ok(());
+ }
+
+ self.generate_contexts()?;
+ Ok(())
+ }
+
+ pub fn generate_contexts(&self) -> Result<(), ApplicationDownloadError> {
+ let manifest = self.manifest.lock().unwrap().clone().unwrap();
+ let game_id = self.id.clone();
+
+ let mut contexts = Vec::new();
+ let base_path = Path::new(&self.stored_manifest.base_path);
+ create_dir_all(base_path).unwrap();
+
+ {
+ let mut completed_contexts_lock = self.completed_contexts.lock().unwrap();
+ completed_contexts_lock.clear();
+ completed_contexts_lock
+ .extend_from_slice(&self.stored_manifest.get_completed_contexts());
+ }
+
+ for (raw_path, chunk) in manifest {
+ let path = base_path.join(Path::new(&raw_path));
+
+ let container = path.parent().unwrap();
+ create_dir_all(container).unwrap();
+
+ let file = File::create(path.clone()).unwrap();
+ let mut running_offset = 0;
+
+ for (index, length) in chunk.lengths.iter().enumerate() {
+ contexts.push(DropDownloadContext {
+ file_name: raw_path.to_string(),
+ version: chunk.version_name.to_string(),
+ offset: running_offset,
+ index,
+ game_id: game_id.to_string(),
+ path: path.clone(),
+ checksum: chunk.checksums[index].clone(),
+ length: *length,
+ permissions: chunk.permissions,
+ });
+ running_offset += *length as u64;
+ }
+
+ #[cfg(target_os = "linux")]
+ if running_offset > 0 {
+ let _ = fallocate(file, FallocateFlags::empty(), 0, running_offset);
+ }
+ }
+ *self.contexts.lock().unwrap() = contexts;
+
+ Ok(())
+ }
+
+ // TODO: Change return value on Err
+ pub fn run(&self) -> Result {
+ let max_download_threads = borrow_db_checked().settings.max_download_threads;
+
+ debug!(
+ "downloading game: {} with {} threads",
+ self.id, max_download_threads
+ );
+ let pool = ThreadPoolBuilder::new()
+ .num_threads(max_download_threads)
+ .build()
+ .unwrap();
+
+ let completed_indexes = Arc::new(boxcar::Vec::new());
+ let completed_indexes_loop_arc = completed_indexes.clone();
+
+ let contexts = self.contexts.lock().unwrap();
+ pool.scope(|scope| {
+ let client = &reqwest::blocking::Client::new();
+ for (index, context) in contexts.iter().enumerate() {
+ let client = client.clone();
+ let completed_indexes = completed_indexes_loop_arc.clone();
+
+ let progress = self.progress.get(index);
+ let progress_handle = ProgressHandle::new(progress, self.progress.clone());
+
+ // If we've done this one already, skip it
+ if self.completed_contexts.lock().unwrap().contains(&index) {
+ progress_handle.skip(context.length);
+ continue;
+ }
+
+ let sender = self.sender.clone();
+
+ let request = match make_request(
+ &client,
+ &["/api/v1/client/chunk"],
+ &[
+ ("id", &context.game_id),
+ ("version", &context.version),
+ ("name", &context.file_name),
+ ("chunk", &context.index.to_string()),
+ ],
+ |r| r.header("Authorization", generate_authorization_header()),
+ ) {
+ Ok(request) => request,
+ Err(e) => {
+ sender.send(DownloadManagerSignal::Error(ApplicationDownloadError::Communication(e))).unwrap();
+ continue;
+ },
+ };
+
+ scope.spawn(move |_| {
+ match download_game_chunk(context, &self.control_flag, progress_handle, request)
+ {
+ Ok(res) => {
+ if res {
+ completed_indexes.push(index);
+ }
+ }
+ Err(e) => {
+ error!("{}", e);
+ sender.send(DownloadManagerSignal::Error(e)).unwrap();
+ }
+ }
+ });
+ }
+ });
+
+ let newly_completed = completed_indexes.to_owned();
+
+ let completed_lock_len = {
+ let mut completed_contexts_lock = self.completed_contexts.lock().unwrap();
+ for (_, item) in newly_completed.iter() {
+ completed_contexts_lock.push_front(*item);
+ }
+
+ completed_contexts_lock.len()
+ };
+
+ // If we're not out of contexts, we're not done, so we don't fire completed
+ if completed_lock_len != contexts.len() {
+ info!(
+ "download agent for {} exited without completing ({}/{})",
+ self.id.clone(),
+ completed_lock_len,
+ contexts.len(),
+ );
+ self.stored_manifest
+ .set_completed_contexts(self.completed_contexts.lock().unwrap().as_slice());
+ self.stored_manifest.write();
+ return Ok(false);
+ }
+
+ // We've completed
+ self.sender
+ .send(DownloadManagerSignal::Completed(self.metadata()))
+ .unwrap();
+
+ Ok(true)
+ }
+}
+
+impl Downloadable for GameDownloadAgent {
+ fn download(&self, app_handle: &AppHandle) -> Result {
+ *self.status.lock().unwrap() = DownloadStatus::Downloading;
+ self.download(app_handle)
+ }
+
+ fn progress(&self) -> Arc {
+ self.progress.clone()
+ }
+
+ fn control_flag(&self) -> DownloadThreadControl {
+ self.control_flag.clone()
+ }
+
+ fn metadata(&self) -> DownloadableMetadata {
+ DownloadableMetadata {
+ id: self.id.clone(),
+ version: Some(self.version.clone()),
+ download_type: DownloadType::Game,
+ }
+ }
+
+ fn on_initialised(&self, _app_handle: &tauri::AppHandle) {
+ *self.status.lock().unwrap() = DownloadStatus::Queued;
+ }
+
+ fn on_error(&self, app_handle: &tauri::AppHandle, error: ApplicationDownloadError) {
+ *self.status.lock().unwrap() = DownloadStatus::Error;
+ app_handle
+ .emit("download_error", error.to_string())
+ .unwrap();
+
+ error!("error while managing download: {}", error);
+
+ set_game_status(app_handle, self.metadata(), |db_handle, meta| {
+ db_handle.applications.transient_statuses.remove(meta);
+ });
+ }
+
+ fn on_complete(&self, app_handle: &tauri::AppHandle) {
+ on_game_complete(
+ &self.metadata(),
+ self.stored_manifest.base_path.to_string_lossy().to_string(),
+ app_handle,
+ )
+ .unwrap();
+ }
+
+ // TODO: fix this function. It doesn't restart the download properly, nor does it reset the state properly
+ fn on_incomplete(&self, app_handle: &tauri::AppHandle) {
+ let meta = self.metadata();
+ *self.status.lock().unwrap() = DownloadStatus::Queued;
+ app_handle
+ .emit(
+ &format!("update_game/{}", meta.id),
+ GameUpdateEvent {
+ game_id: meta.id.clone(),
+ status: (Some(GameDownloadStatus::Remote {}), None),
+ },
+ )
+ .unwrap();
+ }
+
+ fn on_cancelled(&self, _app_handle: &tauri::AppHandle) {}
+
+ fn status(&self) -> DownloadStatus {
+ self.status.lock().unwrap().clone()
+ }
+}
diff --git a/src-tauri/src/games/downloads/download_logic.rs b/src-tauri/src/games/downloads/download_logic.rs
new file mode 100644
index 0000000..70e5b7e
--- /dev/null
+++ b/src-tauri/src/games/downloads/download_logic.rs
@@ -0,0 +1,189 @@
+use crate::download_manager::download_thread_control_flag::{
+ DownloadThreadControl, DownloadThreadControlFlag,
+};
+use crate::download_manager::progress_object::ProgressHandle;
+use crate::error::application_download_error::ApplicationDownloadError;
+use crate::error::remote_access_error::RemoteAccessError;
+use crate::games::downloads::manifest::DropDownloadContext;
+use log::warn;
+use md5::{Context, Digest};
+use reqwest::blocking::{RequestBuilder, Response};
+
+use std::fs::{set_permissions, Permissions};
+use std::io::{ErrorKind, Read};
+#[cfg(unix)]
+use std::os::unix::fs::PermissionsExt;
+use std::{
+ fs::{File, OpenOptions},
+ io::{self, BufWriter, Seek, SeekFrom, Write},
+ path::PathBuf,
+};
+
+pub struct DropWriter {
+ hasher: Context,
+ destination: W,
+}
+impl DropWriter {
+ fn new(path: PathBuf) -> Self {
+ Self {
+ destination: OpenOptions::new().write(true).open(path).unwrap(),
+ hasher: Context::new(),
+ }
+ }
+
+ fn finish(mut self) -> io::Result {
+ self.flush().unwrap();
+ Ok(self.hasher.compute())
+ }
+}
+// Write automatically pushes to file and hasher
+impl Write for DropWriter {
+ fn write(&mut self, buf: &[u8]) -> io::Result {
+ self.hasher.write_all(buf).map_err(|e| {
+ io::Error::new(
+ ErrorKind::Other,
+ format!("Unable to write to hasher: {}", e),
+ )
+ })?;
+ self.destination.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.hasher.flush()?;
+ self.destination.flush()
+ }
+}
+// Seek moves around destination output
+impl Seek for DropWriter {
+ fn seek(&mut self, pos: SeekFrom) -> io::Result {
+ self.destination.seek(pos)
+ }
+}
+
+pub struct DropDownloadPipeline<'a, R: Read, W: Write> {
+ pub source: R,
+ pub destination: DropWriter,
+ pub control_flag: &'a DownloadThreadControl,
+ pub progress: ProgressHandle,
+ pub size: usize,
+}
+impl<'a> DropDownloadPipeline<'a, Response, File> {
+ fn new(
+ source: Response,
+ destination: DropWriter,
+ control_flag: &'a DownloadThreadControl,
+ progress: ProgressHandle,
+ size: usize,
+ ) -> Self {
+ Self {
+ source,
+ destination,
+ control_flag,
+ progress,
+ size,
+ }
+ }
+
+ fn copy(&mut self) -> Result {
+ let copy_buf_size = 512;
+ let mut copy_buf = vec![0; copy_buf_size];
+ let mut buf_writer = BufWriter::with_capacity(1024 * 1024, &mut self.destination);
+
+ let mut current_size = 0;
+ loop {
+ if self.control_flag.get() == DownloadThreadControlFlag::Stop {
+ return Ok(false);
+ }
+
+ let bytes_read = self.source.read(&mut copy_buf)?;
+ current_size += bytes_read;
+
+ buf_writer.write_all(©_buf[0..bytes_read])?;
+ self.progress.add(bytes_read);
+
+ if current_size == self.size {
+ break;
+ }
+ }
+
+ Ok(true)
+ }
+
+ fn finish(self) -> Result {
+ let checksum = self.destination.finish()?;
+ Ok(checksum)
+ }
+}
+
+pub fn download_game_chunk(
+ ctx: &DropDownloadContext,
+ control_flag: &DownloadThreadControl,
+ progress: ProgressHandle,
+ request: RequestBuilder,
+) -> Result {
+ // If we're paused
+ if control_flag.get() == DownloadThreadControlFlag::Stop {
+ progress.set(0);
+ return Ok(false);
+ }
+
+ let response = request
+ .send()
+ .map_err(|e| ApplicationDownloadError::Communication(e.into()))?;
+
+ if response.status() != 200 {
+ let err = response.json().unwrap();
+ return Err(ApplicationDownloadError::Communication(
+ RemoteAccessError::InvalidResponse(err),
+ ));
+ }
+
+ let mut destination = DropWriter::new(ctx.path.clone());
+
+ if ctx.offset != 0 {
+ destination
+ .seek(SeekFrom::Start(ctx.offset))
+ .expect("Failed to seek to file offset");
+ }
+
+ let content_length = response.content_length();
+ if content_length.is_none() {
+ warn!("recieved 0 length content from server");
+ return Err(ApplicationDownloadError::Communication(
+ RemoteAccessError::InvalidResponse(response.json().unwrap()),
+ ));
+ }
+
+ let mut pipeline = DropDownloadPipeline::new(
+ response,
+ destination,
+ control_flag,
+ progress,
+ content_length.unwrap().try_into().unwrap(),
+ );
+
+ let completed = pipeline
+ .copy()
+ .map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
+ if !completed {
+ return Ok(false);
+ };
+
+ // If we complete the file, set the permissions (if on Linux)
+ #[cfg(unix)]
+ {
+ let permissions = Permissions::from_mode(ctx.permissions);
+ set_permissions(ctx.path.clone(), permissions).unwrap();
+ }
+
+ let checksum = pipeline
+ .finish()
+ .map_err(|e| ApplicationDownloadError::IoError(e.kind()))?;
+
+ let res = hex::encode(checksum.0);
+ if res != ctx.checksum {
+ return Err(ApplicationDownloadError::Checksum);
+ }
+
+ Ok(true)
+}
diff --git a/src-tauri/src/games/downloads/manifest.rs b/src-tauri/src/games/downloads/manifest.rs
new file mode 100644
index 0000000..7c5b3a9
--- /dev/null
+++ b/src-tauri/src/games/downloads/manifest.rs
@@ -0,0 +1,27 @@
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+use std::path::PathBuf;
+
+pub type DropManifest = HashMap;
+#[derive(Serialize, Deserialize, Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct DropChunk {
+ pub permissions: u32,
+ pub ids: Vec,
+ pub checksums: Vec,
+ pub lengths: Vec,
+ pub version_name: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct DropDownloadContext {
+ pub file_name: String,
+ pub version: String,
+ pub index: usize,
+ pub offset: u64,
+ pub game_id: String,
+ pub path: PathBuf,
+ pub checksum: String,
+ pub length: usize,
+ pub permissions: u32,
+}
diff --git a/src-tauri/src/games/downloads/mod.rs b/src-tauri/src/games/downloads/mod.rs
new file mode 100644
index 0000000..c9b3cd4
--- /dev/null
+++ b/src-tauri/src/games/downloads/mod.rs
@@ -0,0 +1,5 @@
+pub mod commands;
+pub mod download_agent;
+mod download_logic;
+mod manifest;
+mod stored_manifest;
diff --git a/src-tauri/src/games/downloads/stored_manifest.rs b/src-tauri/src/games/downloads/stored_manifest.rs
new file mode 100644
index 0000000..fdc232b
--- /dev/null
+++ b/src-tauri/src/games/downloads/stored_manifest.rs
@@ -0,0 +1,79 @@
+use std::{
+ fs::File,
+ io::{Read, Write},
+ path::PathBuf,
+ sync::Mutex,
+};
+
+use log::{error, warn};
+use serde::{Deserialize, Serialize};
+use serde_binary::binary_stream::Endian;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct StoredManifest {
+ game_id: String,
+ game_version: String,
+ pub completed_contexts: Mutex>,
+ pub base_path: PathBuf,
+}
+
+static DROP_DATA_PATH: &str = ".dropdata";
+
+impl StoredManifest {
+ pub fn new(game_id: String, game_version: String, base_path: PathBuf) -> Self {
+ Self {
+ base_path,
+ game_id,
+ game_version,
+ completed_contexts: Mutex::new(Vec::new()),
+ }
+ }
+ pub fn generate(game_id: String, game_version: String, base_path: PathBuf) -> Self {
+ let mut file = match File::open(base_path.join(DROP_DATA_PATH)) {
+ Ok(file) => file,
+ Err(_) => return StoredManifest::new(game_id, game_version, base_path),
+ };
+
+ let mut s = Vec::new();
+ match file.read_to_end(&mut s) {
+ Ok(_) => {}
+ Err(e) => {
+ error!("{}", e);
+ return StoredManifest::new(game_id, game_version, base_path);
+ }
+ };
+
+ match serde_binary::from_vec::(s, Endian::Little) {
+ Ok(manifest) => manifest,
+ Err(e) => {
+ warn!("{}", e);
+ StoredManifest::new(game_id, game_version, base_path)
+ }
+ }
+ }
+ pub fn write(&self) {
+ let manifest_raw = match serde_binary::to_vec(&self, Endian::Little) {
+ Ok(json) => json,
+ Err(_) => return,
+ };
+
+ let mut file = match File::create(self.base_path.join(DROP_DATA_PATH)) {
+ Ok(file) => file,
+ Err(e) => {
+ error!("{}", e);
+ return;
+ }
+ };
+
+ match file.write_all(&manifest_raw) {
+ Ok(_) => {}
+ Err(e) => error!("{}", e),
+ };
+ }
+ pub fn set_completed_contexts(&self, completed_contexts: &[usize]) {
+ *self.completed_contexts.lock().unwrap() = completed_contexts.to_owned();
+ }
+ pub fn get_completed_contexts(&self) -> Vec {
+ self.completed_contexts.lock().unwrap().clone()
+ }
+}
diff --git a/src-tauri/src/games/library.rs b/src-tauri/src/games/library.rs
new file mode 100644
index 0000000..b2f2fe2
--- /dev/null
+++ b/src-tauri/src/games/library.rs
@@ -0,0 +1,353 @@
+use std::fs::remove_dir_all;
+use std::sync::Mutex;
+use std::thread::spawn;
+
+use log::{debug, error, warn};
+use serde::{Deserialize, Serialize};
+use tauri::Emitter;
+use tauri::{AppHandle, Manager};
+
+use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db, GameVersion};
+use crate::database::db::{ApplicationTransientStatus, GameDownloadStatus};
+use crate::download_manager::download_manager::DownloadStatus;
+use crate::download_manager::downloadable_metadata::DownloadableMetadata;
+use crate::error::remote_access_error::RemoteAccessError;
+use crate::games::state::{GameStatusManager, GameStatusWithTransient};
+use crate::remote::auth::generate_authorization_header;
+use crate::remote::requests::make_request;
+use crate::AppState;
+
+#[derive(serde::Serialize)]
+pub struct FetchGameStruct {
+ game: Game,
+ status: GameStatusWithTransient,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct Game {
+ id: String,
+ m_name: String,
+ m_short_description: String,
+ m_description: String,
+ // mDevelopers
+ // mPublishers
+ m_icon_id: String,
+ m_banner_id: String,
+ m_cover_id: String,
+ m_image_library: Vec,
+}
+#[derive(serde::Serialize, Clone)]
+pub struct GameUpdateEvent {
+ pub game_id: String,
+ pub status: (
+ Option,
+ Option,
+ ),
+}
+
+#[derive(Serialize, Clone)]
+pub struct QueueUpdateEventQueueData {
+ pub meta: DownloadableMetadata,
+ pub status: DownloadStatus,
+ pub progress: f64,
+ pub current: usize,
+ pub max: usize,
+}
+
+#[derive(serde::Serialize, Clone)]
+pub struct QueueUpdateEvent {
+ pub queue: Vec,
+}
+
+#[derive(serde::Serialize, Clone)]
+pub struct StatsUpdateEvent {
+ pub speed: usize,
+ pub time: usize,
+}
+
+pub fn fetch_library_logic(app: AppHandle) -> Result, RemoteAccessError> {
+ let header = generate_authorization_header();
+
+ let client = reqwest::blocking::Client::new();
+ let response = make_request(&client, &["/api/v1/client/user/library"], &[], |f| {
+ f.header("Authorization", header)
+ })?
+ .send()?;
+
+ if response.status() != 200 {
+ let err = response.json().unwrap();
+ warn!("{:?}", err);
+ return Err(RemoteAccessError::InvalidResponse(err));
+ }
+
+ let games: Vec = response.json()?;
+
+ let state = app.state::>();
+ let mut handle = state.lock().unwrap();
+
+ let mut db_handle = borrow_db_mut_checked();
+
+ for game in games.iter() {
+ handle.games.insert(game.id.clone(), game.clone());
+ if !db_handle.applications.game_statuses.contains_key(&game.id) {
+ db_handle
+ .applications
+ .game_statuses
+ .insert(game.id.clone(), GameDownloadStatus::Remote {});
+ }
+ }
+
+ drop(handle);
+
+ Ok(games)
+}
+
+pub fn fetch_game_logic(
+ id: String,
+ app: tauri::AppHandle,
+) -> Result {
+ let state = app.state::>();
+ let mut state_handle = state.lock().unwrap();
+
+ let game = state_handle.games.get(&id);
+ if let Some(game) = game {
+ let status = GameStatusManager::fetch_state(&id);
+
+ let data = FetchGameStruct {
+ game: game.clone(),
+ status,
+ };
+
+ return Ok(data);
+ }
+ let client = reqwest::blocking::Client::new();
+ let response = make_request(&client, &["/api/v1/game/", &id], &[], |r| {
+ r.header("Authorization", generate_authorization_header())
+ })?
+ .send()?;
+
+ if response.status() == 404 {
+ return Err(RemoteAccessError::GameNotFound);
+ }
+ if response.status() != 200 {
+ let err = response.json().unwrap();
+ warn!("{:?}", err);
+ return Err(RemoteAccessError::InvalidResponse(err));
+ }
+
+ let game: Game = response.json()?;
+ state_handle.games.insert(id.clone(), game.clone());
+
+ let mut db_handle = borrow_db_mut_checked();
+
+ db_handle
+ .applications
+ .game_statuses
+ .entry(id.clone())
+ .or_insert(GameDownloadStatus::Remote {});
+ drop(db_handle);
+
+ let status = GameStatusManager::fetch_state(&id);
+
+ let data = FetchGameStruct {
+ game: game.clone(),
+ status,
+ };
+
+ Ok(data)
+}
+
+pub fn fetch_game_verion_options_logic(
+ game_id: String,
+ state: tauri::State<'_, Mutex>,
+) -> Result, RemoteAccessError> {
+ let client = reqwest::blocking::Client::new();
+
+ let response = make_request(
+ &client,
+ &["/api/v1/client/game/versions"],
+ &[("id", &game_id)],
+ |r| r.header("Authorization", generate_authorization_header()),
+ )?
+ .send()?;
+
+ if response.status() != 200 {
+ let err = response.json().unwrap();
+ warn!("{:?}", err);
+ return Err(RemoteAccessError::InvalidResponse(err));
+ }
+
+ let data: Vec = response.json()?;
+
+ let state_lock = state.lock().unwrap();
+ let process_manager_lock = state_lock.process_manager.lock().unwrap();
+ let data: Vec = data
+ .into_iter()
+ .filter(|v| process_manager_lock.valid_platform(&v.platform).unwrap())
+ .collect();
+ drop(process_manager_lock);
+ drop(state_lock);
+
+ Ok(data)
+}
+
+pub fn uninstall_game_logic(meta: DownloadableMetadata, app_handle: &AppHandle) {
+ println!("triggered uninstall for agent");
+ let mut db_handle = borrow_db_mut_checked();
+ db_handle
+ .applications
+ .transient_statuses
+ .entry(meta.clone())
+ .and_modify(|v| *v = ApplicationTransientStatus::Uninstalling {});
+
+ push_game_update(
+ app_handle,
+ &meta.id,
+ (None, Some(ApplicationTransientStatus::Uninstalling {})),
+ );
+
+ let previous_state = db_handle.applications.game_statuses.get(&meta.id).cloned();
+ if previous_state.is_none() {
+ warn!("uninstall job doesn't have previous state, failing silently");
+ return;
+ }
+ let previous_state = previous_state.unwrap();
+ if let Some((_, install_dir)) = match previous_state {
+ GameDownloadStatus::Installed {
+ version_name,
+ install_dir,
+ } => Some((version_name, install_dir)),
+ GameDownloadStatus::SetupRequired {
+ version_name,
+ install_dir,
+ } => Some((version_name, install_dir)),
+ _ => None,
+ } {
+ db_handle
+ .applications
+ .transient_statuses
+ .entry(meta.clone())
+ .and_modify(|v| *v = ApplicationTransientStatus::Uninstalling {});
+ drop(db_handle);
+
+ let app_handle = app_handle.clone();
+ spawn(move || match remove_dir_all(install_dir) {
+ Err(e) => {
+ error!("{}", e);
+ }
+ Ok(_) => {
+ let mut db_handle = borrow_db_mut_checked();
+ db_handle.applications.transient_statuses.remove(&meta);
+ db_handle
+ .applications
+ .game_statuses
+ .entry(meta.id.clone())
+ .and_modify(|e| *e = GameDownloadStatus::Remote {});
+ drop(db_handle);
+ save_db();
+
+ debug!("uninstalled game id {}", &meta.id);
+
+ push_game_update(
+ &app_handle,
+ &meta.id,
+ (Some(GameDownloadStatus::Remote {}), None),
+ );
+ }
+ });
+ }
+}
+
+pub fn get_current_meta(game_id: &String) -> Option {
+ borrow_db_checked()
+ .applications
+ .installed_game_version
+ .get(game_id)
+ .cloned()
+}
+
+pub fn on_game_complete(
+ meta: &DownloadableMetadata,
+ install_dir: String,
+ app_handle: &AppHandle,
+) -> Result<(), RemoteAccessError> {
+ // Fetch game version information from remote
+ if meta.version.is_none() {
+ return Err(RemoteAccessError::GameNotFound);
+ }
+
+ let header = generate_authorization_header();
+
+ let client = reqwest::blocking::Client::new();
+ let response = make_request(
+ &client,
+ &["/api/v1/client/metadata/version"],
+ &[
+ ("id", &meta.id),
+ ("version", meta.version.as_ref().unwrap()),
+ ],
+ |f| f.header("Authorization", header),
+ )?
+ .send()?;
+
+ let data: GameVersion = response.json()?;
+
+ let mut handle = borrow_db_mut_checked();
+ handle
+ .applications
+ .game_versions
+ .entry(meta.id.clone())
+ .or_default()
+ .insert(meta.version.clone().unwrap(), data.clone());
+ handle
+ .applications
+ .installed_game_version
+ .insert(meta.id.clone(), meta.clone());
+
+ drop(handle);
+ save_db();
+
+ let status = if data.setup_command.is_empty() {
+ GameDownloadStatus::Installed {
+ version_name: meta.version.clone().unwrap(),
+ install_dir,
+ }
+ } else {
+ GameDownloadStatus::SetupRequired {
+ version_name: meta.version.clone().unwrap(),
+ install_dir,
+ }
+ };
+
+ let mut db_handle = borrow_db_mut_checked();
+ db_handle
+ .applications
+ .game_statuses
+ .insert(meta.id.clone(), status.clone());
+ drop(db_handle);
+ save_db();
+ app_handle
+ .emit(
+ &format!("update_game/{}", meta.id),
+ GameUpdateEvent {
+ game_id: meta.id.clone(),
+ status: (Some(status), None),
+ },
+ )
+ .unwrap();
+
+ Ok(())
+}
+
+pub fn push_game_update(app_handle: &AppHandle, game_id: &String, status: GameStatusWithTransient) {
+ app_handle
+ .emit(
+ &format!("update_game/{}", game_id),
+ GameUpdateEvent {
+ game_id: game_id.clone(),
+ status,
+ },
+ )
+ .unwrap();
+}
diff --git a/src-tauri/src/games/mod.rs b/src-tauri/src/games/mod.rs
new file mode 100644
index 0000000..65c5c6b
--- /dev/null
+++ b/src-tauri/src/games/mod.rs
@@ -0,0 +1,4 @@
+pub mod commands;
+pub mod downloads;
+pub mod library;
+pub mod state;
diff --git a/src-tauri/src/games/state.rs b/src-tauri/src/games/state.rs
new file mode 100644
index 0000000..19b1769
--- /dev/null
+++ b/src-tauri/src/games/state.rs
@@ -0,0 +1,29 @@
+use crate::database::db::{borrow_db_checked, ApplicationTransientStatus, GameDownloadStatus};
+
+pub type GameStatusWithTransient = (
+ Option,
+ Option,
+);
+pub struct GameStatusManager {}
+
+impl GameStatusManager {
+ pub fn fetch_state(game_id: &String) -> GameStatusWithTransient {
+ let db_lock = borrow_db_checked();
+ let online_state = match db_lock.applications.installed_game_version.get(game_id) {
+ Some(meta) => db_lock.applications.transient_statuses.get(meta).cloned(),
+ None => None,
+ };
+ let offline_state = db_lock.applications.game_statuses.get(game_id).cloned();
+ drop(db_lock);
+
+ if online_state.is_some() {
+ return (None, online_state);
+ }
+
+ if offline_state.is_some() {
+ return (offline_state, None);
+ }
+
+ (None, None)
+ }
+}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 4f7d644..1e374eb 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,112 +1,405 @@
-mod auth;
-mod data;
+mod database;
+mod games;
+
+mod autostart;
+mod cleanup;
+mod commands;
+mod download_manager;
+mod error;
+mod process;
mod remote;
-mod unpacker;
-use std::{
- io,
- sync::{LazyLock, Mutex},
- task, thread,
+use crate::database::db::DatabaseImpls;
+use autostart::{get_autostart_enabled, toggle_autostart};
+use cleanup::{cleanup_and_exit, quit};
+use commands::fetch_state;
+use database::commands::{
+ add_download_dir, delete_download_dir, fetch_download_dir_stats, fetch_settings,
+ fetch_system_data, update_settings,
};
-
-use auth::{auth_initiate, recieve_handshake};
-use data::DatabaseInterface;
-use log::info;
-use remote::use_remote;
-use serde::Serialize;
-use structured_logger::{json::new_writer, Builder};
+use database::db::{
+ borrow_db_checked, borrow_db_mut_checked, DatabaseInterface, GameDownloadStatus, DATA_ROOT_DIR,
+};
+use download_manager::commands::{
+ cancel_game, move_download_in_queue, pause_downloads, resume_downloads,
+};
+use download_manager::download_manager::DownloadManager;
+use download_manager::download_manager_builder::DownloadManagerBuilder;
+use games::commands::{
+ fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game,
+};
+use games::downloads::commands::download_game;
+use games::library::Game;
+use http::Response;
+use http::{header::*, response::Builder as ResponseBuilder};
+use log::{debug, info, warn, LevelFilter};
+use log4rs::append::console::ConsoleAppender;
+use log4rs::append::file::FileAppender;
+use log4rs::config::{Appender, Root};
+use log4rs::encode::pattern::PatternEncoder;
+use log4rs::Config;
+use process::commands::{kill_game, launch_game};
+use process::process_manager::ProcessManager;
+use remote::auth::{self, generate_authorization_header, recieve_handshake};
+use remote::commands::{
+ auth_initiate, gen_drop_url, manual_recieve_handshake, retry_connect, sign_out, use_remote,
+};
+use remote::requests::make_request;
+use serde::{Deserialize, Serialize};
+use std::env;
+use std::path::Path;
+use std::str::FromStr;
+use std::sync::Arc;
+use std::{
+ collections::HashMap,
+ sync::{LazyLock, Mutex},
+};
+use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
+use tauri::tray::TrayIconBuilder;
+use tauri::{AppHandle, Manager, RunEvent, WindowEvent};
use tauri_plugin_deep_link::DeepLinkExt;
+use tauri_plugin_dialog::DialogExt;
#[derive(Clone, Copy, Serialize)]
pub enum AppStatus {
NotConfigured,
+ ServerError,
SignedOut,
SignedIn,
SignedInNeedsReauth,
+ ServerUnavailable,
}
-#[derive(Clone, Copy, Serialize)]
-pub struct User {}
-#[derive(Clone, Copy, Serialize)]
-pub struct AppState {
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct User {
+ id: String,
+ username: String,
+ admin: bool,
+ display_name: String,
+ profile_picture: String,
+}
+
+#[derive(Clone, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AppState<'a> {
status: AppStatus,
user: Option,
+ games: HashMap,
+
+ #[serde(skip_serializing)]
+ download_manager: Arc,
+ #[serde(skip_serializing)]
+ process_manager: Arc>>,
}
-#[tauri::command]
-fn fetch_state<'a>(state: tauri::State<'_, Mutex>) -> Result {
- let guard = state.lock().unwrap();
- let cloned_state = guard.clone();
- drop(guard);
- Ok(cloned_state)
-}
+fn setup(handle: AppHandle) -> AppState<'static> {
+ let logfile = FileAppender::builder()
+ .encoder(Box::new(PatternEncoder::new(
+ "{d} | {l} | {f}:{L} - {m}{n}",
+ )))
+ .append(false)
+ .build(DATA_ROOT_DIR.lock().unwrap().join("./drop.log"))
+ .unwrap();
-fn setup<'a>() -> AppState {
- Builder::with_level("info")
- .with_target_writer("*", new_writer(io::stdout()))
- .init();
+ let console = ConsoleAppender::builder()
+ .encoder(Box::new(PatternEncoder::new(
+ "{d} | {l} | {f}:{L} - {m}{n}",
+ )))
+ .build();
- let is_set_up = data::is_set_up();
+ let log_level = env::var("RUST_LOG").unwrap_or(String::from("Info"));
+
+ let config = Config::builder()
+ .appenders(vec![
+ Appender::builder().build("logfile", Box::new(logfile)),
+ Appender::builder().build("console", Box::new(console)),
+ ])
+ .build(
+ Root::builder()
+ .appenders(vec!["logfile", "console"])
+ .build(LevelFilter::from_str(&log_level).expect("Invalid log level")),
+ )
+ .unwrap();
+
+ log4rs::init_config(config).unwrap();
+
+ let games = HashMap::new();
+ let download_manager = Arc::new(DownloadManagerBuilder::build(handle.clone()));
+ let process_manager = Arc::new(Mutex::new(ProcessManager::new(handle.clone())));
+
+ debug!("checking if database is set up");
+ let is_set_up = DB.database_is_set_up();
if !is_set_up {
return AppState {
status: AppStatus::NotConfigured,
user: None,
+ games,
+ download_manager,
+ process_manager,
};
}
- let auth_result = auth::setup().unwrap();
- return AppState {
- status: auth_result.0,
- user: auth_result.1,
- };
+ debug!("database is set up");
+
+ // TODO: Account for possible failure
+ let (app_status, user) = auth::setup();
+
+ let db_handle = borrow_db_checked();
+ let mut missing_games = Vec::new();
+ let statuses = db_handle.applications.game_statuses.clone();
+ drop(db_handle);
+ for (game_id, status) in statuses.into_iter() {
+ match status {
+ database::db::GameDownloadStatus::Remote {} => {}
+ database::db::GameDownloadStatus::SetupRequired {
+ version_name: _,
+ install_dir,
+ } => {
+ let install_dir_path = Path::new(&install_dir);
+ if !install_dir_path.exists() {
+ missing_games.push(game_id);
+ }
+ }
+ database::db::GameDownloadStatus::Installed {
+ version_name: _,
+ install_dir,
+ } => {
+ let install_dir_path = Path::new(&install_dir);
+ if !install_dir_path.exists() {
+ missing_games.push(game_id);
+ }
+ }
+ }
+ }
+
+ info!("detected games missing: {:?}", missing_games);
+
+ let mut db_handle = borrow_db_mut_checked();
+ for game_id in missing_games {
+ db_handle
+ .applications
+ .game_statuses
+ .entry(game_id)
+ .and_modify(|v| *v = GameDownloadStatus::Remote {});
+ }
+
+ drop(db_handle);
+
+ debug!("finished setup!");
+
+ // Sync autostart state
+ if let Err(e) = autostart::sync_autostart_on_startup(&handle) {
+ warn!("failed to sync autostart state: {}", e);
+ }
+
+ AppState {
+ status: app_status,
+ user,
+ games,
+ download_manager,
+ process_manager,
+ }
}
-pub static DB: LazyLock = LazyLock::new(|| data::setup());
+pub static DB: LazyLock = LazyLock::new(DatabaseInterface::set_up_database);
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
- let state = setup();
- info!("Initialized drop client");
-
- let mut builder = tauri::Builder::default();
+ let mut builder = tauri::Builder::default()
+ .plugin(tauri_plugin_os::init())
+ .plugin(tauri_plugin_dialog::init());
#[cfg(desktop)]
+ #[allow(unused_variables)]
{
builder = builder.plugin(tauri_plugin_single_instance::init(|_app, argv, _cwd| {
// when defining deep link schemes at runtime, you must also check `argv` here
}));
}
- builder
+ let app = builder
.plugin(tauri_plugin_deep_link::init())
- .manage(Mutex::new(state))
.invoke_handler(tauri::generate_handler![
+ // Core utils
fetch_state,
+ quit,
+ fetch_system_data,
+ // User utils
+ update_settings,
+ fetch_settings,
+ // Auth
auth_initiate,
- use_remote
+ retry_connect,
+ manual_recieve_handshake,
+ sign_out,
+ // Remote
+ use_remote,
+ gen_drop_url,
+ // Library
+ fetch_library,
+ fetch_game,
+ add_download_dir,
+ delete_download_dir,
+ fetch_download_dir_stats,
+ fetch_game_status,
+ fetch_game_verion_options,
+ // Downloads
+ download_game,
+ move_download_in_queue,
+ pause_downloads,
+ resume_downloads,
+ cancel_game,
+ uninstall_game,
+ // Processes
+ launch_game,
+ kill_game,
+ toggle_autostart,
+ get_autostart_enabled,
])
.plugin(tauri_plugin_shell::init())
+ .plugin(tauri_plugin_dialog::init())
+ .plugin(tauri_plugin_autostart::init(
+ tauri_plugin_autostart::MacosLauncher::LaunchAgent,
+ Some(vec!["--minimize"]),
+ ))
.setup(|app| {
+ let handle = app.handle().clone();
+ let state = setup(handle);
+ debug!("initialized drop client");
+ app.manage(Mutex::new(state));
+
#[cfg(any(target_os = "linux", all(debug_assertions, windows)))]
{
use tauri_plugin_deep_link::DeepLinkExt;
app.deep_link().register_all()?;
- info!("registered all pre-defined deep links");
+ debug!("registered all pre-defined deep links");
}
let handle = app.handle().clone();
+ let _main_window = tauri::WebviewWindowBuilder::new(
+ &handle,
+ "main", // BTW this is not the name of the window, just the label. Keep this 'main', there are permissions & configs that depend on it
+ tauri::WebviewUrl::App("index.html".into()),
+ )
+ .title("Drop Desktop App")
+ .min_inner_size(1000.0, 500.0)
+ .inner_size(1536.0, 864.0)
+ .decorations(false)
+ .shadow(false)
+ .data_directory(DATA_ROOT_DIR.lock().unwrap().join(".webview"))
+ .build()
+ .unwrap();
+
app.deep_link().on_open_url(move |event| {
- info!("handling drop:// url");
+ debug!("handling drop:// url");
let binding = event.urls();
- let url = binding.get(0).unwrap();
- match url.host_str().unwrap() {
- "handshake" => recieve_handshake(handle.clone(), url.path().to_string()),
- _ => (),
+ let url = binding.first().unwrap();
+ if url.host_str().unwrap() == "handshake" {
+ recieve_handshake(handle.clone(), url.path().to_string())
}
});
+
+ let menu = Menu::with_items(
+ app,
+ &[
+ &MenuItem::with_id(app, "open", "Open", true, None::<&str>)?,
+ &PredefinedMenuItem::separator(app)?,
+ /*
+ &MenuItem::with_id(app, "show_library", "Library", true, None::<&str>)?,
+ &MenuItem::with_id(app, "show_settings", "Settings", true, None::<&str>)?,
+ &PredefinedMenuItem::separator(app)?,
+ */
+ &MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?,
+ ],
+ )?;
+
+ TrayIconBuilder::new()
+ .icon(app.default_window_icon().unwrap().clone())
+ .menu(&menu)
+ .on_menu_event(|app, event| match event.id.as_ref() {
+ "open" => {
+ app.webview_windows().get("main").unwrap().show().unwrap();
+ }
+ "quit" => {
+ cleanup_and_exit(app, &app.state());
+ }
+
+ _ => {
+ println!("menu event not handled: {:?}", event.id);
+ }
+ })
+ .build(app)
+ .expect("error while setting up tray menu");
+
+ {
+ let mut db_handle = borrow_db_mut_checked();
+ if let Some(original) = db_handle.prev_database.take() {
+ warn!(
+ "Database corrupted. Original file at {}",
+ original
+ .canonicalize()
+ .unwrap()
+ .to_string_lossy()
+ .to_string()
+ );
+ app.dialog()
+ .message(
+ "Database corrupted. A copy has been saved at: ".to_string()
+ + original.to_str().unwrap(),
+ )
+ .title("Database corrupted")
+ .show(|_| {});
+ }
+ }
+
Ok(())
})
- .run(tauri::generate_context!())
+ .register_asynchronous_uri_scheme_protocol("object", move |_ctx, request, responder| {
+ // Drop leading /
+ let object_id = &request.uri().path()[1..];
+
+ let header = generate_authorization_header();
+ let client: reqwest::blocking::Client = reqwest::blocking::Client::new();
+ let response = make_request(&client, &["/api/v1/client/object/", object_id], &[], |f| {
+ f.header("Authorization", header)
+ })
+ .unwrap()
+ .send();
+ if response.is_err() {
+ warn!(
+ "failed to fetch object with error: {}",
+ response.err().unwrap()
+ );
+ responder.respond(Response::builder().status(500).body(Vec::new()).unwrap());
+ return;
+ }
+ let response = response.unwrap();
+
+ let resp_builder = ResponseBuilder::new().header(
+ CONTENT_TYPE,
+ response.headers().get("Content-Type").unwrap(),
+ );
+ let data = Vec::from(response.bytes().unwrap());
+ let resp = resp_builder.body(data).unwrap();
+
+ responder.respond(resp);
+ })
+ .on_window_event(|window, event| {
+ if let WindowEvent::CloseRequested { api, .. } = event {
+ window.hide().unwrap();
+ api.prevent_close();
+ }
+ })
+ .build(tauri::generate_context!())
.expect("error while running tauri application");
+
+ app.run(|_app_handle, event| {
+ if let RunEvent::ExitRequested { code, api, .. } = event {
+ if code.is_none() {
+ api.prevent_exit();
+ }
+ }
+ });
}
diff --git a/src-tauri/src/process/commands.rs b/src-tauri/src/process/commands.rs
new file mode 100644
index 0000000..8a213a4
--- /dev/null
+++ b/src-tauri/src/process/commands.rs
@@ -0,0 +1,40 @@
+use std::sync::Mutex;
+
+use crate::{error::process_error::ProcessError, AppState};
+
+#[tauri::command]
+pub fn launch_game(
+ id: String,
+ state: tauri::State<'_, Mutex>,
+) -> Result<(), ProcessError> {
+ let state_lock = state.lock().unwrap();
+ let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
+
+ //let meta = DownloadableMetadata {
+ // id,
+ // version: Some(version),
+ // download_type: DownloadType::Game,
+ //};
+
+ match process_manager_lock.launch_process(id) {
+ Ok(_) => {}
+ Err(e) => return Err(e),
+ };
+
+ drop(process_manager_lock);
+ drop(state_lock);
+
+ Ok(())
+}
+
+#[tauri::command]
+pub fn kill_game(
+ game_id: String,
+ state: tauri::State<'_, Mutex>,
+) -> Result<(), ProcessError> {
+ let state_lock = state.lock().unwrap();
+ let mut process_manager_lock = state_lock.process_manager.lock().unwrap();
+ process_manager_lock
+ .kill_game(game_id)
+ .map_err(ProcessError::IOError)
+}
diff --git a/src-tauri/src/process/compat.rs b/src-tauri/src/process/compat.rs
new file mode 100644
index 0000000..2b4f307
--- /dev/null
+++ b/src-tauri/src/process/compat.rs
@@ -0,0 +1,13 @@
+// Since this code isn't being used, we can either:
+// 1. Delete the entire file if compatibility features are not planned
+// 2. Or add a TODO comment if planning to implement later
+
+// Option 1: Delete the file
+// Delete src-tauri/src/process/compat.rs
+
+// Option 2: Add TODO comment
+/*
+TODO: Compatibility layer for running Windows games on Linux
+This module is currently unused but reserved for future implementation
+of Windows game compatibility features on Linux.
+*/
diff --git a/src-tauri/src/process/mod.rs b/src-tauri/src/process/mod.rs
new file mode 100644
index 0000000..6a1aed5
--- /dev/null
+++ b/src-tauri/src/process/mod.rs
@@ -0,0 +1,3 @@
+pub mod commands;
+pub mod compat;
+pub mod process_manager;
diff --git a/src-tauri/src/process/process_manager.rs b/src-tauri/src/process/process_manager.rs
new file mode 100644
index 0000000..4d8ae12
--- /dev/null
+++ b/src-tauri/src/process/process_manager.rs
@@ -0,0 +1,387 @@
+use std::{
+ collections::HashMap,
+ fs::{File, OpenOptions},
+ io::{self, Error},
+ path::{Path, PathBuf},
+ process::{Child, Command, ExitStatus},
+ sync::{Arc, Mutex},
+ thread::spawn,
+};
+
+use log::{debug, info, warn};
+use serde::{Deserialize, Serialize};
+use shared_child::SharedChild;
+use tauri::{AppHandle, Manager};
+use umu_wrapper_lib::command_builder::UmuCommandBuilder;
+
+use crate::{
+ database::db::{
+ borrow_db_mut_checked, ApplicationTransientStatus, GameDownloadStatus, GameVersion, DATA_ROOT_DIR
+ },
+ download_manager::downloadable_metadata::{DownloadType, DownloadableMetadata},
+ error::process_error::ProcessError,
+ games::{library::push_game_update, state::GameStatusManager},
+ AppState, DB,
+};
+
+pub struct ProcessManager<'a> {
+ current_platform: Platform,
+ log_output_dir: PathBuf,
+ processes: HashMap>,
+ app_handle: AppHandle,
+ game_launchers: HashMap<(Platform, Platform), &'a (dyn ProcessHandler + Sync + Send + 'static)>,
+}
+
+impl ProcessManager<'_> {
+ pub fn new(app_handle: AppHandle) -> Self {
+ let root_dir_lock = DATA_ROOT_DIR.lock().unwrap();
+ let log_output_dir = root_dir_lock.join("logs");
+ drop(root_dir_lock);
+
+ ProcessManager {
+ current_platform: if cfg!(windows) {
+ Platform::Windows
+ } else {
+ Platform::Linux
+ },
+
+ app_handle,
+ processes: HashMap::new(),
+ log_output_dir,
+ game_launchers: HashMap::from([
+ // Current platform to target platform
+ (
+ (Platform::Windows, Platform::Windows),
+ &NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
+ ),
+ (
+ (Platform::Linux, Platform::Linux),
+ &NativeGameLauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
+ ),
+ (
+ (Platform::Linux, Platform::Windows),
+ &UMULauncher {} as &(dyn ProcessHandler + Sync + Send + 'static),
+ ),
+ ]),
+ }
+ }
+
+ fn process_command(&self, install_dir: &String, command: Vec) -> (PathBuf, Vec) {
+ let root = &command[0];
+
+ let install_dir = Path::new(install_dir);
+ let absolute_exe = install_dir.join(root);
+
+ /*
+ let args = command_components[1..]
+ .iter()
+ .map(|v| v.to_string())
+ .collect();
+ */
+ (absolute_exe, Vec::new())
+ }
+ pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> {
+ match self.processes.get(&game_id) {
+ Some(child) => {
+ child.kill()?;
+ child.wait()?;
+ Ok(())
+ }
+ None => Err(io::Error::new(
+ io::ErrorKind::NotFound,
+ "Game ID not running",
+ )),
+ }
+ }
+
+ fn on_process_finish(&mut self, game_id: String, result: Result) {
+ if !self.processes.contains_key(&game_id) {
+ warn!("process on_finish was called, but game_id is no longer valid. finished with result: {:?}", result);
+ return;
+ }
+
+ debug!("process for {:?} exited with {:?}", &game_id, result);
+
+ self.processes.remove(&game_id);
+
+ let mut db_handle = borrow_db_mut_checked();
+ let meta = db_handle
+ .applications
+ .installed_game_version
+ .get(&game_id)
+ .cloned()
+ .unwrap();
+ db_handle.applications.transient_statuses.remove(&meta);
+
+ let current_state = db_handle.applications.game_statuses.get(&game_id).cloned();
+ if let Some(saved_state) = current_state {
+ if let GameDownloadStatus::SetupRequired {
+ version_name,
+ install_dir,
+ } = saved_state
+ {
+ if let Ok(exit_code) = result {
+ if exit_code.success() {
+ db_handle.applications.game_statuses.insert(
+ game_id.clone(),
+ GameDownloadStatus::Installed {
+ version_name: version_name.to_string(),
+ install_dir: install_dir.to_string(),
+ },
+ );
+ }
+ }
+ }
+ }
+ drop(db_handle);
+
+ let status = GameStatusManager::fetch_state(&game_id);
+
+ push_game_update(&self.app_handle, &game_id, status);
+
+ // TODO better management
+ }
+
+ pub fn valid_platform(&self, platform: &Platform) -> Result {
+ let current = &self.current_platform;
+ Ok(self
+ .game_launchers
+ .contains_key(&(current.clone(), platform.clone())))
+ }
+
+ pub fn launch_process(&mut self, game_id: String) -> Result<(), ProcessError> {
+ if self.processes.contains_key(&game_id) {
+ return Err(ProcessError::AlreadyRunning);
+ }
+
+ let version = match DB
+ .borrow_data()
+ .unwrap()
+ .applications
+ .game_statuses
+ .get(&game_id)
+ .cloned()
+ {
+ Some(GameDownloadStatus::Installed { version_name, .. }) => version_name,
+ Some(GameDownloadStatus::SetupRequired { .. }) => {
+ return Err(ProcessError::SetupRequired)
+ }
+ _ => return Err(ProcessError::NotInstalled),
+ };
+ let meta = DownloadableMetadata {
+ id: game_id.clone(),
+ version: Some(version.clone()),
+ download_type: DownloadType::Game,
+ };
+
+ let mut db_lock = borrow_db_mut_checked();
+ debug!(
+ "Launching process {:?} with games {:?}",
+ &game_id, db_lock.applications.game_versions
+ );
+
+ let game_status = db_lock
+ .applications
+ .game_statuses
+ .get(&game_id)
+ .ok_or(ProcessError::NotInstalled)?;
+
+ let (version_name, install_dir) = match game_status {
+ GameDownloadStatus::Installed {
+ version_name,
+ install_dir,
+ } => (version_name, install_dir),
+ GameDownloadStatus::SetupRequired {
+ version_name,
+ install_dir,
+ } => (version_name, install_dir),
+ _ => return Err(ProcessError::NotDownloaded),
+ };
+
+
+ let game_version = db_lock
+ .applications
+ .game_versions
+ .get(&game_id)
+ .ok_or(ProcessError::InvalidID)?
+ .get(version_name)
+ .ok_or(ProcessError::InvalidVersion)?;
+
+ let mut command: Vec = Vec::new();
+
+ match game_status {
+ GameDownloadStatus::Installed {
+ version_name: _,
+ install_dir: _,
+ } => {
+ command.extend([game_version.launch_command.clone()]);
+ command.extend(game_version.launch_args.clone());
+ },
+ GameDownloadStatus::SetupRequired {
+ version_name: _,
+ install_dir: _,
+ } => {
+ command.extend([game_version.setup_command.clone()]);
+ command.extend(game_version.setup_args.clone());
+ },
+ _ => panic!("unreachable code"),
+ };
+ info!("Command: {:?}", &command);
+
+ let (command, args) = self.process_command(install_dir, command);
+
+ let target_current_dir = command.parent().unwrap().to_str().unwrap();
+
+ info!(
+ "launching process {} in {}",
+ command.to_str().unwrap(),
+ target_current_dir
+ );
+
+ let current_time = chrono::offset::Local::now();
+ let log_file = OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .read(true)
+ .create(true)
+ .open(self.log_output_dir.join(format!(
+ "{}-{}-{}.log",
+ &game_id,
+ &version,
+ current_time.timestamp()
+ )))
+ .map_err(ProcessError::IOError)?;
+
+ let error_file = OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .read(true)
+ .create(true)
+ .open(self.log_output_dir.join(format!(
+ "{}-{}-{}-error.log",
+ &game_id,
+ &version,
+ current_time.timestamp()
+ )))
+ .map_err(ProcessError::IOError)?;
+
+ let current_platform = self.current_platform.clone();
+ let target_platform = game_version.platform.clone();
+
+ let game_launcher = self
+ .game_launchers
+ .get(&(current_platform, target_platform))
+ .ok_or(ProcessError::InvalidPlatform)?;
+
+ let launch_process = game_launcher
+ .launch_process(
+ &meta,
+ command.to_string_lossy().to_string(),
+ game_version,
+ target_current_dir,
+ log_file,
+ error_file,
+ )
+ .map_err(ProcessError::IOError)?;
+
+ let launch_process_handle =
+ Arc::new(SharedChild::new(launch_process).map_err(ProcessError::IOError)?);
+
+ db_lock
+ .applications
+ .transient_statuses
+ .insert(meta.clone(), ApplicationTransientStatus::Running {});
+
+ push_game_update(
+ &self.app_handle,
+ &meta.id,
+ (None, Some(ApplicationTransientStatus::Running {})),
+ );
+
+ let wait_thread_handle = launch_process_handle.clone();
+ let wait_thread_apphandle = self.app_handle.clone();
+ let wait_thread_game_id = meta.clone();
+
+ spawn(move || {
+ let result: Result = launch_process_handle.wait();
+
+ let app_state = wait_thread_apphandle.state::>();
+ let app_state_handle = app_state.lock().unwrap();
+
+ let mut process_manager_handle = app_state_handle.process_manager.lock().unwrap();
+ process_manager_handle.on_process_finish(wait_thread_game_id.id, result);
+
+ // As everything goes out of scope, they should get dropped
+ // But just to explicit about it
+ drop(process_manager_handle);
+ drop(app_state_handle);
+ });
+
+ self.processes.insert(meta.id, wait_thread_handle);
+ Ok(())
+ }
+}
+
+#[derive(Eq, Hash, PartialEq, Serialize, Deserialize, Clone, Debug)]
+pub enum Platform {
+ Windows,
+ Linux,
+}
+
+pub trait ProcessHandler: Send + 'static {
+ fn launch_process(
+ &self,
+ meta: &DownloadableMetadata,
+ launch_command: String,
+ game_version: &GameVersion,
+ current_dir: &str,
+ log_file: File,
+ error_file: File,
+ ) -> Result;
+}
+
+struct NativeGameLauncher;
+impl ProcessHandler for NativeGameLauncher {
+ fn launch_process(
+ &self,
+ _meta: &DownloadableMetadata,
+ launch_command: String,
+ game_version: &GameVersion,
+ current_dir: &str,
+ log_file: File,
+ error_file: File,
+ ) -> Result {
+ Command::new(PathBuf::from(launch_command))
+ .current_dir(current_dir)
+ .stdout(log_file)
+ .stderr(error_file)
+ .args(game_version.launch_args.clone())
+ .spawn()
+ }
+}
+
+const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run";
+struct UMULauncher;
+impl ProcessHandler for UMULauncher {
+ fn launch_process(
+ &self,
+ _meta: &DownloadableMetadata,
+ launch_command: String,
+ game_version: &GameVersion,
+ _current_dir: &str,
+ _log_file: File,
+ _error_file: File,
+ ) -> Result {
+ println!("Game override: .{:?}.", &game_version.umu_id_override);
+ let game_id = match &game_version.umu_id_override {
+ Some(game_override) => game_override.is_empty().then_some(game_version.game_id.clone()).unwrap_or(game_override.clone()) ,
+ None => game_version.game_id.clone()
+ };
+ info!("Game ID: {}", game_id);
+ UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, launch_command)
+ .game_id(game_id)
+ .launch_args(game_version.launch_args.clone())
+ .build()
+ .spawn()
+ }
+}
diff --git a/src-tauri/src/remote.rs b/src-tauri/src/remote.rs
deleted file mode 100644
index d02770b..0000000
--- a/src-tauri/src/remote.rs
+++ /dev/null
@@ -1,61 +0,0 @@
-use std::{
- borrow::{Borrow, BorrowMut},
- sync::Mutex,
-};
-
-use log::{info, warn};
-use serde::Deserialize;
-use url::Url;
-
-use crate::{AppState, AppStatus, DB};
-
-macro_rules! unwrap_or_return {
- ( $e:expr ) => {
- match $e {
- Ok(x) => x,
- Err(e) => {
- return Err(format!(
- "Invalid URL or Drop is inaccessible ({})",
- e.to_string()
- ))
- }
- }
- };
-}
-
-#[derive(Deserialize)]
-struct DropHealthcheck {
- appName: String,
-}
-
-#[tauri::command]
-pub async fn use_remote<'a>(
- url: String,
- state: tauri::State<'_, Mutex>,
-) -> Result<(), String> {
- info!("connecting to url {}", url);
- let base_url = unwrap_or_return!(Url::parse(&url));
-
- // Test Drop url
- let test_endpoint = base_url.join("/api/v1").unwrap();
- let response = unwrap_or_return!(reqwest::get(test_endpoint.to_string()).await);
-
- let result = response.json::().await.unwrap();
-
- if result.appName != "Drop" {
- warn!("user entered drop endpoint that connected, but wasn't identified as Drop");
- return Err("Not a valid Drop endpoint".to_string());
- }
-
- let mut app_state = state.lock().unwrap();
- app_state.status = AppStatus::SignedOut;
- drop(app_state);
-
- let mut db_state = DB.borrow_data_mut().unwrap();
- db_state.base_url = base_url.to_string();
- drop(db_state);
-
- DB.save().unwrap();
-
- return Ok(());
-}
diff --git a/src-tauri/src/remote/auth.rs b/src-tauri/src/remote/auth.rs
new file mode 100644
index 0000000..d2594de
--- /dev/null
+++ b/src-tauri/src/remote/auth.rs
@@ -0,0 +1,201 @@
+use std::{env, sync::Mutex};
+
+use chrono::Utc;
+use log::{debug, error, warn};
+use openssl::{ec::EcKey, hash::MessageDigest, pkey::PKey, sign::Signer};
+use serde::{Deserialize, Serialize};
+use tauri::{AppHandle, Emitter, Manager};
+use url::Url;
+
+use crate::{
+ database::db::{
+ borrow_db_checked, borrow_db_mut_checked, save_db, DatabaseAuth, DatabaseImpls,
+ },
+ error::{drop_server_error::DropServerError, remote_access_error::RemoteAccessError},
+ AppState, AppStatus, User, DB,
+};
+
+use super::requests::make_request;
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct InitiateRequestBody {
+ name: String,
+ platform: String,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct HandshakeRequestBody {
+ client_id: String,
+ token: String,
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct HandshakeResponse {
+ private: String,
+ certificate: String,
+ id: String,
+}
+
+// TODO: Change return value on Err
+pub fn sign_nonce(private_key: String, nonce: String) -> Result {
+ let client_private_key = EcKey::private_key_from_pem(private_key.as_bytes()).unwrap();
+ let pkey_private_key = PKey::from_ec_key(client_private_key).unwrap();
+
+ let mut signer = Signer::new(MessageDigest::sha256(), &pkey_private_key).unwrap();
+ signer.update(nonce.as_bytes()).unwrap();
+ let signature = signer.sign_to_vec().unwrap();
+
+ let hex_signature = hex::encode(signature);
+
+ Ok(hex_signature)
+}
+
+pub fn generate_authorization_header() -> String {
+ let certs = {
+ let db = borrow_db_checked();
+ db.auth.clone().unwrap()
+ };
+
+ let nonce = Utc::now().timestamp_millis().to_string();
+
+ let signature = sign_nonce(certs.private, nonce.clone()).unwrap();
+
+ format!("Nonce {} {} {}", certs.client_id, nonce, signature)
+}
+
+pub fn fetch_user() -> Result {
+ let base_url = DB.fetch_base_url();
+
+ let header = generate_authorization_header();
+
+ let client = reqwest::blocking::Client::new();
+ let response = make_request(&client, &["/api/v1/client/user"], &[], |f| {
+ f.header("Authorization", header)
+ })?
+ .send()?;
+ if response.status() != 200 {
+ let err: DropServerError = response.json()?;
+ warn!("{:?}", err);
+
+ if err.status_message == "Nonce expired" {
+ return Err(RemoteAccessError::OutOfSync);
+ }
+
+ return Err(RemoteAccessError::InvalidResponse(err));
+ }
+
+ response.json::().map_err(|e| e.into())
+}
+
+fn recieve_handshake_logic(app: &AppHandle, path: String) -> Result<(), RemoteAccessError> {
+ let path_chunks: Vec<&str> = path.split("/").collect();
+ if path_chunks.len() != 3 {
+ app.emit("auth/failed", ()).unwrap();
+ return Err(RemoteAccessError::HandshakeFailed(
+ "failed to parse token".to_string(),
+ ));
+ }
+
+ let base_url = {
+ let handle = borrow_db_checked();
+ Url::parse(handle.base_url.as_str())?
+ };
+
+ let client_id = path_chunks.get(1).unwrap();
+ let token = path_chunks.get(2).unwrap();
+ let body = HandshakeRequestBody {
+ client_id: client_id.to_string(),
+ token: token.to_string(),
+ };
+
+ let endpoint = base_url.join("/api/v1/client/auth/handshake")?;
+ let client = reqwest::blocking::Client::new();
+ let response = client.post(endpoint).json(&body).send()?;
+ debug!("handshake responsded with {}", response.status().as_u16());
+ let response_struct: HandshakeResponse = response.json()?;
+
+ {
+ let mut handle = borrow_db_mut_checked();
+ handle.auth = Some(DatabaseAuth {
+ private: response_struct.private,
+ cert: response_struct.certificate,
+ client_id: response_struct.id,
+ });
+ drop(handle);
+ save_db();
+ }
+
+ {
+ let app_state = app.state::>();
+ let mut app_state_handle = app_state.lock().unwrap();
+ app_state_handle.status = AppStatus::SignedIn;
+ app_state_handle.user = Some(fetch_user()?);
+ }
+
+ Ok(())
+}
+
+pub fn recieve_handshake(app: AppHandle, path: String) {
+ // Tell the app we're processing
+ app.emit("auth/processing", ()).unwrap();
+
+ let handshake_result = recieve_handshake_logic(&app, path);
+ if let Err(e) = handshake_result {
+ warn!("error with authentication: {}", e);
+ app.emit("auth/failed", e.to_string()).unwrap();
+ return;
+ }
+
+ app.emit("auth/finished", ()).unwrap();
+}
+
+pub fn auth_initiate_logic() -> Result<(), RemoteAccessError> {
+ let base_url = {
+ let db_lock = borrow_db_checked();
+ Url::parse(&db_lock.base_url.clone())?
+ };
+
+ let endpoint = base_url.join("/api/v1/client/auth/initiate")?;
+ let body = InitiateRequestBody {
+ name: "Drop Desktop Client".to_string(),
+ platform: env::consts::OS.to_string(),
+ };
+
+ let client = reqwest::blocking::Client::new();
+ let response = client.post(endpoint.to_string()).json(&body).send()?;
+
+ if response.status() != 200 {
+ let data: DropServerError = response.json()?;
+ error!("could not start handshake: {}", data.status_message);
+
+ return Err(RemoteAccessError::HandshakeFailed(data.status_message));
+ }
+
+ let redir_url = response.text()?;
+ let complete_redir_url = base_url.join(&redir_url)?;
+
+ debug!("opening web browser to continue authentication");
+ webbrowser::open(complete_redir_url.as_ref()).unwrap();
+
+ Ok(())
+}
+
+pub fn setup() -> (AppStatus, Option) {
+ let data = borrow_db_checked();
+ let auth = data.auth.clone();
+ drop(data);
+
+ if auth.is_some() {
+ let user_result = match fetch_user() {
+ Ok(data) => data,
+ Err(RemoteAccessError::FetchError(_)) => return (AppStatus::ServerUnavailable, None),
+ Err(_) => return (AppStatus::SignedInNeedsReauth, None),
+ };
+ return (AppStatus::SignedIn, Some(user_result));
+ }
+
+ (AppStatus::SignedOut, None)
+}
diff --git a/src-tauri/src/remote/commands.rs b/src-tauri/src/remote/commands.rs
new file mode 100644
index 0000000..82781c8
--- /dev/null
+++ b/src-tauri/src/remote/commands.rs
@@ -0,0 +1,78 @@
+use std::sync::Mutex;
+
+use tauri::{AppHandle, Emitter, Manager};
+use url::Url;
+
+use crate::{
+ database::db::{borrow_db_checked, borrow_db_mut_checked, save_db},
+ error::remote_access_error::RemoteAccessError,
+ AppState, AppStatus,
+};
+
+use super::{
+ auth::{auth_initiate_logic, recieve_handshake, setup},
+ remote::use_remote_logic,
+};
+
+#[tauri::command]
+pub fn use_remote(
+ url: String,
+ state: tauri::State<'_, Mutex>>,
+) -> Result<(), RemoteAccessError> {
+ use_remote_logic(url, state)
+}
+
+#[tauri::command]
+pub fn gen_drop_url(path: String) -> Result {
+ let base_url = {
+ let handle = borrow_db_checked();
+
+ Url::parse(&handle.base_url).map_err(RemoteAccessError::ParsingError)?
+ };
+
+ let url = base_url.join(&path).unwrap();
+
+ Ok(url.to_string())
+}
+
+#[tauri::command]
+pub fn sign_out(app: AppHandle) {
+ // Clear auth from database
+ {
+ let mut handle = borrow_db_mut_checked();
+ handle.auth = None;
+ drop(handle);
+ save_db();
+ }
+
+ // Update app state
+ {
+ let app_state = app.state::>();
+ let mut app_state_handle = app_state.lock().unwrap();
+ app_state_handle.status = AppStatus::SignedOut;
+ app_state_handle.user = None;
+ }
+
+ // Emit event for frontend
+ app.emit("auth/signedout", ()).unwrap();
+}
+
+#[tauri::command]
+pub fn retry_connect(state: tauri::State<'_, Mutex>) {
+ let (app_status, user) = setup();
+
+ let mut guard = state.lock().unwrap();
+ guard.status = app_status;
+ guard.user = user;
+ drop(guard);
+}
+
+#[tauri::command]
+pub fn auth_initiate() -> Result<(), RemoteAccessError> {
+ auth_initiate_logic()
+}
+
+#[tauri::command]
+pub fn manual_recieve_handshake(app: AppHandle, token: String) {
+ recieve_handshake(app, format!("handshake/{}", token));
+}
diff --git a/src-tauri/src/remote/mod.rs b/src-tauri/src/remote/mod.rs
new file mode 100644
index 0000000..a2eb666
--- /dev/null
+++ b/src-tauri/src/remote/mod.rs
@@ -0,0 +1,4 @@
+pub mod auth;
+pub mod commands;
+pub mod remote;
+pub mod requests;
diff --git a/src-tauri/src/remote/remote.rs b/src-tauri/src/remote/remote.rs
new file mode 100644
index 0000000..01f2c2b
--- /dev/null
+++ b/src-tauri/src/remote/remote.rs
@@ -0,0 +1,48 @@
+use std::sync::Mutex;
+
+use log::{debug, warn};
+use serde::Deserialize;
+use url::Url;
+
+use crate::{
+ database::db::{borrow_db_mut_checked, save_db},
+ error::remote_access_error::RemoteAccessError,
+ AppState, AppStatus,
+};
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct DropHealthcheck {
+ app_name: String,
+}
+
+pub fn use_remote_logic(
+ url: String,
+ state: tauri::State<'_, Mutex>>,
+) -> Result<(), RemoteAccessError> {
+ debug!("connecting to url {}", url);
+ let base_url = Url::parse(&url)?;
+
+ // Test Drop url
+ let test_endpoint = base_url.join("/api/v1")?;
+ let response = reqwest::blocking::get(test_endpoint.to_string())?;
+
+ let result: DropHealthcheck = response.json()?;
+
+ if result.app_name != "Drop" {
+ warn!("user entered drop endpoint that connected, but wasn't identified as Drop");
+ return Err(RemoteAccessError::InvalidEndpoint);
+ }
+
+ let mut app_state = state.lock().unwrap();
+ app_state.status = AppStatus::SignedOut;
+ drop(app_state);
+
+ let mut db_state = borrow_db_mut_checked();
+ db_state.base_url = base_url.to_string();
+ drop(db_state);
+
+ save_db();
+
+ Ok(())
+}
diff --git a/src-tauri/src/remote/requests.rs b/src-tauri/src/remote/requests.rs
new file mode 100644
index 0000000..44cdc83
--- /dev/null
+++ b/src-tauri/src/remote/requests.rs
@@ -0,0 +1,23 @@
+use reqwest::blocking::{Client, RequestBuilder};
+
+use crate::{database::db::DatabaseImpls, error::remote_access_error::RemoteAccessError, DB};
+
+pub fn make_request, F: FnOnce(RequestBuilder) -> RequestBuilder>(
+ client: &Client,
+ path_components: &[T],
+ query: &[(T, T)],
+ f: F,
+) -> Result {
+ let mut base_url = DB.fetch_base_url();
+ for endpoint in path_components {
+ base_url = base_url.join(endpoint.as_ref())?;
+ }
+ {
+ let mut queries = base_url.query_pairs_mut();
+ for (param, val) in query {
+ queries.append_pair(param.as_ref(), val.as_ref());
+ }
+ }
+ let response = client.get(base_url);
+ Ok(f(response))
+}
diff --git a/src-tauri/src/unpacker.rs b/src-tauri/src/unpacker.rs
deleted file mode 100644
index a85bb89..0000000
--- a/src-tauri/src/unpacker.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-use ciborium::from_reader;
-use rayon::iter::{IntoParallelIterator, ParallelIterator};
-use serde::Deserialize;
-use std::{
- collections::HashMap,
- fs::{create_dir_all, File},
- io::{self, BufReader, Error, Seek, Write},
- os::unix::fs::PermissionsExt,
- path::Path,
-};
-use tauri::Runtime;
-
-#[derive(Deserialize)]
-struct ManifestChunk {
- uuid: String,
- index: i64,
-}
-
-#[derive(Deserialize)]
-struct ManifestRecord {
- chunks: Vec,
- permissions: u32,
-}
-
-#[derive(Deserialize)]
-struct Manifest {
- record: HashMap,
-}
-
-pub async fn unpack() -> Result<(), Error> {
- let chunk_size: u64 = 1024 * 1024 * 16;
-
- let input = Path::new("/home/decduck/Dev/droplet-output");
- let output = Path::new("/home/decduck/Dev/droplet-rebuilt");
-
- let manifest_path = input.join("manifest.drop");
- let manifest_file_handle = File::open(manifest_path).unwrap();
-
- let manifest: Manifest = from_reader(manifest_file_handle).unwrap();
- manifest.record.into_par_iter().for_each(|(key, value)| {
- let file = output.join(key.clone());
- create_dir_all(file.parent().unwrap()).unwrap();
- let mut file_handle = File::create(file).unwrap();
-
- #[cfg(unix)]
- {
- let mut file_permissions = file_handle.metadata().unwrap().permissions();
- file_permissions.set_mode(value.permissions);
- file_handle.set_permissions(file_permissions).unwrap();
- }
-
- for chunk in value.chunks {
- let chunk_path = input.join(chunk.uuid + ".bin");
- let chunk_handle = File::open(chunk_path).unwrap();
-
- let mut chunk_reader = BufReader::new(chunk_handle);
-
- let offset = u64::try_from(chunk.index).unwrap() * chunk_size;
- file_handle.seek(io::SeekFrom::Start(offset)).unwrap();
-
- io::copy(&mut chunk_reader, &mut file_handle).unwrap();
- file_handle.flush().unwrap();
- }
- });
-
- return Ok(());
-}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index f6cd689..1dead92 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,8 +1,8 @@
{
"$schema": "https://schema.tauri.app/config/2.0.0",
- "productName": "drop",
- "version": "0.1.0",
- "identifier": "dev.drop.drop",
+ "productName": "Drop Desktop Client",
+ "version": "0.2.0-beta",
+ "identifier": "dev.drop.app",
"build": {
"beforeDevCommand": "yarn dev --port 1432",
"devUrl": "http://localhost:1432",
@@ -10,16 +10,6 @@
"frontendDist": "../.output/public"
},
"app": {
- "windows": [
- {
- "title": "drop-app",
- "width": 1536,
- "height": 864,
- "minWidth": 820,
- "minHeight": 600,
- "decorations": false
- }
- ],
"security": {
"csp": null
}
@@ -33,12 +23,24 @@
},
"bundle": {
"active": true,
+ "targets": ["nsis", "deb", "rpm", "dmg", "appimage"],
+ "windows": {
+ "nsis": {
+ "installMode": "both"
+ },
+ "webviewInstallMode": {
+ "silent": true,
+ "type": "embedBootstrapper"
+ },
+ "wix": null
+ },
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
- ]
+ ],
+ "externalBin": []
}
}
diff --git a/tailwind.config.js b/tailwind.config.js
index c74301f..91b4f2a 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -16,5 +16,5 @@ export default {
},
},
},
- plugins: [require("@tailwindcss/forms")],
+ plugins: [require("@tailwindcss/forms"), require('@tailwindcss/typography')],
};
diff --git a/types.d.ts b/types.d.ts
deleted file mode 100644
index 32c2816..0000000
--- a/types.d.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum AppStatus {
- NotConfigured = "NotConfigured",
- SignedOut = "SignedOut",
- SignedIn = "SignedIn",
- SignedInNeedsReauth = "SignedInNeedsReauth",
-}
\ No newline at end of file
diff --git a/types.ts b/types.ts
new file mode 100644
index 0000000..60a5b23
--- /dev/null
+++ b/types.ts
@@ -0,0 +1,79 @@
+import type { Component } from "vue";
+
+export type NavigationItem = {
+ prefix: string;
+ route: string;
+ label: string;
+};
+
+export type QuickActionNav = {
+ icon: Component;
+ notifications?: number;
+ action: () => Promise;
+};
+
+export type User = {
+ id: string;
+ username: string;
+ admin: boolean;
+ displayName: string;
+ profilePicture: string;
+};
+
+export type AppState = {
+ status: AppStatus;
+ user?: User;
+};
+
+export type Game = {
+ id: string;
+ mName: string;
+ mShortDescription: string;
+ mDescription: string;
+ mIconId: string;
+ mBannerId: string;
+ mCoverId: string;
+ mImageLibrary: string[];
+};
+
+export enum AppStatus {
+ NotConfigured = "NotConfigured",
+ SignedOut = "SignedOut",
+ SignedIn = "SignedIn",
+ SignedInNeedsReauth = "SignedInNeedsReauth",
+ ServerUnavailable = "ServerUnavailable",
+}
+
+export enum GameStatusEnum {
+ Remote = "Remote",
+ Queued = "Queued",
+ Downloading = "Downloading",
+ Installed = "Installed",
+ Updating = "Updating",
+ Uninstalling = "Uninstalling",
+ SetupRequired = "SetupRequired",
+ Running = "Running"
+}
+
+export type GameStatus = {
+ type: GameStatusEnum;
+ version_name?: string;
+};
+
+export enum DownloadableType {
+ Game = "Game",
+ Tool = "Tool",
+ DLC = "DLC",
+ Mod = "Mod"
+}
+
+export type DownloadableMetadata = {
+ id: string,
+ version: string,
+ downloadType: DownloadableType
+}
+
+export type Settings = {
+ autostart: boolean,
+ maxDownloadThreads: number,
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index b361d19..2c770d1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -301,6 +301,16 @@
dependencies:
mime "^3.0.0"
+"@csstools/selector-resolve-nested@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz#704a9b637975680e025e069a4c58b3beb3e2752a"
+ integrity sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==
+
+"@csstools/selector-specificity@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz#037817b574262134cabd68fc4ec1a454f168407b"
+ integrity sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==
+
"@esbuild/aix-ppc64@0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
@@ -725,6 +735,17 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@koa/router@^12.0.1":
+ version "12.0.2"
+ resolved "https://registry.yarnpkg.com/@koa/router/-/router-12.0.2.tgz#286d51959ed611255faa944818a112e35567835a"
+ integrity sha512-sYcHglGKTxGF+hQ6x67xDfkE9o+NhVlRHBqq6gLywaMc6CojK/5vFZByphdonKinYlMLkEkacm+HEse9HzwgTA==
+ dependencies:
+ debug "^4.3.4"
+ http-errors "^2.0.0"
+ koa-compose "^4.1.0"
+ methods "^1.1.2"
+ path-to-regexp "^6.3.0"
+
"@kwsites/file-exists@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99"
@@ -973,6 +994,25 @@
vite-plugin-checker "^0.8.0"
vue-bundle-renderer "^2.1.0"
+"@nuxtjs/tailwindcss@^6.12.2":
+ version "6.12.2"
+ resolved "https://registry.yarnpkg.com/@nuxtjs/tailwindcss/-/tailwindcss-6.12.2.tgz#3fa41b0b9361cd69ec14934f800e66225b3c9e1f"
+ integrity sha512-qPJiFH67CkTj/2kBGBzqXihOD1rQXMsbVS4vdQvfBxOBLPfGhU1yw7AATdhPl2BBjO2krjJLuZj39t7dnDYOwg==
+ dependencies:
+ "@nuxt/kit" "^3.13.2"
+ autoprefixer "^10.4.20"
+ consola "^3.2.3"
+ defu "^6.1.4"
+ h3 "^1.13.0"
+ klona "^2.0.6"
+ pathe "^1.1.2"
+ postcss "^8.4.47"
+ postcss-nesting "^13.0.0"
+ tailwind-config-viewer "^2.0.4"
+ tailwindcss "~3.4.13"
+ ufo "^1.5.4"
+ unctx "^2.3.1"
+
"@parcel/watcher-android-arm64@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84"
@@ -1245,6 +1285,16 @@
dependencies:
mini-svg-data-uri "^1.2.3"
+"@tailwindcss/typography@^0.5.15":
+ version "0.5.15"
+ resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.15.tgz#007ab9870c86082a1c76e5b3feda9392c7c8d648"
+ integrity sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==
+ dependencies:
+ lodash.castarray "^4.4.0"
+ lodash.isplainobject "^4.0.6"
+ lodash.merge "^4.6.2"
+ postcss-selector-parser "6.0.10"
+
"@tanstack/virtual-core@3.10.8":
version "3.10.8"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz#975446a667755222f62884c19e5c3c66d959b8b4"
@@ -1335,6 +1385,20 @@
dependencies:
"@tauri-apps/api" "^2.0.0"
+"@tauri-apps/plugin-dialog@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.1.tgz#cca38f2ef361c6d92495f5aa12154492cf3fa779"
+ integrity sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==
+ dependencies:
+ "@tauri-apps/api" "^2.0.0"
+
+"@tauri-apps/plugin-os@~2":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-os/-/plugin-os-2.2.0.tgz#ef5511269f59c0ccc580a9d09600034cfaa9743b"
+ integrity sha512-HszbCdbisMlu5QhCNAN8YIWyz2v33abAWha6+uvV2CKX8P5VSct/y+kEe22JeyqrxCnWlQ3DRx7s49Byg7/0EA==
+ dependencies:
+ "@tauri-apps/api" "^2.0.0"
+
"@tauri-apps/plugin-shell@>=2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0.tgz#b6fc88ab070fd5f620e46405715779aa44eb8428"
@@ -1359,6 +1423,24 @@
dependencies:
"@types/node" "*"
+"@types/linkify-it@^5":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76"
+ integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==
+
+"@types/markdown-it@^14.1.2":
+ version "14.1.2"
+ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-14.1.2.tgz#57f2532a0800067d9b934f3521429a2e8bfb4c61"
+ integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==
+ dependencies:
+ "@types/linkify-it" "^5"
+ "@types/mdurl" "^2"
+
+"@types/mdurl@^2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd"
+ integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
+
"@types/node@*":
version "22.7.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc"
@@ -1631,6 +1713,14 @@ abort-controller@^3.0.0:
dependencies:
event-target-shim "^5.0.0"
+accepts@^1.3.5:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
acorn-import-attributes@^1.9.2:
version "1.9.5"
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
@@ -1772,11 +1862,23 @@ async-sema@^3.1.1:
resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.1.1.tgz#e527c08758a0f8f6f9f15f799a173ff3c40ea808"
integrity sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==
+async@^2.6.4:
+ version "2.6.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+ integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
+ dependencies:
+ lodash "^4.17.14"
+
async@^3.2.4:
version "3.2.6"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
+at-least-node@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+ integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
autoprefixer@^10.4.20:
version "10.4.20"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
@@ -1916,6 +2018,14 @@ cac@^6.7.14:
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
+cache-content-type@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c"
+ integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==
+ dependencies:
+ mime-types "^2.1.18"
+ ylru "^1.2.0"
+
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
@@ -1945,7 +2055,7 @@ chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
-chalk@^4.1.1:
+chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -2018,6 +2128,11 @@ cluster-key-slot@^1.1.0:
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+ integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
+
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -2067,6 +2182,11 @@ commander@^4.0.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+commander@^6.0.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
+ integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
+
commander@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
@@ -2118,6 +2238,18 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+content-disposition@~0.5.2:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
@@ -2128,6 +2260,14 @@ cookie-es@^1.1.0, cookie-es@^1.2.2:
resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.2.2.tgz#18ceef9eb513cac1cb6c14bcbf8bdb2679b34821"
integrity sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==
+cookies@~0.9.0:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3"
+ integrity sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==
+ dependencies:
+ depd "~2.0.0"
+ keygrip "~1.1.0"
+
copy-anything@^3.0.2:
version "3.0.5"
resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0"
@@ -2311,6 +2451,25 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@^4.3.5, d
dependencies:
ms "^2.1.3"
+debug@^3.1.0, debug@^3.2.7:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+debug@^4.3.2:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+ dependencies:
+ ms "^2.1.3"
+
+deep-equal@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
+ integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==
+
deepmerge@^4.2.2:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
@@ -2354,17 +2513,22 @@ denque@^2.1.0:
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
-depd@2.0.0:
+depd@2.0.0, depd@^2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+ integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
+
destr@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449"
integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==
-destroy@1.2.0:
+destroy@1.2.0, destroy@^1.0.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
@@ -2471,7 +2635,7 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
-encodeurl@~1.0.2:
+encodeurl@^1.0.2, encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
@@ -2489,7 +2653,7 @@ enhanced-resolve@^5.14.1:
graceful-fs "^4.2.4"
tapable "^2.2.0"
-entities@^4.2.0, entities@^4.5.0:
+entities@^4.2.0, entities@^4.4.0, entities@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
@@ -2597,7 +2761,7 @@ escalade@^3.1.1, escalade@^3.2.0:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
-escape-html@~1.0.3:
+escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
@@ -2742,7 +2906,7 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
-fresh@0.5.2:
+fresh@0.5.2, fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
@@ -2756,6 +2920,16 @@ fs-extra@^11.1.0, fs-extra@^11.2.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
+fs-extra@^9.0.1:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d"
+ integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==
+ dependencies:
+ at-least-node "^1.0.0"
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-minipass@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@@ -2878,7 +3052,7 @@ glob@^10.0.0, glob@^10.3.10:
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
-glob@^7.1.3:
+glob@^7.1.3, glob@^7.2.0:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -2937,7 +3111,7 @@ gzip-size@^7.0.0:
dependencies:
duplexer "^0.1.2"
-h3@^1.12.0:
+h3@^1.12.0, h3@^1.13.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/h3/-/h3-1.13.0.tgz#b5347a8936529794b6754b440e26c0ab8a60dceb"
integrity sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==
@@ -2963,6 +3137,18 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+has-symbols@^1.0.3:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -2990,7 +3176,15 @@ html-tags@^3.3.1:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
-http-errors@2.0.0:
+http-assert@^1.3.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
+ integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==
+ dependencies:
+ deep-equal "~1.0.1"
+ http-errors "~1.8.0"
+
+http-errors@2.0.0, http-errors@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
@@ -3001,6 +3195,27 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
+http-errors@^1.6.3, http-errors@^1.7.3, http-errors@~1.8.0:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
+ integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses ">= 1.5.0 < 2"
+ toidentifier "1.0.1"
+
+http-errors@~1.6.2:
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+ integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.3"
+ setprototypeof "1.1.0"
+ statuses ">= 1.4.0 < 2"
+
http-shutdown@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/http-shutdown/-/http-shutdown-1.2.2.tgz#41bc78fc767637c4c95179bc492f312c0ae64c5f"
@@ -3073,6 +3288,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+inherits@2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==
+
ini@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ini/-/ini-4.1.1.tgz#d95b3d843b1e906e56d6747d5447904ff50ce7a1"
@@ -3117,6 +3337,13 @@ is-core-module@^2.13.0:
dependencies:
hasown "^2.0.2"
+is-core-module@^2.16.0:
+ version "2.16.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
+ integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+ dependencies:
+ hasown "^2.0.2"
+
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@@ -3137,6 +3364,13 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+is-generator-function@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
+ integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
@@ -3203,7 +3437,7 @@ is-what@^4.1.8:
resolved "https://registry.yarnpkg.com/is-what/-/is-what-4.1.16.tgz#1ad860a19da8b4895ad5495da3182ce2acdd7a6f"
integrity sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==
-is-wsl@^2.2.0:
+is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -3289,6 +3523,13 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+keygrip@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
+ integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==
+ dependencies:
+ tsscmp "1.0.6"
+
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@@ -3304,6 +3545,65 @@ knitwork@^1.0.0, knitwork@^1.1.0:
resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.1.0.tgz#d8c9feafadd7ee744ff64340b216a52c7199c417"
integrity sha512-oHnmiBUVHz1V+URE77PNot2lv3QiYU2zQf1JjOVkMt3YDKGbu8NAFr+c4mcNOhdsGrB/VpVbRwPwhiXrPhxQbw==
+koa-compose@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877"
+ integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==
+
+koa-convert@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5"
+ integrity sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==
+ dependencies:
+ co "^4.6.0"
+ koa-compose "^4.1.0"
+
+koa-send@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.1.tgz#39dceebfafb395d0d60beaffba3a70b4f543fe79"
+ integrity sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==
+ dependencies:
+ debug "^4.1.1"
+ http-errors "^1.7.3"
+ resolve-path "^1.4.0"
+
+koa-static@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943"
+ integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==
+ dependencies:
+ debug "^3.1.0"
+ koa-send "^5.0.0"
+
+koa@^2.14.2:
+ version "2.15.3"
+ resolved "https://registry.yarnpkg.com/koa/-/koa-2.15.3.tgz#062809266ee75ce0c75f6510a005b0e38f8c519a"
+ integrity sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==
+ dependencies:
+ accepts "^1.3.5"
+ cache-content-type "^1.0.0"
+ content-disposition "~0.5.2"
+ content-type "^1.0.4"
+ cookies "~0.9.0"
+ debug "^4.3.2"
+ delegates "^1.0.0"
+ depd "^2.0.0"
+ destroy "^1.0.4"
+ encodeurl "^1.0.2"
+ escape-html "^1.0.3"
+ fresh "~0.5.2"
+ http-assert "^1.3.0"
+ http-errors "^1.6.3"
+ is-generator-function "^1.0.7"
+ koa-compose "^4.1.0"
+ koa-convert "^2.0.0"
+ on-finished "^2.3.0"
+ only "~0.0.2"
+ parseurl "^1.3.2"
+ statuses "^1.5.0"
+ type-is "^1.6.16"
+ vary "^1.1.2"
+
kolorist@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
@@ -3334,11 +3634,23 @@ lilconfig@^3.0.0, lilconfig@^3.1.2:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb"
integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==
+lilconfig@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
+ integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
+
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+linkify-it@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-5.0.0.tgz#9ef238bfa6dc70bd8e7f9572b52d369af569b421"
+ integrity sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==
+ dependencies:
+ uc.micro "^2.0.0"
+
listhen@^1.7.2:
version "1.9.0"
resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.9.0.tgz#59355f7e4fc1eefda6bc494ae7e9ed13aa7658ef"
@@ -3371,6 +3683,11 @@ local-pkg@^0.5.0:
mlly "^1.4.2"
pkg-types "^1.0.3"
+lodash.castarray@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
+ integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==
+
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@@ -3381,17 +3698,27 @@ lodash.isarguments@^3.1.0:
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
+lodash.isplainobject@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+ integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==
-lodash@^4.17.15:
+lodash@^4.17.14, lodash@^4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3438,6 +3765,18 @@ make-dir@^3.1.0:
dependencies:
semver "^6.0.0"
+markdown-it@^14.1.0:
+ version "14.1.0"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.1.0.tgz#3c3c5992883c633db4714ccb4d7b5935d98b7d45"
+ integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==
+ dependencies:
+ argparse "^2.0.1"
+ entities "^4.4.0"
+ linkify-it "^5.0.0"
+ mdurl "^2.0.0"
+ punycode.js "^2.3.1"
+ uc.micro "^2.1.0"
+
mdn-data@2.0.28:
version "2.0.28"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba"
@@ -3448,6 +3787,16 @@ mdn-data@2.0.30:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+mdurl@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
+ integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -3458,7 +3807,12 @@ merge2@^1.3.0:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
-micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
+methods@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
@@ -3466,6 +3820,18 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.3"
picomatch "^2.3.1"
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.18, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
mime@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@@ -3512,6 +3878,11 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.1"
+minimist@^1.2.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+
minipass@^3.0.0:
version "3.3.6"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
@@ -3542,6 +3913,13 @@ mitt@^3.0.1:
resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
+mkdirp@^0.5.6:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
+ integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
+ dependencies:
+ minimist "^1.2.6"
+
mkdirp@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
@@ -3572,7 +3950,7 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-ms@2.1.3, ms@^2.1.3:
+ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -3606,6 +3984,11 @@ napi-wasm@^1.1.0:
resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.3.tgz#7bb95c88e6561f84880bb67195437b1cfbe99224"
integrity sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg==
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
nitropack@^2.9.7:
version "2.9.7"
resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.9.7.tgz#66da772d14d0e364dd17adbd439f0b1e44112a8d"
@@ -3874,7 +4257,7 @@ ometa@0.2.2:
resolved "https://registry.yarnpkg.com/ometa/-/ometa-0.2.2.tgz#f53c4735ba6d56af5a46b04dfb7c4334c596d44e"
integrity sha512-LZuoK/yjU3FvrxPjUXUlZ1bavCfBPqauA7fsNdwi+AVhRdyk2IzgP3JRnevvjzQ6fKHdUw8YISshf53FmpHrng==
-on-finished@2.4.1:
+on-finished@2.4.1, on-finished@^2.3.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
@@ -3895,6 +4278,11 @@ onetime@^6.0.0:
dependencies:
mimic-fn "^4.0.0"
+only@~0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4"
+ integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==
+
open@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1"
@@ -3905,6 +4293,14 @@ open@^10.1.0:
is-inside-container "^1.0.0"
is-wsl "^3.1.0"
+open@^7.0.4:
+ version "7.4.2"
+ resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+ integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+ dependencies:
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
+
open@^8.4.0:
version "8.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9"
@@ -3958,12 +4354,12 @@ parse-url@^8.1.0:
dependencies:
parse-path "^7.0.0"
-parseurl@~1.3.3:
+parseurl@^1.3.2, parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
-path-is-absolute@^1.0.0:
+path-is-absolute@1.0.1, path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
@@ -3991,6 +4387,11 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+path-to-regexp@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
+ integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
+
path-type@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8"
@@ -4011,6 +4412,11 @@ picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59"
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
+picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
@@ -4040,6 +4446,15 @@ pkg-types@^1.0.3, pkg-types@^1.1.1, pkg-types@^1.2.0:
mlly "^1.7.1"
pathe "^1.1.2"
+portfinder@^1.0.26:
+ version "1.0.32"
+ resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81"
+ integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
+ dependencies:
+ async "^2.6.4"
+ debug "^3.2.7"
+ mkdirp "^0.5.6"
+
postcss-calc@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-10.0.2.tgz#15f01635a27b9d38913a98c4ef2877f5b715b439"
@@ -4104,7 +4519,7 @@ postcss-js@^4.0.1:
dependencies:
camelcase-css "^2.0.1"
-postcss-load-config@^4.0.1:
+postcss-load-config@^4.0.1, postcss-load-config@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3"
integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==
@@ -4163,13 +4578,22 @@ postcss-minify-selectors@^7.0.4:
cssesc "^3.0.0"
postcss-selector-parser "^6.1.2"
-postcss-nested@^6.0.1:
+postcss-nested@^6.0.1, postcss-nested@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.2.0.tgz#4c2d22ab5f20b9cb61e2c5c5915950784d068131"
integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==
dependencies:
postcss-selector-parser "^6.1.1"
+postcss-nesting@^13.0.0:
+ version "13.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-13.0.1.tgz#c405796d7245a3e4c267a9956cacfe9670b5d43e"
+ integrity sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==
+ dependencies:
+ "@csstools/selector-resolve-nested" "^3.0.0"
+ "@csstools/selector-specificity" "^5.0.0"
+ postcss-selector-parser "^7.0.0"
+
postcss-normalize-charset@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-7.0.0.tgz#92244ae73c31bf8f8885d5f16ff69e857ac6c001"
@@ -4255,6 +4679,14 @@ postcss-reduce-transforms@^7.0.0:
dependencies:
postcss-value-parser "^4.2.0"
+postcss-selector-parser@6.0.10:
+ version "6.0.10"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
+ integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
@@ -4263,6 +4695,14 @@ postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1, postcss-selecto
cssesc "^3.0.0"
util-deprecate "^1.0.2"
+postcss-selector-parser@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz#41bd8b56f177c093ca49435f65731befe25d6b9c"
+ integrity sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
postcss-svgo@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-7.0.1.tgz#2b63571d8e9568384df334bac9917baff4d23f58"
@@ -4320,6 +4760,11 @@ protocols@^2.0.0, protocols@^2.0.1:
resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86"
integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==
+punycode.js@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7"
+ integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==
+
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -4421,6 +4866,15 @@ redis-parser@^3.0.0:
dependencies:
redis-errors "^1.0.0"
+replace-in-file@^6.1.0:
+ version "6.3.5"
+ resolved "https://registry.yarnpkg.com/replace-in-file/-/replace-in-file-6.3.5.tgz#ff956b0ab5bc96613207d603d197cd209400a654"
+ integrity sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==
+ dependencies:
+ chalk "^4.1.2"
+ glob "^7.2.0"
+ yargs "^17.2.1"
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -4431,6 +4885,14 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+resolve-path@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7"
+ integrity sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==
+ dependencies:
+ http-errors "~1.6.2"
+ path-is-absolute "1.0.1"
+
resolve@^1.1.7, resolve@^1.22.1, resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
@@ -4440,6 +4902,15 @@ resolve@^1.1.7, resolve@^1.22.1, resolve@^1.22.2:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+resolve@^1.22.8:
+ version "1.22.10"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
+ integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
+ dependencies:
+ is-core-module "^2.16.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -4511,7 +4982,7 @@ rxjs@^7.4.0:
dependencies:
tslib "^2.1.0"
-safe-buffer@^5.1.0, safe-buffer@~5.2.0:
+safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -4725,6 +5196,11 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+setprototypeof@1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+ integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
+
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@@ -4790,6 +5266,11 @@ smob@^1.0.0:
resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
+sortablejs@1.14.0:
+ version "1.14.0"
+ resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
+ integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
+
source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -4828,6 +5309,11 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
+
std-env@^3.7.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2"
@@ -4926,7 +5412,7 @@ stylehacks@^7.0.4:
browserslist "^4.23.3"
postcss-selector-parser "^6.1.2"
-sucrase@^3.32.0:
+sucrase@^3.32.0, sucrase@^3.35.0:
version "3.35.0"
resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263"
integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==
@@ -5000,6 +5486,20 @@ system-architecture@^0.1.0:
resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d"
integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==
+tailwind-config-viewer@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/tailwind-config-viewer/-/tailwind-config-viewer-2.0.4.tgz#5f47ef0f0ba3719557f88628de8bf276cad7a4cb"
+ integrity sha512-icvcmdMmt9dphvas8wL40qttrHwAnW3QEN4ExJ2zICjwRsPj7gowd1cOceaWG3IfTuM/cTNGQcx+bsjMtmV+cw==
+ dependencies:
+ "@koa/router" "^12.0.1"
+ commander "^6.0.0"
+ fs-extra "^9.0.1"
+ koa "^2.14.2"
+ koa-static "^5.0.0"
+ open "^7.0.4"
+ portfinder "^1.0.26"
+ replace-in-file "^6.1.0"
+
tailwindcss@^3.4.13:
version "3.4.13"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.13.tgz#3d11e5510660f99df4f1bfb2d78434666cb8f831"
@@ -5028,6 +5528,34 @@ tailwindcss@^3.4.13:
resolve "^1.22.2"
sucrase "^3.32.0"
+tailwindcss@~3.4.13:
+ version "3.4.17"
+ resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.17.tgz#ae8406c0f96696a631c790768ff319d46d5e5a63"
+ integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==
+ dependencies:
+ "@alloc/quick-lru" "^5.2.0"
+ arg "^5.0.2"
+ chokidar "^3.6.0"
+ didyoumean "^1.2.2"
+ dlv "^1.1.3"
+ fast-glob "^3.3.2"
+ glob-parent "^6.0.2"
+ is-glob "^4.0.3"
+ jiti "^1.21.6"
+ lilconfig "^3.1.3"
+ micromatch "^4.0.8"
+ normalize-path "^3.0.0"
+ object-hash "^3.0.0"
+ picocolors "^1.1.1"
+ postcss "^8.4.47"
+ postcss-import "^15.1.0"
+ postcss-js "^4.0.1"
+ postcss-load-config "^4.0.2"
+ postcss-nested "^6.2.0"
+ postcss-selector-parser "^6.1.2"
+ resolve "^1.22.8"
+ sucrase "^3.35.0"
+
tapable@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
@@ -5143,6 +5671,11 @@ tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
+tsscmp@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb"
+ integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==
+
type-fest@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
@@ -5153,6 +5686,19 @@ type-fest@^3.8.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==
+type-is@^1.6.16:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+uc.micro@^2.0.0, uc.micro@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
+ integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==
+
ufo@^1.1.2, ufo@^1.5.3, ufo@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
@@ -5346,6 +5892,11 @@ varint@^6.0.0:
resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0"
integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==
+vary@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
vite-hot-client@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/vite-hot-client/-/vite-hot-client-0.2.3.tgz#db52aba46edbcfa7906dbca8255fd35b9a9270b2"
@@ -5496,6 +6047,13 @@ vue@^3.5.5, vue@latest:
"@vue/server-renderer" "3.5.11"
"@vue/shared" "3.5.11"
+vuedraggable@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
+ integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
+ dependencies:
+ sortablejs "1.14.0"
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@@ -5597,7 +6155,7 @@ yargs-parser@^21.1.1:
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
-yargs@^17.5.1:
+yargs@^17.2.1, yargs@^17.5.1:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
@@ -5610,6 +6168,11 @@ yargs@^17.5.1:
y18n "^5.0.5"
yargs-parser "^21.1.1"
+ylru@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.4.0.tgz#0cf0aa57e9c24f8a2cbde0cc1ca2c9592ac4e0f6"
+ integrity sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==
+
zhead@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/zhead/-/zhead-2.2.4.tgz#87cd1e2c3d2f465fa9f43b8db23f9716dfe6bed7"