diff --git a/.dockerignore b/.dockerignore index e7a6de43..4f976f2d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,52 +1,28 @@ -.editorconfig -.npmrc -.nvmrc - +.git .github -docs - -**/node_modules -**/.next -**/dist -**.md - -# Dependencies -.pnp -.pnp.js - -# Local env files -.env -.env.local -.env.development.local -.env.test.local -.env.production.local -# Testing -coverage - -# Turbo .turbo +node_modules +**/node_modules -# Vercel -.vercel - -# Build Outputs -out/ -/build -**/dist - -# Debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* +target +target-* +apps/web/.next +apps/web/out +**/.next +**/.turbo +apps/android +apps/windows +artifacts -**/prisma/music.db +docs +scripts +tests -# Misc -.DS_Store -*.pem +**/bin +**/obj +**/__pycache__ -# Cargo -**/target -**/*.log -.git \ No newline at end of file +.env +*.log +*.db diff --git a/.env.example b/.env.example index f4f9edd7..76940a29 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,22 @@ -JWT_SECRET= -NEXT_PUBLIC_AI_URL= \ No newline at end of file +# Parson needs no secrets or settings for a standard installation. +# Uncomment only the values your deployment needs. +# PARSON_PORT=1993 +# PARSON_LIBRARY_NAME=Parson Library +# PARSON_DATA_DIR=/Parson +# PARSON_PUBLIC_URL=https://parson.dev +# PARSON_DATABASE_SNAPSHOTS=true +# PARSON_DATABASE_SNAPSHOT_GENERATIONS=3 +# PARSON_DATABASE_INTEGRITY_INTERVAL_HOURS=168 +# PARSON_TRANSCODE_CACHE_MAX_BYTES=2147483648 +# Filesystem discovery defaults are tuned for local SSD/NVMe libraries. +# PARSON_SCAN_THREADS=8 +# PARSON_SCAN_INITIAL_CAPACITY=4096 +# PARSON_SCAN_SKIP_HIDDEN=true +# PARSON_SCAN_SKIP_SYSTEM=true +# Semicolon- or comma-separated glob patterns, relative to the library root. +# PARSON_SCAN_EXCLUDED_DIRS=**/.cache/**;**/@eaDir/**;**/System Volume Information/** +# Notification queues fall back to a full walk if this many paths accumulate. +# PARSON_SCAN_MAX_PENDING_CHANGES=4096 +# Periodic repair walk for missed filesystem notifications (0 means every scan). +# PARSON_SCAN_RECONCILIATION_HOURS=24 +# RUST_LOG=info diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f4f11226 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf + +*.png binary +*.ico binary +*.icns binary +*.webp binary +*.woff2 binary diff --git a/.github/workflows/rust/cargo-check.yml b/.github/workflows/rust/cargo-check.yml deleted file mode 100644 index c6d5bed2..00000000 --- a/.github/workflows/rust/cargo-check.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "Check Server" - -on: - push: - pull_request: - -jobs: - check: - name: cargo check - runs-on: ubuntu-latest - strategy: - matrix: - toolchain: [stable, beta, nightly] - steps: - - uses: actions/checkout@v4 - - name: Install NASM - run: sudo apt-get update && sudo apt-get install -y nasm - - name: Create necessary folders - run: mkdir -p apps/web/out - - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - components: clippy,rustfmt - cache: true - cache-on-failure: true - matcher: true - rustflags: "" - - run: cargo check \ No newline at end of file diff --git a/.gitignore b/.gitignore index 469bc195..f9791254 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,34 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +node_modules/ -# Dependencies -node_modules -.pnp -.pnp.js - -# Local env files -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Testing -coverage - -# Turbo -.turbo - -# Vercel -.vercel - -# Build Outputs .next/ +.turbo/ out/ -build -dist - -# Debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* +dist/ +target/ +target-*/ +artifacts/ +coverage/ +**/bin/ +**/obj/ +**/.source/ +**/next-env.d.ts +*.tsbuildinfo + +apps/mobile/.expo/ +apps/mobile/android/ +apps/mobile/ios/ -# Misc -.DS_Store +.env +.env.*.local +.vercel/ +*.db +*.sqlite +*.sqlite3 *.pem -# Cargo -/target +*.log +*.lockb +*.py[cod] +__pycache__/ -lrclib* -deprecated -data.ms -meilisearch +.DS_Store diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..a7168be9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,22 @@ +node_modules/ +**/node_modules/ + +.next/ +**/.next/ +out/ +dist/ +.turbo/ +target/ +target-*/ +artifacts/ +**/build/ +**/bin/ +**/obj/ +**/.source/ + +apps/mobile/android/ +apps/mobile/ios/ + +Cargo.lock +bun.lock +LICENSE diff --git a/Cargo.lock b/Cargo.lock index f35f1cdc..701dc0b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "actix-codec" @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "bytes", "futures-core", "futures-sink", @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e772b3bcafe335042b5db010ab7c09013dad6eac4915c91d8d50902769f331" +checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d" dependencies = [ "actix-utils", "actix-web", @@ -34,73 +34,37 @@ dependencies = [ "smallvec", ] -[[package]] -name = "actix-embed" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c98712e024dd57b911850c78d6fc5277e8be54afcdef70d6f4b628812c07dcf7" -dependencies = [ - "actix-web", - "futures-core", - "hex", - "mime_guess", - "rust-embed 6.8.1", -] - -[[package]] -name = "actix-files" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be" -dependencies = [ - "actix-http", - "actix-service", - "actix-utils", - "actix-web", - "bitflags 2.6.0", - "bytes", - "derive_more", - "futures-core", - "http-range", - "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project-lite", - "v_htmlescape", -] - [[package]] name = "actix-http" -version = "3.9.0" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", - "ahash", - "base64 0.22.1", - "bitflags 2.6.0", + "base64", + "bitflags 2.13.0", "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", "flate2", + "foldhash", "futures-core", - "h2 0.3.26", + "h2", "http 0.2.12", "httparse", "httpdate", - "itoa 1.0.11", + "itoa", "language-tags", "local-channel", "mime", "percent-encoding", "pin-project-lite", - "rand 0.8.5", + "rand 0.10.2", "sha1", "smallvec", "tokio", @@ -116,14 +80,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "actix-multipart" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5118a26dee7e34e894f7e85aa0ee5080ae4c18bf03c0e30d49a80e418f00a53" +checksum = "560e3dd4eae03837f86d1b6bf6222c508568eff36845ef5ebb3a0dff480e3f64" dependencies = [ "actix-multipart-derive", "actix-utils", @@ -136,7 +100,7 @@ dependencies = [ "log", "memchr", "mime", - "rand 0.8.5", + "rand 0.10.2", "serde", "serde_json", "serde_plain", @@ -146,22 +110,22 @@ dependencies = [ [[package]] name = "actix-multipart-derive" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11eb847f49a700678ea2fa73daeb3208061afa2b9d1a8527c03390f4c4a1c6b" +checksum = "8720bceaa6797fd8b2deab968d52e1120b2a8c30950939f6c8cdb42a910bc885" dependencies = [ - "darling", - "parse-size", + "bytesize", + "darling 0.23.0", "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -174,9 +138,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ "futures-core", "tokio", @@ -184,9 +148,9 @@ dependencies = [ [[package]] name = "actix-server" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" dependencies = [ "actix-rt", "actix-service", @@ -194,19 +158,18 @@ dependencies = [ "futures-core", "futures-util", "mio", - "socket2", + "socket2 0.5.10", "tokio", "tracing", ] [[package]] name = "actix-service" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" dependencies = [ "futures-core", - "paste", "pin-project-lite", ] @@ -222,9 +185,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.9.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -235,17 +198,17 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "ahash", "bytes", "bytestring", "cfg-if", "cookie", "derive_more", "encoding_rs", + "foldhash", "futures-core", "futures-util", "impl-more", - "itoa 1.0.11", + "itoa", "language-tags", "log", "mime", @@ -257,8 +220,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2", + "socket2 0.6.4", "time", + "tracing", "url", ] @@ -271,7 +235,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] @@ -282,92 +246,61 @@ checksum = "456348ed9dcd72a13a1f4a660449fafdecee9ac8205552e286809eb5b0b29bd3" dependencies = [ "actix-utils", "actix-web", - "base64 0.22.1", + "base64", "futures-core", "futures-util", "log", "pin-project-lite", ] -[[package]] -name = "actix-web-rust-embed-responder" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ecbc5efd67fac66e7b4f37ad693cbd9d035cf6174afb5d87f183f887e935c6" -dependencies = [ - "actix-web", - "base64 0.21.7", - "brotli", - "chrono", - "flate2", - "futures-core", - "lazy_static", - "regex", - "rust-embed 8.5.0", - "rust-embed-for-web", -] - [[package]] name = "actix-ws" -version = "0.2.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535aec173810be3ca6f25dd5b4d431ae7125d62000aa3cbae1ec739921b02cf3" +checksum = "decf53c3cdd63dd6f289980b430238f9a2f6d19f8bce8e418272e08d3da43f0f" dependencies = [ "actix-codec", "actix-http", "actix-web", + "bytestring", "futures-core", + "futures-sink", "tokio", + "tokio-util", ] -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] -name = "ahash" -version = "0.8.11" +name = "aho-corasick" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ - "cfg-if", - "getrandom 0.2.15", - "once_cell", - "version_check", - "zerocopy", + "memchr", ] [[package]] -name = "aho-corasick" -version = "1.1.3" +name = "aligned" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" dependencies = [ - "memchr", + "as-slice", ] [[package]] name = "aligned-vec" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] [[package]] name = "alloc-no-stdlib" @@ -377,19 +310,13 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -401,28 +328,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" - -[[package]] -name = "app" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tauri", - "tauri-build", -] +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" [[package]] name = "arg_enum_proc_macro" @@ -432,7 +346,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] @@ -443,49 +357,23 @@ checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", - "cpufeatures", + "cpufeatures 0.2.17", "password-hash", ] [[package]] name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-trait" -version = "0.1.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "atk" -version = "0.15.1" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "glib", - "libc", -] +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] -name = "atk-sys" -version = "0.15.1" +name = "as-slice" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", + "stable_deref_trait", ] [[package]] @@ -495,36 +383,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "audiotags" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e797ce0164cf599c71f2c3849b56301d96a3dc033544588e875686b050ed39" -dependencies = [ - "audiotags-macro", - "id3", - "metaflac", - "mp4ameta", - "readme-rustdocifier", - "thiserror", -] - -[[package]] -name = "audiotags-macro" -version = "0.2.0" +name = "autocfg" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa9b2312fc01f7291f3b7b0f52ed08b1c0177c96a2e696ab55695cc4d06889" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "autocfg" -version = "1.3.0" +name = "av-scenechange" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror", + "v_frame", + "y4m", +] [[package]] name = "av1-grain" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" dependencies = [ "anyhow", "arrayvec", @@ -536,39 +424,36 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" dependencies = [ "arrayvec", ] [[package]] -name = "backtrace" -version = "0.3.73" +name = "aws-lc-rs" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.7.4", - "object", - "rustc-demangle", + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", ] [[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" +name = "aws-lc-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] [[package]] name = "base64" @@ -578,21 +463,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "base85rs" -version = "0.1.3" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87678d33a2af71f019ed11f52db246ca6c5557edee2cccbe689676d1ad9c6b5a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ "autocfg", "libm", @@ -601,27 +480,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -630,15 +488,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "bitstream-io" -version = "2.5.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcde5f311c85b8ca30c2e4198d4326bc342c76541590106f5fa4a50946ea499" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] [[package]] name = "blake2" @@ -646,15 +507,9 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block-buffer" version = "0.10.4" @@ -664,11 +519,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "brotli" -version = "6.0.0" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -677,9 +541,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.1" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -687,31 +551,31 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" dependencies = [ "memchr", - "serde", + "serde_core", ] [[package]] name = "built" -version = "0.7.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" [[package]] name = "byteorder" @@ -727,156 +591,83 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" - -[[package]] -name = "bytestring" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" -dependencies = [ - "bytes", -] - -[[package]] -name = "cairo-rs" -version = "0.15.12" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "glib", - "libc", - "thiserror", -] +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] -name = "cairo-sys-rs" -version = "0.15.1" +name = "bytesize" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", -] +checksum = "3d7c8918969267b2932ffd5655509bbbea0833823058c378876953217f5fc50e" [[package]] -name = "cargo_toml" -version = "0.15.3" +name = "bytestring" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ - "serde", - "toml 0.7.8", + "bytes", ] [[package]] name = "cc" -version = "1.1.10" +version = "1.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" dependencies = [ + "find-msvc-tools", "jobserver", "libc", + "shlex", ] [[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "cfg-expr" -version = "0.9.1" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" -dependencies = [ - "smallvec", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "cfg-expr" -version = "0.15.8" +name = "chacha20" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ - "smallvec", - "target-lexicon", + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", ] -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", + "windows-link", ] [[package]] -name = "cocoa-foundation" -version = "0.1.2" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", + "cc", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "combine" version = "4.6.7" @@ -888,35 +679,16 @@ dependencies = [ ] [[package]] -name = "console" -version = "0.15.8" +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.52.0", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "convert_case" -version = "0.6.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -934,9 +706,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -949,61 +721,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core-graphics" -version = "0.22.3" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", "libc", ] [[package]] -name = "core-graphics-types" -version = "0.1.3" +name = "cpufeatures" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "bitflags 1.3.2", - "core-foundation", "libc", ] [[package]] -name = "cpufeatures" -version = "0.2.13" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "libc", + "cfg-if", ] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "crossbeam" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ - "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", ] [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1011,232 +781,188 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" +name = "crossbeam-queue" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +dependencies = [ + "crossbeam-utils", +] [[package]] -name = "crunchy" -version = "0.2.2" +name = "crossbeam-utils" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] [[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 1.0.11", - "phf 0.11.2", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" +name = "crypto-common" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ - "quote", - "syn 2.0.74", + "hybrid-array", ] [[package]] -name = "ctor" -version = "0.2.8" +name = "darling" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "quote", - "syn 2.0.74", + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] -name = "curve25519-dalek" -version = "4.1.3" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" +name = "darling_core" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", - "syn 2.0.74", -] - -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", + "strsim", + "syn", ] [[package]] name = "darling_core" -version = "0.20.10" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.74", + "syn", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", "quote", - "syn 2.0.74", + "syn", ] [[package]] -name = "data-encoding" -version = "2.6.0" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn", +] [[package]] -name = "der" -version = "0.7.9" +name = "data-encoding" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" -dependencies = [ - "const-oid", - "zeroize", -] +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] -name = "derive_arbitrary" -version = "1.3.2" +name = "derive_more" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "derive_more-impl", ] [[package]] -name = "derive_more" -version = "0.99.18" +name = "derive_more-impl" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case 0.4.0", + "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 2.0.74", + "syn", + "unicode-xid", ] [[package]] name = "diesel" -version = "2.2.2" +version = "2.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94" +checksum = "e54d1f576cd3a3460f212a4615fd12ce1b6303c095b79a44449ffbe627753dc1" dependencies = [ "bigdecimal", "chrono", "diesel_derives", + "downcast-rs", "libsqlite3-sys", "num-bigint", "num-integer", "num-traits", "r2d2", + "sqlite-wasm-rs", "time", ] [[package]] name = "diesel_derives" -version = "2.2.2" +version = "2.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ff2be1e7312c858b2ef974f5c7089833ae57b5311b334b30923af58e5718d8" +checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "diesel_migrations" -version = "2.2.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" +checksum = "28d0f4a98124ba6d4ca75da535f65984badec16a003b6e2f94a01e31a79490b8" dependencies = [ "diesel", "migrations_internals", @@ -1245,11 +971,11 @@ dependencies = [ [[package]] name = "diesel_table_macro_syntax" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" dependencies = [ - "syn 2.0.74", + "syn", ] [[package]] @@ -1258,68 +984,52 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] [[package]] -name = "dirs" -version = "5.0.1" +name = "digest" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "dirs-sys", + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "cfg-if", - "dirs-sys-next", + "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", + "windows-sys 0.61.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] @@ -1328,33 +1038,24 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast-rs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" + [[package]] name = "dsl_auto_type" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e" dependencies = [ - "darling", + "darling 0.21.3", "either", - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.74", -] - -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", + "syn", ] [[package]] @@ -1363,201 +1064,110 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "signature", - "subtle", - "zeroize", -] - -[[package]] -name = "ego-tree" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" - [[package]] name = "either" -version = "1.13.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -dependencies = [ - "serde", -] +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embed-resource" -version = "2.4.3" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.8.19", + "toml 1.1.2+spec-1.1.0", "vswhom", "winreg", ] -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - [[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", ] [[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.9" +name = "equator" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" dependencies = [ - "libc", - "windows-sys 0.52.0", + "equator-macro", ] [[package]] -name = "exr" -version = "1.72.0" +name = "equator-macro" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide 0.7.4", - "rayon-core", - "smallvec", - "zune-inflate", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "fancy-regex" -version = "0.13.0" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "fastrand" -version = "1.9.0" +name = "errno" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ - "instant", + "libc", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.25" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.0.33" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", -] - -[[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", + "miniz_oxide", ] [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ + "futures-core", + "futures-sink", "spin", ] @@ -1568,44 +1178,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "foldhash" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] -name = "futf" -version = "0.1.5" +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ - "mac", - "new_debug_unreachable", + "libc", ] [[package]] name = "futures" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1618,9 +1224,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1628,15 +1234,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1645,38 +1251,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1686,342 +1292,82 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] [[package]] -name = "fxhash" -version = "0.2.1" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" -dependencies = [ - "bitflags 1.3.2", - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gdk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkx11-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps 6.2.2", - "x11", -] - -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] -name = "getopts" -version = "0.2.21" +name = "getrandom" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "unicode-width", + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "r-efi 5.3.0", + "wasip2", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "r-efi 6.0.0", + "rand_core 0.10.1", "wasm-bindgen", ] -[[package]] -name = "gif" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gimli" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" - -[[package]] -name = "gio" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-io", - "gio-sys", - "glib", - "libc", - "once_cell", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", - "winapi", -] - -[[package]] -name = "glib" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "once_cell", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.15.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" -dependencies = [ - "anyhow", - "heck 0.4.1", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "glib-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" -dependencies = [ - "libc", - "system-deps 6.2.2", -] - [[package]] name = "glob" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "gobject-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gtk" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" -dependencies = [ - "atk", - "bitflags 1.3.2", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps 6.2.2", -] - -[[package]] -name = "gtk3-macros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" -dependencies = [ - "anyhow", - "proc-macro-crate", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "regex-automata", + "regex-syntax", ] [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", @@ -2029,68 +1375,27 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.3.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap 2.3.0", + "indexmap", "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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "heck" -version = "0.3.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ - "unicode-segmentation", + "foldhash", ] [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -2098,46 +1403,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 = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever 0.11.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "html5ever" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" -dependencies = [ - "log", - "mac", - "markup5ever 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "http" version = "0.2.12" @@ -2146,54 +1411,47 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.11", + "itoa", ] [[package]] name = "http" -version = "1.1.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", - "fnv", - "itoa 1.0.11", + "itoa", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.1.0", + "http 1.4.2", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", - "futures-util", - "http 1.1.0", + "futures-core", + "http 1.4.2", "http-body", "pin-project-lite", ] -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - [[package]] name = "httparse" -version = "1.9.4" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -2201,20 +1459,29 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" -version = "1.4.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.5", - "http 1.1.0", + "futures-core", + "http 1.4.2", "http-body", "httparse", - "itoa 1.0.11", + "itoa", "pin-project-lite", "smallvec", "tokio", @@ -2223,68 +1490,53 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "futures-util", - "http 1.1.0", + "http 1.4.2", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "hyper-util" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", - "http-body-util", + "futures-channel", + "futures-util", + "http 1.4.2", + "http-body", "hyper", - "hyper-util", - "native-tls", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.4", "tokio", - "tokio-native-tls", "tower-service", + "tracing", ] [[package]] -name = "hyper-util" -version = "0.1.7" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body", - "hyper", - "pin-project-lite", - "socket2", - "tokio", - "tower", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -2299,1387 +1551,956 @@ dependencies = [ ] [[package]] -name = "ico" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "id3" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f4e785f2c700217ee82a1c727c720449421742abd5fcb2f1df04e1244760e9" -dependencies = [ - "bitflags 2.6.0", - "byteorder", - "flate2", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.7", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "image" -version = "0.24.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-traits", -] - -[[package]] -name = "image" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" -dependencies = [ - "bytemuck", - "byteorder-lite", - "color_quant", - "exr", - "gif", - "image-webp", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imgref" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" - -[[package]] -name = "impl-more" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", - "serde", -] - -[[package]] -name = "indicatif" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" -dependencies = [ - "console", - "instant", - "number_prefix", - "portable-atomic", - "unicode-width", -] - -[[package]] -name = "infer" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" -dependencies = [ - "cfb", -] - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "interpolate_name" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "iso8601" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" -dependencies = [ - "nom", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "javascriptcore-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "jni" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -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 = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "jsonptr" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" -dependencies = [ - "fluent-uri", - "serde", - "serde_json", -] - -[[package]] -name = "jsonwebtoken" -version = "9.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" -dependencies = [ - "base64 0.21.7", - "js-sys", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser 0.27.2", - "html5ever 0.26.0", - "indexmap 1.9.3", - "matches", - "selectors 0.22.0", -] - -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - -[[package]] -name = "libc" -version = "0.2.155" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "libfuzzer-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" -dependencies = [ - "arbitrary", - "cc", - "once_cell", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.1.3" +name = "icu_collections" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ - "bitflags 2.6.0", - "libc", - "redox_syscall", + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "libsqlite3-sys" -version = "0.29.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4588d65215825ee71ebff9e1c9982067833b1355d7546845ffdb3165cbd7456" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "libwebp-sys" -version = "0.9.5" +name = "icu_normalizer" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829b6b604f31ed6d2bccbac841fe0788de93dbd87e4eb1ba2c4adfe8c012a838" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "cc", - "glob", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "icu_normalizer_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] -name = "local-channel" -version = "0.1.5" +name = "icu_properties" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "futures-core", - "futures-sink", - "local-waker", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", ] [[package]] -name = "local-waker" -version = "0.1.4" +name = "icu_properties_data" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] -name = "lock_api" -version = "0.4.12" +name = "icu_provider" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ - "autocfg", - "scopeguard", + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "lofty" -version = "0.18.2" +name = "id3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75066eb1d25a7047fb2667edb410ae2592439ed81546f95c28b0a1c7d7d3818" +checksum = "70d6d9f23a28c6feab60d7b4a339e004809019ec9d5fd964b3f4c655ff9bc141" dependencies = [ + "bitflags 2.13.0", "byteorder", - "data-encoding", "flate2", - "lofty_attr", - "log", - "ogg_pager", - "paste", ] [[package]] -name = "lofty_attr" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764b60e1ddd07e5665a6a17636a95cd7d8f3b86c73503a69c32979d05f72f3cf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "loom" -version = "0.5.6" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "loop9" -version = "0.1.5" +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "imgref", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "mac" -version = "0.1.1" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] [[package]] -name = "malloc_buf" -version = "0.0.6" +name = "if-addrs" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +checksum = "bf39cc0423ee66021dc5eccface85580e4a001e0c5288bae8bea7ecb69225e90" dependencies = [ "libc", + "windows-sys 0.59.0", ] [[package]] -name = "markup5ever" -version = "0.11.0" +name = "image" +version = "0.25.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", + "bytemuck", + "byteorder-lite", + "image-webp", + "moxcms", + "num-traits", + "png", + "zune-core", + "zune-jpeg", ] [[package]] -name = "markup5ever" -version = "0.12.1" +name = "image-webp" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ - "log", - "phf 0.11.2", - "phf_codegen 0.11.2", - "string_cache", - "string_cache_codegen", - "tendril", + "byteorder-lite", + "quick-error", ] [[package]] -name = "matchers" -version = "0.1.0" +name = "imgref" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" [[package]] -name = "matches" -version = "0.1.10" +name = "impl-more" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" [[package]] -name = "maybe-rayon" -version = "0.1.1" +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ - "cfg-if", - "rayon", + "equivalent", + "hashbrown 0.17.1", ] [[package]] -name = "meilisearch-index-setting-macro" -version = "0.27.1" +name = "inotify" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056e8c0652af81cc6525e0d9c0e1037ea7bcd77955dcd4aef1a1441be7ad7e55" +checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" dependencies = [ - "convert_case 0.6.0", - "proc-macro2", - "quote", - "structmeta", - "syn 2.0.74", + "bitflags 2.13.0", + "inotify-sys", + "libc", ] [[package]] -name = "meilisearch-sdk" -version = "0.27.1" +name = "inotify-sys" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66958255878d712b4f2dece377a8661b41dc976ff15f564b91bfce8b4a619304" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" dependencies = [ - "async-trait", - "bytes", - "either", - "futures", - "futures-io", - "iso8601", - "jsonwebtoken", - "log", - "meilisearch-index-setting-macro", - "pin-project-lite", - "reqwest", - "serde", - "serde_json", - "thiserror", - "time", - "uuid", - "wasm-bindgen-futures", - "web-sys", - "yaup", + "libc", ] [[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memoffset" -version = "0.9.1" +name = "interpolate_name" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ - "autocfg", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "metaflac" -version = "0.2.7" +name = "io-uring" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f083edae4a21f5acb1fda8220d1c14fa31f725bfd4e21005a14c2d8944db9b" +checksum = "595a0399f411a508feb2ec1e970a4a30c249351e30208960d58298de8660b0e5" dependencies = [ - "byteorder", - "hex", + "bitflags 1.3.2", + "libc", ] [[package]] -name = "migrations_internals" -version = "2.2.0" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" -dependencies = [ - "serde", - "toml 0.8.19", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "migrations_macros" -version = "2.2.0" +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", + "either", ] [[package]] -name = "mime" -version = "0.3.17" +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "mime_guess" -version = "2.0.5" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "mime", - "unicase", + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", ] [[package]] -name = "minimal-lexical" -version = "0.2.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] [[package]] -name = "miniz_oxide" -version = "0.7.4" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" dependencies = [ - "adler", - "simd-adler32", + "jni-sys-macros", ] [[package]] -name = "miniz_oxide" -version = "0.8.0" +name = "jni-sys-macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ - "adler2", + "quote", + "syn", ] [[package]] -name = "mio" -version = "1.0.2" +name = "jobserver" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "hermit-abi", + "getrandom 0.4.3", "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", ] [[package]] -name = "mp4ameta" -version = "0.11.0" +name = "js-sys" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb23d62e8eb5299a3f79657c70ea9269eac8f6239a76952689bcd06a74057e81" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "lazy_static", - "mp4ameta_proc", + "cfg-if", + "futures-util", + "wasm-bindgen", ] [[package]] -name = "mp4ameta_proc" -version = "0.6.0" +name = "jsonwebtoken" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dcca13d1740c0a665f77104803360da0bdb3323ecce2e93fa2c959a6d52806" - -[[package]] -name = "music-server" -version = "0.1.0" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ - "actix-cors", - "actix-embed", - "actix-files", - "actix-multipart", - "actix-web", - "actix-web-httpauth", - "actix-web-rust-embed-responder", - "actix-ws", - "argon2", - "audiotags", - "chrono", - "diesel", - "diesel_migrations", - "dirs", - "dotenvy", - "fancy-regex", - "futures", - "image 0.25.2", - "jsonwebtoken", - "lazy_static", - "levenshtein", - "libsqlite3-sys", - "lofty", - "meilisearch-sdk", - "mime_guess", - "openssl", - "rand 0.8.5", - "ravif", - "rayon", - "regex", - "reqwest", - "rust-embed 8.5.0", - "scraper", - "self_update", + "aws-lc-rs", + "base64", + "getrandom 0.2.17", + "js-sys", "serde", "serde_json", - "tokio", - "tokio-util", - "tracing", - "tracing-subscriber", - "uuid", - "walkdir", - "webp", + "signature", + "zeroize", ] [[package]] -name = "nasm-rs" -version = "0.2.5" +name = "jwalk" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" +checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" dependencies = [ + "crossbeam", "rayon", ] [[package]] -name = "native-tls" -version = "0.2.12" +name = "kqueue" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" dependencies = [ + "kqueue-sys", "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", ] [[package]] -name = "ndk" -version = "0.6.0" +name = "kqueue-sys" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", + "bitflags 2.13.0", + "libc", ] [[package]] -name = "ndk-context" -version = "0.1.1" +name = "language-tags" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] -name = "ndk-sys" -version = "0.3.0" +name = "lazy_static" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "new_debug_unreachable" -version = "1.0.6" +name = "libc" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] -name = "new_mime_guess" -version = "4.0.4" +name = "libfuzzer-sys" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a2dfb3559d53e90b709376af1c379462f7fb3085a0177deb73e6ea0d99eff4" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ - "mime", - "unicase", + "arbitrary", + "cc", ] [[package]] -name = "nodrop" -version = "0.1.14" +name = "libm" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] -name = "nom" -version = "7.1.3" +name = "libredox" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "memchr", - "minimal-lexical", + "libc", ] [[package]] -name = "noop_proc_macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "libsqlite3-sys" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" dependencies = [ - "overload", - "winapi", + "cc", + "pkg-config", + "vcpkg", ] [[package]] -name = "num-bigint" -version = "0.4.6" +name = "libwebp-sys" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "54cd30df7c7165ce74a456e4ca9732c603e8dc5e60784558c1c6dc047f876733" dependencies = [ - "num-integer", - "num-traits", + "cc", + "glob", ] [[package]] -name = "num-conv" -version = "0.1.0" +name = "linux-raw-sys" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] -name = "num-derive" -version = "0.4.2" +name = "litemap" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] -name = "num-integer" -version = "0.1.46" +name = "local-channel" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ - "num-traits", + "futures-core", + "futures-sink", + "local-waker", ] [[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] -name = "num-traits" -version = "0.2.19" +name = "lock_api" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", + "scopeguard", ] [[package]] -name = "num_enum" -version = "0.5.11" +name = "lofty" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +checksum = "dec4feeff6c7d75093278133a06e827d7af6d2bfe20b0f331f9d10338a5ec7ca" dependencies = [ - "num_enum_derive", + "byteorder", + "data-encoding", + "flate2", + "lofty_attr", + "log", + "ogg_pager", + "paste", ] [[package]] -name = "num_enum_derive" -version = "0.5.11" +name = "lofty_attr" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +checksum = "458ace39169e4b83c4f77ae3d42d5d1d11c422feef590219a97c973d3b524557" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] -name = "number_prefix" -version = "0.4.0" +name = "log" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] -name = "objc" -version = "0.2.7" +name = "loop9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ - "malloc_buf", - "objc_exception", + "imgref", ] [[package]] -name = "objc_exception" +name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] -name = "objc_id" -version = "0.1.1" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "objc", + "regex-automata", ] [[package]] -name = "object" -version = "0.36.3" +name = "maybe-rayon" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ - "memchr", + "cfg-if", + "rayon", ] [[package]] -name = "ogg_pager" -version = "0.6.1" +name = "mdns-sd" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b0bef808533c5890ab77279538212efdbbbd9aa4ef1ccdfcfbf77a42f7e6fa" +checksum = "183d7ebd9fe3815c1635cd039d1ed95f1d315731cc13e6d504e4ae039d527b8d" dependencies = [ - "byteorder", + "fastrand", + "flume", + "if-addrs", + "log", + "mio", + "socket-pktinfo", + "socket2 0.6.4", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "memchr" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] -name = "openssl" -version = "0.10.66" +name = "migrations_internals" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d" dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "serde", + "toml 0.9.12+spec-1.1.0", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "migrations_macros" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "36fc5ac76be324cfd2d3f2cf0fdf5d5d3c4f14ed8aaebadb09e304ba42282703" dependencies = [ + "migrations_internals", "proc-macro2", "quote", - "syn 2.0.74", ] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "openssl-src" -version = "300.3.1+3.3.1" +name = "mime_guess" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ - "cc", + "mime", + "unicase", ] [[package]] -name = "openssl-sys" -version = "0.9.103" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", + "adler2", + "simd-adler32", ] [[package]] -name = "option-ext" -version = "0.2.0" +name = "mio" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] [[package]] -name = "overload" -version = "0.1.1" +name = "moxcms" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] [[package]] -name = "pango" -version = "0.15.10" +name = "nasm-rs" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" dependencies = [ - "bitflags 1.3.2", - "glib", - "libc", - "once_cell", - "pango-sys", + "jobserver", + "log", ] [[package]] -name = "pango-sys" -version = "0.15.10" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] -name = "parking_lot" -version = "0.12.3" +name = "no_std_io2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" dependencies = [ - "lock_api", - "parking_lot_core", + "memchr", ] [[package]] -name = "parking_lot_core" -version = "0.9.10" +name = "nom" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", + "memchr", ] [[package]] -name = "parse-size" -version = "1.0.0" +name = "noop_proc_macro" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] -name = "password-hash" -version = "0.5.0" +name = "notify" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "bitflags 2.13.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", ] [[package]] -name = "paste" -version = "1.0.15" +name = "notify-types" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.13.0", +] [[package]] -name = "pem" -version = "3.0.4" +name = "nu-ansi-term" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "base64 0.22.1", - "serde", + "windows-sys 0.61.2", ] [[package]] -name = "percent-encoding" -version = "2.3.1" +name = "num-bigint" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] [[package]] -name = "phf" -version = "0.8.0" +name = "num-conv" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] -name = "phf" -version = "0.10.1" +name = "num-derive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "phf_shared 0.10.0", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "phf" -version = "0.11.2" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "phf_macros 0.11.2", - "phf_shared 0.11.2", + "num-traits", ] [[package]] -name = "phf_codegen" -version = "0.8.0" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "num-bigint", + "num-integer", + "num-traits", ] [[package]] -name = "phf_codegen" -version = "0.10.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "autocfg", ] [[package]] -name = "phf_codegen" -version = "0.11.2" +name = "ogg_pager" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "9d36b1d6964c3ac92b7aea701057e02b6b91143d70d83b20abf75a231a3c0216" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "byteorder", ] [[package]] -name = "phf_generator" -version = "0.8.0" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "phf_generator" -version = "0.10.0" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "phf_generator" -version = "0.11.2" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", + "lock_api", + "parking_lot_core", ] [[package]] -name = "phf_macros" -version = "0.8.0" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", ] [[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +name = "parson-core" +version = "1.0.0" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.74", + "base64", + "diesel", + "libsqlite3-sys", + "serde", + "sha2", ] [[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +name = "parson-music" +version = "1.0.0" dependencies = [ - "siphasher", + "actix-cors", + "actix-multipart", + "actix-web", + "actix-web-httpauth", + "actix-ws", + "argon2", + "base64", + "bytes", + "chrono", + "diesel", + "diesel_migrations", + "dirs", + "dotenvy", + "futures", + "globset", + "id3", + "image", + "jsonwebtoken", + "jwalk", + "libc", + "libsqlite3-sys", + "lofty", + "mdns-sd", + "mime_guess", + "notify", + "parson-core", + "percent-encoding", + "rand 0.10.2", + "ravif", + "rayon", + "regex", + "reqwest", + "rust-embed", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-uring", + "tokio-util", + "tracing", + "tracing-subscriber", + "unicode-normalization", + "uuid", + "webp", + "windows-sys 0.61.2", + "zstd", ] [[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +name = "parson-music-windows-bridge" +version = "1.0.0" dependencies = [ - "siphasher", + "parson-music", + "serde_json", + "tokio", ] [[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +name = "parson-music-windows-server" +version = "1.0.0" dependencies = [ - "siphasher", + "actix-web", + "dirs", + "embed-resource", + "parson-music", + "reqwest", + "semver", + "serde", + "sha2", + "tempfile", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", + "windows-sys 0.61.2", + "winreg", ] [[package]] -name = "pin-project" -version = "1.1.5" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "pin-project-internal", + "base64ct", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "pin-project-internal" -version = "1.1.5" +name = "paste" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] -name = "pin-project-lite" -version = "0.2.14" +name = "pastey" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "percent-encoding" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] -name = "pkcs8" -version = "0.10.2" +name = "pin-project-lite" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "plist" -version = "1.7.0" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" -dependencies = [ - "base64 0.22.1", - "indexmap 2.3.0", - "quick-xml 0.32.0", - "serde", - "time", -] +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "png" -version = "0.17.13" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.0", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] -name = "portable-atomic" -version = "1.7.0" +name = "potential_utf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -3689,95 +2510,46 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn 2.0.74", + "syn", ] [[package]] -name = "qoi" -version = "0.4.1" +name = "pxfm" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] name = "quick-error" @@ -3785,81 +2557,84 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.4", "thiserror", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.6" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ + "aws-lc-rs", "bytes", - "rand 0.8.5", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", + "rustls-pki-types", "slab", "thiserror", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ + "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.4", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "r2d2" version = "0.8.10" @@ -3873,94 +2648,79 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", + "rand_chacha", + "rand_core 0.9.5", ] [[package]] name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core 0.9.5", ] [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.1.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.3.4", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_core" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rand_pcg" -version = "0.2.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" dependencies = [ - "rand_core 0.5.1", + "rand_core 0.10.1", ] [[package]] name = "rav1e" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" dependencies = [ + "aligned-vec", "arbitrary", "arg_enum_proc_macro", "arrayvec", + "av-scenechange", "av1-grain", "bitstream-io", "built", @@ -3977,13 +2737,11 @@ dependencies = [ "noop_proc_macro", "num-derive", "num-traits", - "once_cell", "paste", "profiling", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.5", + "rand_chacha", "simd_helpers", - "system-deps 6.2.2", "thiserror", "v_frame", "wasm-bindgen", @@ -3991,9 +2749,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.10" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" dependencies = [ "avif-serialize", "imgref", @@ -4004,17 +2762,11 @@ dependencies = [ "rgb", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -4022,317 +2774,210 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] -[[package]] -name = "readme-rustdocifier" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ad765b21a08b1a8e5cdce052719188a23772bcbefb3c439f0baaf62c56ceac" - [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", ] [[package]] name = "redox_users" -version = "0.4.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - -[[package]] -name = "regex-syntax" -version = "0.6.29" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.12.5" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64 0.22.1", + "base64", "bytes", - "encoding_rs", "futures-channel", "futures-core", "futures-util", - "h2 0.4.5", - "http 1.1.0", + "http 1.4.2", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls", - "tokio-util", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams", "web-sys", - "webpki-roots", - "winreg", ] [[package]] name = "rgb" -version = "0.8.48" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" -dependencies = [ - "bytemuck", -] +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.17", "libc", - "spin", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] [[package]] -name = "rust-embed" -version = "6.8.1" +name = "rsqlite-vfs" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a36224c3276f8c4ebc8c20f158eca7ca4359c8db89991c4925132aaaf6702661" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ - "rust-embed-impl 6.8.1", - "rust-embed-utils 7.8.1", - "walkdir", + "hashbrown 0.16.1", + "thiserror", ] [[package]] name = "rust-embed" -version = "8.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" -dependencies = [ - "rust-embed-impl 8.5.0", - "rust-embed-utils 8.5.0", - "walkdir", -] - -[[package]] -name = "rust-embed-for-web" -version = "11.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acadd634962e6a536f9c0058aaf712b65ee0f21394614b342e8de3feff79c4cc" -dependencies = [ - "rust-embed-for-web-impl", - "rust-embed-for-web-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-for-web-impl" -version = "11.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a25a96341f5b99dfb29ba21dfe72d8d02109b40be52aac50022c91917bc178" -dependencies = [ - "brotli", - "flate2", - "globset", - "proc-macro2", - "quote", - "rust-embed-for-web-utils", - "shellexpand", - "syn 2.0.74", - "walkdir", -] - -[[package]] -name = "rust-embed-for-web-utils" -version = "11.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cc18e05c3925164af76d54900ac3eb6e0557acbd52504f4878e231161d442dd" -dependencies = [ - "base85rs", - "chrono", - "globset", - "new_mime_guess", - "sha2", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "6.8.1" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b94b81e5b2c284684141a2fb9e2a31be90638caf040bf9afbc5a0416afe1ac" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils 7.8.1", - "syn 2.0.74", + "rust-embed-impl", + "rust-embed-utils", "walkdir", ] [[package]] name = "rust-embed-impl" -version = "8.5.0" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" dependencies = [ + "mime_guess", "proc-macro2", "quote", - "rust-embed-utils 8.5.0", - "syn 2.0.74", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "7.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" -dependencies = [ - "sha2", + "rust-embed-utils", + "syn", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.5.0" +version = "8.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" dependencies = [ "sha2", "walkdir", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ + "aws-lc-rs", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -4340,43 +2985,77 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.1.3" +name = "rustls-native-certs" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "base64 0.22.1", + "openssl-probe", "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] [[package]] -name = "rustls-pki-types" -version = "1.8.0" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -4389,11 +3068,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4405,41 +3084,19 @@ dependencies = [ "parking_lot", ] -[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scraper" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90460b31bfe1fc07be8262e42c665ad97118d4585869de9345a84d501a9eaf0" -dependencies = [ - "ahash", - "cssparser 0.31.2", - "ego-tree", - "getopts", - "html5ever 0.27.0", - "once_cell", - "selectors 0.25.0", - "tendril", -] - [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "core-foundation", "core-foundation-sys", "libc", @@ -4448,125 +3105,61 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", ] [[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.27.2", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.1.1", - "smallvec", - "thin-slice", -] - -[[package]] -name = "selectors" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" -dependencies = [ - "bitflags 2.6.0", - "cssparser 0.31.2", - "derive_more", - "fxhash", - "log", - "new_debug_unreachable", - "phf 0.10.1", - "phf_codegen 0.10.0", - "precomputed-hash", - "servo_arc 0.3.0", - "smallvec", -] - -[[package]] -name = "self-replace" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7828a58998685d8bf5a3c5e7a3379a5867289c20828c3ee436280b44b598515" -dependencies = [ - "fastrand 1.9.0", - "tempfile", - "windows-sys 0.48.0", -] - -[[package]] -name = "self_update" -version = "0.41.0" +name = "semver" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469a3970061380c19852269f393e74c0fe607a4e23d85267382cf25486aa8de5" -dependencies = [ - "hyper", - "indicatif", - "log", - "quick-xml 0.23.1", - "regex", - "reqwest", - "self-replace", - "semver", - "serde_json", - "tempfile", - "urlencoding", - "zip", - "zipsign-api", -] +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] -name = "semver" -version = "1.0.23" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ - "serde", + "serde_core", + "serde_derive", ] [[package]] -name = "serde" -version = "1.0.207" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "serde_json" -version = "1.0.124" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap 2.3.0", - "itoa 1.0.11", + "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -4578,24 +3171,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_repr" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - [[package]] name = "serde_spanned" -version = "0.6.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -4605,102 +3187,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.11", + "itoa", "ryu", "serde", ] -[[package]] -name = "serde_with" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.3.0", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "servo_arc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" -dependencies = [ - "stable_deref_trait", -] - [[package]] name = "sha1" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -4713,20 +3224,18 @@ dependencies = [ ] [[package]] -name = "shellexpand" -version = "3.1.0" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" -dependencies = [ - "dirs", -] +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4736,634 +3245,256 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", "rand_core 0.6.4", ] [[package]] name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simd_helpers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" -dependencies = [ - "quote", -] - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "soup2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" -dependencies = [ - "bitflags 1.3.2", - "gio", - "glib", - "libc", - "once_cell", - "soup2-sys", -] - -[[package]] -name = "soup2-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" -dependencies = [ - "bitflags 1.3.2", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "structmeta" -version = "0.3.0" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" -dependencies = [ - "proc-macro2", - "quote", - "structmeta-derive", - "syn 2.0.74", -] +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "structmeta-derive" -version = "0.3.0" +name = "simd_cesu8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "rustc_version", + "simdutf8", ] [[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.109" +name = "simd_helpers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ - "proc-macro2", "quote", - "unicode-ident", ] [[package]] -name = "syn" -version = "2.0.74" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "sync_wrapper" -version = "1.0.1" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "system-configuration" -version = "0.5.1" +name = "smallvec" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "socket-pktinfo" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" dependencies = [ - "core-foundation-sys", "libc", + "socket2 0.6.4", + "windows-sys 0.60.2", ] [[package]] -name = "system-deps" -version = "5.0.0" +name = "socket2" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", - "pkg-config", - "toml 0.5.11", - "version-compare 0.0.11", + "libc", + "winapi", ] [[package]] -name = "system-deps" -version = "6.2.2" +name = "socket2" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ - "cfg-expr 0.15.8", - "heck 0.5.0", - "pkg-config", - "toml 0.8.19", - "version-compare 0.2.0", + "libc", + "windows-sys 0.52.0", ] [[package]] -name = "tao" -version = "0.16.10" +name = "socket2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d298c441a1da46e28e8ad8ec205aab7fd8cd71b9d10e05454224eef422e1ae" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "cc", - "cocoa", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", - "gdkwayland-sys", - "gdkx11-sys", - "gio", - "glib", - "glib-sys", - "gtk", - "image 0.24.9", - "instant", - "jni", - "lazy_static", "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "once_cell", - "parking_lot", - "png", - "raw-window-handle", - "scopeguard", - "serde", - "tao-macros", - "unicode-segmentation", - "uuid", - "windows 0.39.0", - "windows-implement", - "x11-dl", + "windows-sys 0.61.2", ] [[package]] -name = "tao-macros" -version = "0.1.3" +name = "spin" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "lock_api", ] [[package]] -name = "tar" -version = "0.4.41" +name = "sqlite-wasm-rs" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ - "filetime", - "libc", - "xattr", + "cc", + "js-sys", + "rsqlite-vfs", + "wasm-bindgen", ] [[package]] -name = "target-lexicon" -version = "0.12.16" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "tauri" -version = "1.7.2" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e33e3ba00a3b05eb6c57ef135781717d33728b48acf914bb05629e74d897d29" -dependencies = [ - "anyhow", - "cocoa", - "dirs-next", - "dunce", - "embed_plist", - "encoding_rs", - "flate2", - "futures-util", - "getrandom 0.2.15", - "glib", - "glob", - "gtk", - "heck 0.5.0", - "http 0.2.12", - "ignore", - "objc", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "semver", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "state", - "tar", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "tempfile", - "thiserror", - "tokio", - "url", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "tauri-build" -version = "1.5.4" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fb5a90a64241ddb7217d3210d844149070a911e87e8a107a707a1d4973f164" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs-next", - "heck 0.5.0", - "json-patch", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] -name = "tauri-codegen" -version = "1.4.5" +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a9e3f5cebf779a63bf24903e714ec91196c307d8249a0008b882424328bcda" -dependencies = [ - "base64 0.21.7", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "tauri-utils", - "thiserror", - "time", - "uuid", - "walkdir", -] +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] -name = "tauri-macros" -version = "1.4.6" +name = "syn" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d0e989f54fe06c5ef0875c5e19cf96453d099a0a774d5192ab47e80471cdab" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ - "heck 0.5.0", "proc-macro2", "quote", - "syn 1.0.109", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-runtime" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33fda7d213e239077fad52e96c6b734cecedb30c2382118b64f94cb5103ff3a" -dependencies = [ - "gtk", - "http 0.2.12", - "http-range", - "rand 0.8.5", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror", - "url", - "uuid", - "webview2-com", - "windows 0.39.0", + "unicode-ident", ] [[package]] -name = "tauri-runtime-wry" -version = "0.14.10" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c447dcd9b0f09c7dc4b752cc33e72788805bfd761fbda5692d30c48289efec" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ - "cocoa", - "gtk", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "tauri-runtime", - "tauri-utils", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", - "wry", + "futures-core", ] [[package]] -name = "tauri-utils" -version = "1.6.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0c939e88d82903a0a7dfb28388b12a3c03504d6bd6086550edaa3b6d8beaa" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "brotli", - "ctor", - "dunce", - "glob", - "heck 0.5.0", - "html5ever 0.26.0", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.2", "proc-macro2", "quote", - "semver", - "serde", - "serde_json", - "serde_with", - "thiserror", - "url", - "walkdir", - "windows-version", -] - -[[package]] -name = "tauri-winres" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" -dependencies = [ - "embed-resource", - "toml 0.7.8", + "syn", ] [[package]] name = "tempfile" -version = "3.12.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", - "fastrand 2.1.0", + "fastrand", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", + "windows-sys 0.61.2", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" -version = "1.0.63" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa 1.0.11", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -5376,155 +3507,169 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ - "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.4", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "native-tls", + "rustls", "tokio", ] [[package]] -name = "tokio-rustls" -version = "0.26.0" +name = "tokio-uring" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "748482e3e13584a34664a710168ad5068e8cb1d968aa4ffa887e83ca6dd27967" dependencies = [ - "rustls", - "rustls-pki-types", + "futures-util", + "io-uring", + "libc", + "slab", + "socket2 0.4.10", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] [[package]] name = "toml" -version = "0.5.11" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", ] [[package]] name = "toml" -version = "0.7.8" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ - "serde", + "indexmap", + "serde_core", "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", ] [[package]] -name = "toml" -version = "0.8.19" +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.20", + "serde_core", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "serde", + "serde_core", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_parser" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "indexmap 2.3.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", + "winnow 1.0.4", ] [[package]] -name = "toml_edit" -version = "0.22.20" +name = "toml_writer" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" -dependencies = [ - "indexmap 2.3.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.18", -] +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" -version = "0.4.13" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tokio", "tower-layer", "tower-service", ] +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http 1.4.2", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" @@ -5534,9 +3679,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -5544,22 +3689,35 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -5578,14 +3736,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -5602,51 +3760,48 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] -name = "unicode-width" -version = "0.1.13" +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "untrusted" @@ -5656,9 +3811,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -5667,63 +3822,40 @@ dependencies = [ ] [[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf-8" -version = "0.7.6" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom 0.2.15", - "rand 0.8.5", - "serde", - "uuid-macro-internal", - "wasm-bindgen", -] - -[[package]] -name = "uuid-macro-internal" -version = "1.10.0" +version = "1.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee1cd046f83ea2c4e920d6ee9f7c3537ef928d75dce5d84a87c2c5d6b3999a3a" +checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.74", + "getrandom 0.4.3", + "js-sys", + "rand 0.10.2", + "serde_core", + "wasm-bindgen", ] [[package]] name = "v_frame" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] -[[package]] -name = "v_htmlescape" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" - [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -5731,18 +3863,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" @@ -5761,9 +3881,9 @@ dependencies = [ [[package]] name = "vswhom-sys" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" dependencies = [ "cc", "libc", @@ -5790,59 +3910,47 @@ dependencies = [ [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.74", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ - "cfg-if", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5850,156 +3958,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.74", - "wasm-bindgen-backend", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasm-streams" -version = "0.4.0" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webkit2gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup2", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "0.18.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pango-sys", - "pkg-config", - "soup2-sys", - "system-deps 6.2.2", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webp" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f53152f51fb5af0c08484c33d16cca96175881d1f3dec068c23b31a158c2d99" +checksum = "c071456adef4aca59bf6a583c46b90ff5eb0b4f758fc347cea81290288f37ce1" dependencies = [ - "image 0.25.2", + "image", "libwebp-sys", ] [[package]] -name = "webpki-roots" -version = "0.26.3" +name = "webpki-root-certs" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webview2-com" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.39.0", - "windows-implement", -] - -[[package]] -name = "webview2-com-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "webview2-com-sys" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" -dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows 0.39.0", - "windows-bindgen", - "windows-metadata", -] - -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - [[package]] name = "winapi" version = "0.3.9" @@ -6018,11 +4035,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6032,70 +4049,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.39.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows" -version = "0.48.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ - "windows-targets 0.48.5", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "windows-bindgen" -version = "0.39.0" +name = "windows-interface" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ - "windows-metadata", - "windows-tokens", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-implement" -version = "0.39.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "syn 1.0.109", - "windows-tokens", + "windows-link", ] [[package]] -name = "windows-metadata" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" - -[[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link", ] [[package]] @@ -6117,18 +4126,21 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -6140,7 +4152,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -6148,26 +4160,22 @@ dependencies = [ ] [[package]] -name = "windows-tokens" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" - -[[package]] -name = "windows-version" -version = "0.1.1" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-targets 0.52.6", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6175,16 +4183,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -6193,22 +4195,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.39.0" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -6217,256 +4219,245 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.39.0" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.39.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] -name = "windows_x86_64_gnu" +name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] -name = "windows_x86_64_gnullvm" +name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.39.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" +name = "winnow" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] -name = "winnow" -version = "0.5.40" +name = "winreg" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ - "memchr", + "cfg-if", + "windows-sys 0.59.0", ] [[package]] -name = "winnow" -version = "0.6.18" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" -dependencies = [ - "memchr", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "winreg" -version = "0.52.0" +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yoke" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "wry" -version = "0.24.11" +name = "yoke-derive" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55c80b12287eb1ff7c365fc2f7a5037cb6181bd44c9fce81c8d1cf7605ffad6" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ - "base64 0.13.1", - "block", - "cocoa", - "core-graphics", - "crossbeam-channel", - "dunce", - "gdk", - "gio", - "glib", - "gtk", - "html5ever 0.26.0", - "http 0.2.12", - "kuchikiki", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sha2", - "soup2", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.39.0", - "windows-implement", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "x11" -version = "2.21.0" +name = "zerocopy" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ - "libc", - "pkg-config", + "zerocopy-derive", ] [[package]] -name = "x11-dl" -version = "2.21.0" +name = "zerocopy-derive" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ - "libc", - "once_cell", - "pkg-config", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "xattr" -version = "1.3.1" +name = "zerofrom" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ - "libc", - "linux-raw-sys", - "rustix", + "zerofrom-derive", ] [[package]] -name = "yaup" -version = "0.3.1" +name = "zerofrom-derive" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0144f1a16a199846cb21024da74edd930b43443463292f536b7110b4855b5c6" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ - "form_urlencoded", - "serde", - "thiserror", + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "zeroize" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ - "byteorder", - "zerocopy-derive", + "zeroize_derive", ] [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "zeroize_derive" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.74", + "syn", ] [[package]] -name = "zeroize" -version = "1.8.1" +name = "zerotrie" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] -name = "zip" -version = "2.2.0" +name = "zerovec" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "arbitrary", - "crc32fast", - "crossbeam-utils", - "displaydoc", - "indexmap 2.3.0", - "memchr", - "thiserror", - "time", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "zipsign-api" -version = "0.1.2" +name = "zerovec-derive" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6413a546ada9dbcd0b9a3e0b0880581279e35047bce9797e523b3408e1df607c" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ - "ed25519-dalek", - "thiserror", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", @@ -6474,24 +4465,15 @@ dependencies = [ [[package]] name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-inflate" -version = "0.2.54" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 5c8cfbcd..adc084b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,14 @@ [workspace] -members = [ - "apps/web/src-tauri", - "crates/backend", - # "crates/release" -] - +members = ["crates/backend", "crates/parson-core", "crates/windows-bridge", "crates/windows-server"] resolver = "2" -# [profile.release] -# opt-level = "z" -# lto = true -# codegen-units = 1 -# panic = 'abort' -# strip = true +[profile.dev] +opt-level = 1 +debug = true +incremental = true + +[profile.release] +lto = "fat" +codegen-units = 1 +panic = "abort" +strip = "symbols" diff --git a/Cross.toml b/Cross.toml deleted file mode 100644 index 8bfe811c..00000000 --- a/Cross.toml +++ /dev/null @@ -1,28 +0,0 @@ -[target.x86_64-unknown-linux-musl] -pre-build = [ - "apt-get update", - "apt-get install -y nasm" -] - -[target.aarch64-unknown-linux-musl] -pre-build = [ - "apt-get update", - "apt-get install -y nasm" -] - -[target.armv7-unknown-linux-musleabihf] -pre-build = [ - "apt-get update", - "apt-get install -y nasm" -] - -[target.arm-unknown-linux-musleabihf] -pre-build = [ - "apt-get update", - "apt-get install -y nasm" -] - -[target.x86_64-pc-windows-gnu] -pre-build = [ - "choco install nasm" -] \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8c58b57c..1734593d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,89 +1,50 @@ -# Stage 1: Build the React frontend -FROM oven/bun:alpine AS frontend-builder -RUN apk update -RUN apk add --no-cache yarn -WORKDIR /app -COPY . . - -RUN bun install --ignore-scripts -RUN bun install turbo --global -RUN turbo prune music --docker +FROM oven/bun:1.3.14-alpine AS bun-runtime -# Stage 2: Install dependencies and build the project -FROM oven/bun:alpine AS installer +FROM node:24-alpine AS web-deps WORKDIR /app -COPY --from=frontend-builder /app/out/json/ . -COPY --from=frontend-builder /app/out/yarn.lock ./yarn.lock - -RUN bun install --ignore-scripts -RUN bun install yarn --global -RUN apk add --no-cache nodejs-current -COPY --from=frontend-builder /app/out/full/ . -COPY ./package.json ./package.json -COPY ./yarn.lock ./yarn.lock - -WORKDIR /app/apps/web - -RUN yarn build - -# Stage 3: Build the Rust backend -FROM rust:slim-buster AS backend-builder -WORKDIR /usr/src +ENV NEXT_TELEMETRY_DISABLED=1 +COPY --from=bun-runtime /usr/local/bin/bun /usr/local/bin/bun -RUN apt-get update && apt-get install -y --no-install-recommends \ - sqlite3 libsqlite3-dev wget make build-essential pkg-config libssl-dev \ - && rm -rf /var/lib/apt/lists/* +COPY package.json bun.lock turbo.json ./ +COPY apps/web ./apps/web +COPY packages ./packages +RUN bun install --frozen-lockfile -RUN wget https://www.nasm.us/pub/nasm/releasebuilds/2.16/nasm-2.16.tar.gz \ - && tar xzf nasm-2.16.tar.gz \ - && cd nasm-2.16 \ - && ./configure \ - && make \ - && make install \ - && cd .. \ - && rm -rf nasm-2.16 nasm-2.16.tar.gz - -RUN cargo install diesel_cli --no-default-features --features sqlite +FROM web-deps AS web-builder +RUN bun --filter parson-music-web build -COPY ./crates/backend /usr/src/crates/backend -COPY ./diesel.toml /usr/src/diesel.toml -COPY --from=installer /app/apps/web/out /usr/src/apps/web/out - -WORKDIR /usr/src/crates/backend - -ENV DATABASE_URL=sqlite:///usr/src/crates/backend/music.db +FROM rust:1.97-slim-trixie AS backend-builder +WORKDIR /app -RUN diesel migration run --config-file /usr/src/diesel.toml +RUN apt-get update \ + && apt-get install -y --no-install-recommends nasm \ + && rm -rf /var/lib/apt/lists/* -RUN cargo build --release +COPY Cargo.toml Cargo.lock ./ +COPY crates/backend ./crates/backend +COPY crates/parson-core ./crates/parson-core +COPY crates/windows-bridge ./crates/windows-bridge +COPY crates/windows-server ./crates/windows-server +COPY --from=web-builder /app/apps/web/out ./apps/web/out +RUN cargo build --release -p parson-music -# Stage 4: Create the final image -FROM debian:bullseye-slim AS runner +FROM debian:trixie-slim AS runner WORKDIR /app -RUN apt-get update && apt-get install -y --no-install-recommends \ - libssl1.1 \ - sqlite3 \ - libsqlite3-dev \ - wget \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -# Change ownership of the /app directory to the nextjs user -RUN chown -R nextjs:nodejs /app +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl ffmpeg \ + && rm -rf /var/lib/apt/lists/* -USER nextjs -COPY --from=backend-builder --chown=nextjs:nodejs /usr/src/crates/backend/target/release/music-server /usr/local/bin/music-server -COPY --from=backend-builder --chown=nextjs:nodejs /usr/src/crates/backend/music.db /usr/src/crates/backend/music.db -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/out ./apps/web/out +RUN useradd --system --uid 10001 parson \ + && install -d -o parson -g parson /Parson -# Set SSL_CERT_FILE environment variable -ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt +COPY --from=backend-builder /app/target/release/parson-music-server /usr/local/bin/parson-music-server +ENV RUNNING_IN_DOCKER=true +ENV PARSON_PORT=1993 EXPOSE 1993 -EXPOSE 7700 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl --fail --silent http://127.0.0.1:1993/health || exit 1 +USER parson -CMD ["/usr/local/bin/music-server", "--port", "1993"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/parson-music-server"] diff --git a/LICENSE b/LICENSE index 343499cc..b8dd7a9f 100644 --- a/LICENSE +++ b/LICENSE @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - ParsonLabs Music Copyright (C) 2024 WillKirkmanM + Parson Copyright (C) 2024-2026 WillKirkmanM This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/README.md b/README.md index 44be56cc..cf7919a7 100644 --- a/README.md +++ b/README.md @@ -1,145 +1,6 @@ -

- -

-

ParsonLabs Music

+# Parson -

- PWA Shields -

+Parson is a self-hosted, local-first music server and player for the web, +desktop, and mobile. -

- Install - · - Documentation - · - Releases -

- -

ParsonLabs Music is the Self Hosted Audio streaming alternative to YouTube Music, Spotify & Apple Music, providing Unrestricted Access to your library in Uncompressed, Lossless Quality

- -![genres-showcase](https://github.com/user-attachments/assets/9c85421f-a96a-42ae-9747-7efc23c6bd16) -![music-video-home](https://github.com/user-attachments/assets/398a49fd-a569-480f-89b5-0c57f73b526f) -![plm-complete](https://github.com/user-attachments/assets/b5b012e3-7e91-4a33-b932-a04d68d14aee) -![genre-selection](https://github.com/user-attachments/assets/e00e86f8-1bf5-4070-a2ab-f2fa79d0fcec) -![home-plm](https://github.com/user-attachments/assets/996d0285-cf17-4e07-98fc-f6bf482308f1) -![music-album-md](https://github.com/user-attachments/assets/7c19b7db-f427-4864-899a-0cb42aa3ea82) -![pfp-showcase](https://github.com/user-attachments/assets/a72c56f3-a5fa-4c07-9f99-045c9648a46a) -![music-video](https://github.com/user-attachments/assets/3471f42f-e476-4069-ac9c-0b150332b950) -![updated-lyrics](https://github.com/user-attachments/assets/ecbecfc3-4f97-4cfc-9127-f8479d235ab8) -![updated-artist](https://github.com/user-attachments/assets/b84e3dfe-5fe3-412c-b151-4c027ee23782) -![ArtistAlbumSearch](https://github.com/user-attachments/assets/87dfe43e-c5c3-4058-a6e7-4dbb2ab8f8c6) -![profile](https://github.com/user-attachments/assets/71a8a3c0-4069-4617-b2fc-518fb095a05c) -![search](https://github.com/user-attachments/assets/0fa243ad-da6a-4e46-a561-71636ad7a712) -![showcase-home](https://github.com/user-attachments/assets/9adbd8df-2020-4fb3-8757-1d45f27c0159) -![lyrics-showcase](https://github.com/WillKirkmanM/music/assets/98240335/7aa78513-03c9-4ee0-912c-f5dfc816247b) -![music-artist-showcase](https://github.com/WillKirkmanM/music/assets/98240335/76d1c540-d5cd-4ab0-9ecf-7986e0d84e31) -![music-in-your-library](https://github.com/WillKirkmanM/music/assets/98240335/3b99a0c6-640e-4f38-918f-956a3ad0fd25) -![music-album-search](https://github.com/WillKirkmanM/music/assets/98240335/21b01fba-3705-4ad7-b610-5e549fa06b08) -![music-artist-search](https://github.com/WillKirkmanM/music/assets/98240335/b6566927-11d0-47c3-a5bf-df6abbb2e451) -![music-search-did-you-mean](https://github.com/WillKirkmanM/music/assets/98240335/318f543d-e540-4825-89e5-6a5cda96dbb2) -![music-context-menu](https://github.com/WillKirkmanM/music/assets/98240335/27da74b6-4d30-4f0e-be5a-7cba667d02e7) -![music-playlist](https://github.com/WillKirkmanM/music/assets/98240335/ee6319a1-1b7d-4896-86b6-5a886234e2b5) - -## Get Started - -### Docker - -#### Docker Run -```sh -docker run -d \ - --name parsonlabs-music \ - -p 1993:1993 \ - -p 7700:7700 \ - -v "/path/to/config:/ParsonLabsMusic" \ - -v "/path/to/music:/music" \ - -e JWT_SECRET=your_auth_secret_here \ - --restart unless-stopped \ - ghcr.io/willkirkmanm/music -``` - -#### Docker Compose -```yaml -services: - music-server: - image: ghcr.io/willkirkmanm/music - container_name: parsonlabs-music - ports: - - "1993:1993" - - "7700:7700" - volumes: - - "/path/to/config:/ParsonLabsMusic" - - "/path/to/music:/music" - environment: - - JWT_SECRET=your_auth_secret_here - restart: unless-stopped -``` - -### Download Precompiled Binaries - -You can download the precompiled binaries from the [releases page](https://github.com/WillKirkmanM/music/releases). - -- [Windows (x86_64)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-x86_64-pc-windows-gnu.zip) -- [Linux (x86_64)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-x86_64-unknown-linux-gnu.tar.gz) -- [Linux (x86_64 MUSL)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-x86_64-unknown-linux-musl.tar.gz) -- [Linux (ARMv7 gnueabihf)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-armv7-unknown-linux-gnueabihf.tar.gz) -- [Linux (ARMv7 MUSL)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-armv7-unknown-linux-musleabihf.tar.gz) -- [Linux (ARM gnueabihf)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-arm-unknown-linux-gnueabihf.tar.gz) -- [Linux (ARM MUSL)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-arm-unknown-linux-musleabihf.tar.gz) -- [Linux (ARM64)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-aarch64-unknown-linux-gnu.tar.gz) -- [Linux (ARM64 MUSL)](https://github.com/WillKirkmanM/music/releases/download/v1.2.0-alpha/music-server-aarch64-unknown-linux-musl.tar.gz) - -### Compile From Source - -### One Liner -``` -git clone https://github.com/WillKirkmanM/music && cd music && OS=$(uname -s) && if [ "$OS" = "Linux" ]; then if [ -f /etc/debian_version ]; then sudo apt-get update && sudo apt-get install -y --no-install-recommends sqlite3 libsqlite3-dev wget make build-essential pkg-config libssl-dev unzip && wget https://www.nasm.us/pub/nasm/releasebuilds/2.16/nasm-2.16.tar.gz && tar xzf nasm-2.16.tar.gz && cd nasm-2.16 && ./configure && make && sudo make install && cd .. && rm -rf nasm-2.16 nasm-2.16.tar.gz && sudo apt-get install -y libssl1.1 && rm -rf /var/lib/apt/lists/* && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn; elif [ -f /etc/arch-release ]; then sudo pacman -Syu --noconfirm sqlite wget make base-devel pkgconf openssl nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn; elif [ -f /etc/fedora-release ]; then sudo dnf install -y sqlite sqlite-devel wget make gcc gcc-c++ kernel-devel pkgconf-pkg-config openssl-devel nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn; elif [ -f /etc/gentoo-release ]; then sudo emerge --sync && sudo emerge --ask sqlite wget make gcc pkgconfig openssl nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn; else echo "Unsupported Linux distribution" && exit 1; fi; else echo "Unsupported OS" && exit 1; fi && bun run build --filter music && cargo build --package music-server --release && cargo run --package music-server --release -``` - -### Clone the Repository -``` -git clone https://github.com/WillKirkmanM/music -``` - -### Install Dependencies -#### Ubuntu -``` -sudo apt-get update && apt-get install -y --no-install-recommends sqlite3 libsqlite3-dev wget make build-essential pkg-config libssl-dev unzip && wget https://www.nasm.us/pub/nasm/releasebuilds/2.16/nasm-2.16.tar.gz && tar xzf nasm-2.16.tar.gz && cd nasm-2.16 && ./configure && make && make install && cd .. && rm -rf nasm-2.16 nasm-2.16.tar.gz && apt-get install -y libssl1.1 && rm -rf /var/lib/apt/lists/* && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn -``` -#### Arch Linux -``` -sudo pacman -Syu --noconfirm sqlite wget make base-devel pkgconf openssl nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn -``` -#### Fedora -``` -sudo dnf install -y sqlite sqlite-devel wget make gcc gcc-c++ kernel-devel pkgconf-pkg-config openssl-devel nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn -``` - -#### Gentoo -``` -sudo emerge --sync && sudo emerge --ask sqlite wget make gcc pkgconfig openssl nasm unzip && curl -fsSL https://bun.sh/install | bash && export BUN_INSTALL="$HOME/.bun" && export PATH="$BUN_INSTALL/bin:$PATH" && bun install --global yarn -``` - -### Build the Website -``` -bun run build --filter music -``` -### Build the Server -``` -cargo build --package music-server --release -``` - -> [!TIP] -> The authentication is handled by [JWT Tokens](https://jwt.io/)), they require a special `JWT_SECRET` in order to safely protect your accounts against third parties. -> It is reccommended to `cp .env.example .env` and edit the `JWT_SECRET=` value to something secret, alternatively, you can use openssl to generate a random secret: `openssl rand -base64 48`. - -### Run ParsonLabs Music -``` -cargo run --package music-server --release -p 1993 -``` - -> [!NOTE] -> **Done! 🥳**. -> Head to [http://localhost:1993/](http://localhost:1993/) and setup your music library. - -## Documentation -Any additional troubleshooting information can be found in the Documentation https://docs.parsonlabs.com. +![Parson](apps/site/public/screenshots/01-home.png) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..45b7c015 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security policy + +## Supported versions + +Security fixes are provided for the latest 1.0.x release. + +## Deployment boundary + +Plain HTTP and automatic discovery are intended for trusted local networks. +Use an HTTPS reverse proxy and set `PARSON_PUBLIC_URL` for internet access. A +remote first-account registration requires the setup code printed by the server; +localhost setup does not. Keep the `/Parson` data directory and server logs +private because they contain authentication material and the temporary setup +code. + +## Reporting a vulnerability + +Please report vulnerabilities privately through GitHub Security Advisories for this repository. Do not open a public issue with exploit details. Include affected versions, reproduction steps, impact, and any proposed mitigation. You should receive an acknowledgement within seven days. diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 00000000..d912fa54 --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,27 @@ +# Parson desktop client + +Electron client for Linux and Windows. + +## Development + +```powershell +bun --filter parson-music-desktop desktop:dev +``` + +## Packaging + +```powershell +bun --filter parson-music-desktop desktop:build +``` + +Linux package check: + +```bash +./apps/desktop/scripts/verify-linux-package.sh +``` + +Windows release: + +```powershell +.\apps\desktop\scripts\package-windows.ps1 -Version 1.0.0 +``` diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs new file mode 100644 index 00000000..8cf68c90 --- /dev/null +++ b/apps/desktop/electron/main.cjs @@ -0,0 +1,219 @@ +const { + app, + BrowserWindow, + dialog, + ipcMain, + Menu, + shell, +} = require("electron"); +const { spawn } = require("node:child_process"); +const fs = require("node:fs"); +const path = require("node:path"); + +const PORT = 1993; +const ORIGIN = `http://127.0.0.1:${PORT}`; +const STARTUP_POLL_MS = 10; +let backend = null; +let mainWindow = null; +let quitting = false; + +const primary = app.requestSingleInstanceLock(); +if (!primary) { + app.quit(); +} else { + app.on("second-instance", () => showMainWindow()); +} + +function showMainWindow() { + if (!mainWindow || mainWindow.isDestroyed()) return; + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.show(); + mainWindow.focus(); +} + +function backendExecutable() { + const executable = `parson-music-server${process.platform === "win32" ? ".exe" : ""}`; + if (app.isPackaged) return path.join(process.resourcesPath, executable); + return path.join(__dirname, "bin", executable); +} + +function rotateBackendLog(logPath) { + try { + if (fs.statSync(logPath).size <= 2 * 1024 * 1024) return; + fs.renameSync(logPath, `${logPath}.previous`); + } catch (error) { + if (error?.code !== "ENOENT") + console.error("Could not rotate backend log", error); + } +} + +async function isParsonReady() { + try { + const response = await fetch(`${ORIGIN}/.well-known/parson`, { + signal: AbortSignal.timeout(750), + cache: "no-store", + }); + if (!response.ok) return false; + const manifest = await response.json(); + return ( + manifest.protocol === "parson" && manifest.product === "parson-music" + ); + } catch { + return false; + } +} + +async function startBackend() { + if (await isParsonReady()) return; + const executable = backendExecutable(); + if (!fs.existsSync(executable)) { + throw new Error(`The packaged Parson backend is missing: ${executable}`); + } + const logsDirectory = app.getPath("logs"); + fs.mkdirSync(logsDirectory, { recursive: true }); + const logPath = path.join(logsDirectory, "backend.log"); + rotateBackendLog(logPath); + const log = fs.openSync(logPath, "a"); + backend = spawn(executable, [], { + cwd: path.dirname(executable), + env: { + ...process.env, + PARSON_BIND_ADDRESS: "0.0.0.0", + PARSON_PORT: String(PORT), + PARSON_PUBLIC_URL: ORIGIN, + }, + stdio: ["ignore", log, log], + }); + fs.closeSync(log); + backend.once("exit", (code, signal) => { + backend = null; + if (!quitting && !mainWindow?.isDestroyed()) { + void mainWindow.loadFile(path.join(__dirname, "startup-error.html"), { + query: { detail: `Backend stopped (${code ?? signal ?? "unknown"}).` }, + }); + } + }); + const deadline = Date.now() + 30_000; + while (Date.now() < deadline) { + if (await isParsonReady()) return; + if (!backend) throw new Error("The Parson backend stopped during startup."); + await new Promise((resolve) => setTimeout(resolve, STARTUP_POLL_MS)); + } + throw new Error("The Parson backend did not become ready within 30 seconds."); +} + +function installDesktopBridge() { + ipcMain.handle("parson:window-control", (event, action) => { + const window = BrowserWindow.fromWebContents(event.sender); + if (!window || window.isDestroyed()) return false; + switch (action) { + case "minimize": + window.minimize(); + return true; + case "toggle-maximize": + if (window.isMaximized()) window.unmaximize(); + else window.maximize(); + return window.isMaximized(); + case "is-maximized": + return window.isMaximized(); + case "close": + window.close(); + return true; + default: + throw new Error(`Unknown window control: ${action}`); + } + }); + + ipcMain.handle("parson:invoke", async (event, command, args = {}) => { + if (new URL(event.senderFrame.url).origin !== ORIGIN) { + throw new Error( + "Desktop commands are only available to the local Parson app.", + ); + } + switch (command) { + case "platform": + return process.platform === "win32" ? "windows" : "linux"; + case "select_music_folder": { + const selection = await dialog.showOpenDialog(mainWindow, { + properties: ["openDirectory", "createDirectory"], + title: "Choose your music folder", + }); + return selection.canceled ? null : (selection.filePaths[0] ?? null); + } + case "show_track_in_file_manager": { + const target = typeof args.path === "string" ? args.path : ""; + if (!target || !fs.existsSync(target)) + throw new Error("A valid track path is required."); + shell.showItemInFolder(target); + return true; + } + default: + throw new Error(`Unknown desktop command: ${command}`); + } + }); +} + +async function createWindow() { + mainWindow = new BrowserWindow({ + width: 1280, + height: 820, + minWidth: 900, + minHeight: 600, + show: true, + backgroundColor: "#000000", + autoHideMenuBar: true, + frame: false, + title: "Parson", + icon: app.isPackaged + ? path.join(process.resourcesPath, "parson.png") + : path.join(__dirname, "../../web/public/icons/icon-512.png"), + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + preload: path.join(__dirname, "preload.cjs"), + }, + }); + const publishMaximizedState = () => { + if (!mainWindow?.isDestroyed()) { + mainWindow.webContents.send("parson:maximized", mainWindow.isMaximized()); + } + }; + mainWindow.on("maximize", publishMaximizedState); + mainWindow.on("unmaximize", publishMaximizedState); + mainWindow.maximize(); + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + if (url.startsWith("http://") || url.startsWith("https://")) + void shell.openExternal(url); + return { action: "deny" }; + }); + mainWindow.webContents.on("will-navigate", (event, url) => { + if (url.startsWith(ORIGIN) || url.startsWith("file:")) return; + event.preventDefault(); + }); + await mainWindow.loadFile(path.join(__dirname, "startup.html")); + try { + await startBackend(); + await mainWindow.loadURL(ORIGIN); + } catch (error) { + await mainWindow.loadFile(path.join(__dirname, "startup-error.html"), { + query: { detail: error instanceof Error ? error.message : String(error) }, + }); + } +} + +if (primary) { + app.whenReady().then(async () => { + app.setName("Parson"); + Menu.setApplicationMenu(null); + app.setAsDefaultProtocolClient("parson"); + installDesktopBridge(); + await createWindow(); + }); +} + +app.on("window-all-closed", () => app.quit()); +app.on("before-quit", () => { + quitting = true; + if (backend && !backend.killed) backend.kill("SIGTERM"); +}); diff --git a/apps/desktop/electron/preload.cjs b/apps/desktop/electron/preload.cjs new file mode 100644 index 00000000..05368107 --- /dev/null +++ b/apps/desktop/electron/preload.cjs @@ -0,0 +1,20 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("__PARSON_ELECTRON__", { + invoke(command, args) { + return ipcRenderer.invoke("parson:invoke", command, args); + }, + windowControls: { + close: () => ipcRenderer.invoke("parson:window-control", "close"), + isMaximized: () => + ipcRenderer.invoke("parson:window-control", "is-maximized"), + minimize: () => ipcRenderer.invoke("parson:window-control", "minimize"), + toggleMaximize: () => + ipcRenderer.invoke("parson:window-control", "toggle-maximize"), + watchMaximized: (callback) => { + ipcRenderer.on("parson:maximized", (_event, maximized) => + callback(Boolean(maximized)), + ); + }, + }, +}); diff --git a/apps/desktop/electron/startup-error.html b/apps/desktop/electron/startup-error.html new file mode 100644 index 00000000..2cf561ba --- /dev/null +++ b/apps/desktop/electron/startup-error.html @@ -0,0 +1,89 @@ + + + + + + Parson could not start + + + + +
+

Parson could not start

+

The local server did not become available.

+
+ + + diff --git a/apps/desktop/electron/startup.html b/apps/desktop/electron/startup.html new file mode 100644 index 00000000..97630a75 --- /dev/null +++ b/apps/desktop/electron/startup.html @@ -0,0 +1,84 @@ + + + + + + + Parson + + + + +
P
+ + + diff --git a/apps/desktop/flatpak/com.parsonlabs.Parson.yml b/apps/desktop/flatpak/com.parsonlabs.Parson.yml new file mode 100644 index 00000000..4fad2831 --- /dev/null +++ b/apps/desktop/flatpak/com.parsonlabs.Parson.yml @@ -0,0 +1,22 @@ +app-id: com.parsonlabs.Parson +runtime: org.gnome.Platform +runtime-version: "49" +sdk: org.gnome.Sdk +command: parson-music-linux +finish-args: + - --share=ipc + - --share=network + - --socket=wayland + - --socket=fallback-x11 + - --socket=pulseaudio + - --device=dri + - --talk-name=org.freedesktop.Notifications + - --talk-name=org.freedesktop.portal.Desktop +modules: + - name: parson + buildsystem: simple + build-commands: + - install -Dm755 Parson.AppImage /app/bin/parson-music-linux + sources: + - type: file + path: Parson.AppImage diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 00000000..df580cec --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,77 @@ +{ + "name": "parson-music-desktop", + "version": "1.0.0", + "private": true, + "license": "GPL-3.0-only", + "description": "Parson desktop client for Linux and Windows", + "homepage": "https://github.com/ParsonLabs/Parson", + "author": { + "name": "ParsonLabs" + }, + "desktopName": "com.parsonlabs.parson.desktop", + "main": "electron/main.cjs", + "scripts": { + "desktop:dev": "node scripts/prepare-sidecar.cjs && electron .", + "desktop:build": "node scripts/prepare-sidecar.cjs && node scripts/build.cjs" + }, + "devDependencies": { + "electron": "43.1.1", + "electron-builder": "26.15.3" + }, + "build": { + "appId": "com.parsonlabs.parson", + "productName": "Parson", + "asar": true, + "directories": { + "output": "../../target/release/bundle/electron" + }, + "files": [ + "electron/main.cjs", + "electron/preload.cjs", + "electron/startup.html", + "electron/startup-error.html", + "package.json" + ], + "extraResources": [ + { + "from": "electron/bin", + "to": ".", + "filter": [ + "parson-music-server", + "parson-music-server.exe" + ] + }, + { + "from": "../web/public/icons/icon-512.png", + "to": "parson.png" + } + ], + "linux": { + "target": [ + "AppImage", + "deb" + ], + "category": "AudioVideo;Audio;Player", + "executableName": "parson", + "icon": "../web/public/icons/icon-512.png", + "maintainer": "ParsonLabs", + "syncDesktopName": true, + "artifactName": "Parson_${version}_${arch}.${ext}" + }, + "win": { + "target": [ + "nsis" + ], + "icon": "../../crates/windows-server/parson.ico", + "executableName": "Parson", + "artifactName": "Parson_${version}_${arch}-setup.${ext}" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "Parson" + } + } +} diff --git a/apps/desktop/scripts/build-linux.sh b/apps/desktop/scripts/build-linux.sh new file mode 100644 index 00000000..47bd9cf7 --- /dev/null +++ b/apps/desktop/scripts/build-linux.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +workspace="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "$workspace/apps/desktop" +bunx electron-builder --linux AppImage + +if ldconfig -p 2>/dev/null | grep -q 'libcrypt\.so\.1'; then + bunx electron-builder --linux deb +else + echo "Skipping optional .deb: this host does not provide libcrypt.so.1 for electron-builder's fpm." >&2 +fi diff --git a/apps/desktop/scripts/build.cjs b/apps/desktop/scripts/build.cjs new file mode 100644 index 00000000..d8792ab2 --- /dev/null +++ b/apps/desktop/scripts/build.cjs @@ -0,0 +1,52 @@ +const { spawnSync } = require("node:child_process"); +const { existsSync, readdirSync, rmSync } = require("node:fs"); +const path = require("node:path"); + +const desktopDirectory = path.resolve(__dirname, ".."); +const outputDirectory = path.resolve( + desktopDirectory, + "../../target/release/bundle/electron", +); + +function run(command, args) { + const result = spawnSync(command, args, { + cwd: desktopDirectory, + stdio: "inherit", + }); + if (result.error) throw result.error; + if (result.status !== 0) { + throw new Error(`${command} exited with status ${result.status}`); + } +} + +function cleanOutput(predicate) { + if (!existsSync(outputDirectory)) return; + for (const entry of readdirSync(outputDirectory, { withFileTypes: true })) { + if (!predicate(entry.name)) continue; + rmSync(path.join(outputDirectory, entry.name), { + recursive: true, + force: true, + }); + } +} + +if (process.platform === "linux") { + cleanOutput( + (name) => + name === "linux-unpacked" || + name.endsWith(".AppImage") || + name.endsWith(".deb"), + ); + run("bash", [path.join(__dirname, "build-linux.sh")]); +} else if (process.platform === "win32") { + cleanOutput((name) => name === "win-unpacked" || name.endsWith("-setup.exe")); + run(process.execPath, [ + require.resolve("electron-builder/cli.js"), + "--win", + "nsis", + ]); +} else { + throw new Error( + `Desktop packaging is not configured for ${process.platform}.`, + ); +} diff --git a/apps/desktop/scripts/package-windows.ps1 b/apps/desktop/scripts/package-windows.ps1 new file mode 100644 index 00000000..34a4ca53 --- /dev/null +++ b/apps/desktop/scripts/package-windows.ps1 @@ -0,0 +1,32 @@ +param( + [ValidatePattern('^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$')] + [string]$Version = "1.0.0", + [string]$OutputDirectory = "artifacts\windows" +) + +$ErrorActionPreference = "Stop" +$workspace = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..\..\..")).Path +$previousLocation = Get-Location +Set-Location -LiteralPath $workspace +try { + $configuredVersion = (Get-Content -LiteralPath (Join-Path $workspace "package.json") -Raw | ConvertFrom-Json).version + if ($Version -ne $configuredVersion) { + throw "Requested version $Version does not match package.json version $configuredVersion." + } + + & bun install --frozen-lockfile + if ($LASTEXITCODE -ne 0) { throw "Installing JavaScript dependencies failed." } + & bun --filter parson-music-desktop desktop:build + if ($LASTEXITCODE -ne 0) { throw "Building the Windows Electron installer failed." } + + $bundle = Join-Path $workspace "target\release\bundle\electron" + $output = Join-Path $workspace $OutputDirectory + New-Item -ItemType Directory -Path $output -Force | Out-Null + Get-ChildItem -LiteralPath $bundle -File -Filter "*.exe" | + Copy-Item -Destination $output -Force + + Write-Output "Created Windows Electron installer(s) in: $output" +} +finally { + Set-Location -LiteralPath $previousLocation +} diff --git a/apps/desktop/scripts/prepare-sidecar.cjs b/apps/desktop/scripts/prepare-sidecar.cjs new file mode 100644 index 00000000..84745f1b --- /dev/null +++ b/apps/desktop/scripts/prepare-sidecar.cjs @@ -0,0 +1,47 @@ +const { + copyFileSync, + chmodSync, + existsSync, + mkdirSync, + unlinkSync, +} = require("node:fs"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const workspace = path.resolve(__dirname, "../../.."); +const binDirectory = path.join(workspace, "apps", "desktop", "electron", "bin"); +const windows = process.platform === "win32"; +const executable = `parson-music-server${windows ? ".exe" : ""}`; + +function run(command, args, cwd = workspace) { + const result = spawnSync(command, args, { cwd, stdio: "inherit" }); + if (result.error) throw result.error; + if (result.status !== 0) { + throw new Error(`${command} exited with status ${result.status}`); + } +} + +run("bun", ["run", "build"], path.join(workspace, "apps", "web")); +run("cargo", [ + "build", + "--manifest-path", + path.join(workspace, "Cargo.toml"), + "-p", + "parson-music", + "--release", +]); + +mkdirSync(binDirectory, { recursive: true }); +const otherExecutable = path.join( + binDirectory, + windows ? "parson-music-server" : "parson-music-server.exe", +); +if (existsSync(otherExecutable)) unlinkSync(otherExecutable); +const source = path.join(workspace, "target", "release", executable); +const destination = path.join(binDirectory, executable); +copyFileSync(source, destination); +if (!windows) chmodSync(destination, 0o755); + +console.log( + `Prepared the shared Electron shell with the ${process.platform} backend.`, +); diff --git a/apps/desktop/scripts/verify-linux-package.sh b/apps/desktop/scripts/verify-linux-package.sh new file mode 100644 index 00000000..6d3bdac7 --- /dev/null +++ b/apps/desktop/scripts/verify-linux-package.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +bundle_dir="${1:-target/release/bundle/electron}" +bundle_dir="$(realpath "$bundle_dir")" +mapfile -t debs < <(find "$bundle_dir" -maxdepth 1 -type f -name '*.deb' -print) +mapfile -t appimages < <(find "$bundle_dir" -maxdepth 1 -type f -name '*.AppImage' -print) + +if [[ "${#debs[@]}" -gt 1 || "${#appimages[@]}" -ne 1 ]]; then + echo "Expected one AppImage and at most one optional .deb in $bundle_dir" >&2 + exit 1 +fi + +test -s "${appimages[0]}" +if [[ "${#debs[@]}" -eq 1 ]]; then + test -s "${debs[0]}" + if command -v dpkg-deb >/dev/null; then + dpkg-deb --info "${debs[0]}" >/dev/null + else + ar t "${debs[0]}" | grep -qx 'debian-binary' + ar t "${debs[0]}" | grep -q '^control\.tar' + ar t "${debs[0]}" | grep -q '^data\.tar' + fi +fi +chmod +x "${appimages[0]}" +extract_dir="$(mktemp -d -t parson-package-verify-XXXXXX)" +(cd "$extract_dir" && "${appimages[0]}" --appimage-extract >/dev/null) +test -x "$extract_dir/squashfs-root/AppRun" +test -x "$extract_dir/squashfs-root/resources/parson-music-server" +test -f "$extract_dir/squashfs-root/resources/app.asar" +rm -rf "$extract_dir" + +sha256sum "${appimages[0]}" "${debs[@]}" diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore deleted file mode 100644 index 6976b71c..00000000 --- a/apps/docs/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# deps -/node_modules - -# generated content -.map.ts -.contentlayer - -# test & build -/coverage -/.next/ -/out/ -/build -*.tsbuildinfo - -# misc -.DS_Store -*.pem -/.pnp -.pnp.js -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# others -.env*.local -.vercel -next-env.d.ts \ No newline at end of file diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts deleted file mode 100644 index 74983f97..00000000 --- a/apps/docs/app/api/search/route.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getPages } from '@/app/source'; -import { createSearchAPI } from 'fumadocs-core/search/server'; - -export const { GET } = createSearchAPI('advanced', { - indexes: getPages().map((page) => ({ - title: page.data.title, - structuredData: page.data.exports.structuredData, - id: page.url, - url: page.url, - })), -}); diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx deleted file mode 100644 index 901da0f3..00000000 --- a/apps/docs/app/docs/[[...slug]]/page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { getPage, getPages } from '@/app/source'; -import type { Metadata } from 'next'; -import { DocsPage, DocsBody } from 'fumadocs-ui/page'; -import { notFound } from 'next/navigation'; - -export default async function Page({ - params, -}: { - params: { slug?: string[] }; -}) { - const page = getPage(params.slug); - - if (page == null) { - notFound(); - } - - const MDX = page.data.exports.default; - - return ( - - -

{page.data.title}

- -
-
- ); -} - -export async function generateStaticParams() { - return getPages().map((page) => ({ - slug: page.slugs, - })); -} - -export function generateMetadata({ params }: { params: { slug?: string[] } }) { - const page = getPage(params.slug); - - if (page == null) notFound(); - - return { - title: page.data.title, - description: page.data.description, - } satisfies Metadata; -} diff --git a/apps/docs/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx deleted file mode 100644 index cc286a85..00000000 --- a/apps/docs/app/docs/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { pageTree } from '../source'; -import { DocsLayout } from 'fumadocs-ui/layout'; -import { RollButton } from "fumadocs-ui/components/roll-button" -import type { ReactNode } from 'react'; -import { Metadata } from 'next'; - -export const metadata: Metadata = { - title: { - default: "ParsonLabs Documentation", - template: "%s | ParsonLabs Documentation" - } -} - -export default function RootDocsLayout({ children }: { children: ReactNode }) { - return ( - - - {children} - - ); -} diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css deleted file mode 100644 index b5c61c95..00000000 --- a/apps/docs/app/global.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx deleted file mode 100644 index c8007816..00000000 --- a/apps/docs/app/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import "./global.css" -import { RootProvider } from 'fumadocs-ui/provider'; -import { Inter } from 'next/font/google'; -import type { ReactNode } from 'react'; - -const inter = Inter({ - subsets: ['latin'], -}); - -export default function Layout({ children }: { children: ReactNode }) { - return ( - - - {children} - - - ); -} diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx deleted file mode 100644 index 7f74e058..00000000 --- a/apps/docs/app/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function HomePage() { - redirect("/docs") -} diff --git a/apps/docs/app/source.ts b/apps/docs/app/source.ts deleted file mode 100644 index 5cb887be..00000000 --- a/apps/docs/app/source.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { map } from '@/.map'; -import { createMDXSource } from 'fumadocs-mdx'; -import { loader } from 'fumadocs-core/source'; - -export const { getPage, getPages, pageTree } = loader({ - baseUrl: '/docs', - rootDir: 'docs', - source: createMDXSource(map), -}); diff --git a/apps/docs/content/docs/contributing/contribution-guide.mdx b/apps/docs/content/docs/contributing/contribution-guide.mdx deleted file mode 100644 index 7093078d..00000000 --- a/apps/docs/content/docs/contributing/contribution-guide.mdx +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Contribution Guide -description: Guide to Contributing to the ParsonLabs Music repository ---- - -In order to contribute, it is essential that you follow our core ethos when developing. - -## What should I work on? -We use Github Projects to manage feature requests, the kanban board [can be found here](https://github.com/users/WillKirkmanM/projects/2). - -} -/> diff --git a/apps/docs/content/docs/contributing/how-to.mdx b/apps/docs/content/docs/contributing/how-to.mdx deleted file mode 100644 index 4ec51cdb..00000000 --- a/apps/docs/content/docs/contributing/how-to.mdx +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: How To -description: Guide on finding information not covered in the documentation ---- -A Guide on finding information that is not covered in the documentation. - -## Who does this apply to? -When there is a question you have that is not answered in the documentation, it is most likely not the only time the question will be asked and will need to be put in the documentation - -## How To -To ask a question not covered in the Documentation, it is recommended to create a discussion on Github - - - } - /> - - } - /> - - - - It is possible that someone with the same question has asked and recieved an answer! Before you ask a question, make sure it hasn't been answered in both [Discussions](https://github.com/WillKirkmanM/music/discussions) and [Issues](https://github.com/WillKirkmanM/music/issues) - \ No newline at end of file diff --git a/apps/docs/content/docs/contributing/index.mdx b/apps/docs/content/docs/contributing/index.mdx deleted file mode 100644 index e7a040d1..00000000 --- a/apps/docs/content/docs/contributing/index.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Contributing -description: Guides to Contributing to the ParsonLabs Music Repository -index: true ---- - -Guides to Contributing to the ParsonLabs Music Repository \ No newline at end of file diff --git a/apps/docs/content/docs/contributing/meta.json b/apps/docs/content/docs/contributing/meta.json deleted file mode 100644 index 8b15c408..00000000 --- a/apps/docs/content/docs/contributing/meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "pages": [ - "contribution-guide", - "how-to", - "reporting-bugs", - "requesting-features" - ] -} \ No newline at end of file diff --git a/apps/docs/content/docs/contributing/reporting-bugs.mdx b/apps/docs/content/docs/contributing/reporting-bugs.mdx deleted file mode 100644 index 64d24220..00000000 --- a/apps/docs/content/docs/contributing/reporting-bugs.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Reporting Bugs -description: Guide to Reporting Bugs through Github Issues ---- - -Bugs can be terrible for the user experience and need to be fixed. To do this, we need the help of dilligent users to concisely but effectively detail the aspects of a bug they experience, this page will contain information on how to successfully report a bug to the music developers. - -## Requirements for Reporting a Bug -In order for a "bug" to qualify as a bug, it needs to fufil these properties: - -- It harms the user experience -- It is not intended by the developers (a feature) - -### Steps to Effectively create an issue - - - Explain what the issue is - - - Document through images how the issue appears - - - Write the intended behaviour - - - Submit the Issue - - - -## How to report a bug - - - } - /> - - } - /> - - -It may be the case where someone has reported this issue in the past, only for it to be clarified as a feature in music. Check whether this is the case by [heading to the issues page](https://github.com/WillKirkmanM/music/issues). If this is not the case, feel free to [create an issue](https://github.com/WillKirkmanM/music/issues/new). diff --git a/apps/docs/content/docs/contributing/requesting-features.mdx b/apps/docs/content/docs/contributing/requesting-features.mdx deleted file mode 100644 index 77260d03..00000000 --- a/apps/docs/content/docs/contributing/requesting-features.mdx +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Requesting Features -description: Guide to Requesting Features through Github Issues ---- - -Sometimes there is something you would really like to see in music. No matter the complexity, we will be happy to work on enhancing music into the ultimate streaming platform. - -## Requirements for Requesting a Feature -In order to consider a feature, it needs to fufil these properties: - -- It enhances the user experience -- It does not go against our ethos - -### Steps to Effectively create an issue - - - Explain what the feature is in detail - - - Provide images of a pre-existing / pictoral representation of the feature - - - Submit the Discussion - - - -## How to Request a Feature - - - } - /> - - } - /> - - -It may be the case where someone has requested a feature in the past, only for it to be clarified as to why it is or is not in music. Check whether this is the case by [heading to the feature request page](https://github.com/WillKirkmanM/music/discussions/categories/feature-request). If this is not the case, feel free to [create a feature request](https://github.com/WillKirkmanM/music/discussions/new?category=feature-request). - diff --git a/apps/docs/content/docs/getting-started/docker.mdx b/apps/docs/content/docs/getting-started/docker.mdx deleted file mode 100644 index abcc6be8..00000000 --- a/apps/docs/content/docs/getting-started/docker.mdx +++ /dev/null @@ -1,147 +0,0 @@ ---- -title: Docker -description: Deploy ParsonLabs Music to a Docker Container ---- - -We use Docker Compose to orchestrate the deployments of both the frontend and the backend containers. - - - - - - - - - - -In the root of the repository, you will see two files, `docker-compose.yml` and `docker-compose-dev.yml`, these will be used to deploy music on both `production` and `development` environments - -## For Production - -### Editing docker-compose.yml -To build the app in production, you will need to edit the `docker-compose.yml` file - -```yaml title="docker-compose.yml" -version: '3' -services: - frontend: - build: - context: . - dockerfile: ./docker/production/Dockerfile.frontend - ports: - - "3000:3000" - volumes: - - MEDIA_PATH:/Music - - MISSING_COVER_ART_PATH:/missing_cover_art - - CONFIG_PATH:/Config - environment: - - BACKEND_PORT=3001 - - NEXTAUTH_SECRET=SALKDNAKFB@!WQ# - - NEXTAUTH_URL=http://localhost:3000 - - NODE_ENV=production - - DEPLOYMENT_TYPE=docker - backend: - build: - context: . - dockerfile: ./docker/production/Dockerfile.backend - ports: - - "3001:3001" - volumes: - - MEDIA_PATH:/Music - - MISSING_COVER_ART_PATH:/missing_cover_art - - CONFIG_PATH:/Config -``` - -More specifically, the volumes `MEDIA_PATH`, `MISSING_COVER_ART_PATH` and `CONFIG_PATH` will need to be mapped to the respective paths on the host system. The `NEXTAUTH_URL` will also need to be changed to the intended redirect url after a user is logged in. - -### Starting music in docker-compose -To start music, when the volumes and environment variables have been set, run the command: - -```bash -docker-compose up -d -``` - -This will start both the `frontend` and `backend` containers as a `daemon`. - - - Running the containers as a daemon or detached means that the containers will run in the background and means you can safely exit the console. - - - -## For Development - -### Editing docker-compose.yml -To build the app in production, you will need to edit the `docker-compose-dev.yml` file - -```yaml title="docker-compose-dev.yml" -version: '3.8' - -x-defaults: - &defaults - init: true - tty: true - networks: - - monorepo_network - -services: - frontend: - build: - context: . - dockerfile: ./docker/development/Dockerfile.frontend - args: - - APP=music - - DIR_NAME=web - - START_COMMAND=dev - - command: yarn workspace music dev - ports: - - "3000:3000" - volumes: - - ./apps/web:/app/apps/web - - /app/apps/web/node_modules - - /app/apps/web/.next - - /apps/web/node_modules - - - MEDIA_PATH:/Music - - MISSING_COVER_ART_PATH:/missing_cover_art - - CONFIG_PATH:/Config - environment: - - PORT=3000 - - BACKEND_PORT=3001 - - NEXTAUTH_SECRET=SALKDNAKFB@!WQ# - - NEXTAUTH_URL=http://localhost:3000 - - NODE_ENV=development - - DEPLOYMENT_TYPE=docker - backend: - build: - context: . - dockerfile: ./docker/development/Dockerfile.backend - ports: - - "3001:3001" - volumes: - - ./apps/web/backend:/app/src - - - MEDIA_PATH:/Music - - MISSING_COVER_ART_PATH:/missing_cover_art - - CONFIG_PATH:/Config - environment: - - NODE_ENV=development - -networks: - monorepo_network: -``` - - -More specifically, the volumes `MEDIA_PATH`, `MISSING_COVER_ART_PATH` and `CONFIG_PATH` will need to be mapped to the respective paths on the host system. The `NEXTAUTH_URL` will also need to be changed to the intended redirect url after a user is logged in. - -### Starting music in docker-compose -To start music, when the volumes and environment variables have been set, run the command: - -```bash -docker-compose -f docker-compose-dev.yml up -``` - - - The `-f` flag specifies the dockerfile that Docker Compose will use, by default it will use `docker-compose.yml` and it needs to be before the running command (such as `up` or `down`). If the above does not work, add `COMPOSE_FILE` as an environment variable and the name of the docker compose file for the same function. - - diff --git a/apps/docs/content/docs/getting-started/index.mdx b/apps/docs/content/docs/getting-started/index.mdx deleted file mode 100644 index 5324cb7b..00000000 --- a/apps/docs/content/docs/getting-started/index.mdx +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: Getting Started -description: Guides on Getting Started -index: true ---- - -Welcome to the Getting Started page, currently we have support for TrueNas Scale, Docker and Local installations. \ No newline at end of file diff --git a/apps/docs/content/docs/getting-started/locally.mdx b/apps/docs/content/docs/getting-started/locally.mdx deleted file mode 100644 index fd0b0eb0..00000000 --- a/apps/docs/content/docs/getting-started/locally.mdx +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Locally -description: Deploy it Locally ---- - - -To build music, it is recommended to add the `music.json` configuration file to the web folder during build to generate the static pages: - - - - - - - - - - - - - - - - - ### Install dependencies - ```bash - bun install - ``` - - This will install all dependencies for all the apps and packages which includes `web`, `docs` and `landing` - - - - - ### Build music only - Since music is contained in a `monorepo`, it is possible to target the music app specifically to build: - - - ```bash - bunx cross-env NODE_ENV=production DEPLOYMENT_TYPE=containerless bun run build --filter music --force - ``` - - Music is a self-hostable application, and as such, contains a large range of ways we can deploy it. When deploying locally or in a containerless environment, we have to tell music that we do not intent to look for files in places that would suit containerised environments like docker. - - - - As we are using specific environments with `cross-env`, [Turborepo](https://turbo.build/repo) does not pick up on a change in environment variables and uses the cached build already generated. The `--force` flag ensures that all builds are clean. - - - - - ### Run music - ```bash - bunx cross-env NODE_ENV=production DEVELOPMENT_TYPE=containerless bun run start --filter music - ``` - - This will start the production server on port 3000, to change the port, add the `--port` flag - - \ No newline at end of file diff --git a/apps/docs/content/docs/getting-started/meta.json b/apps/docs/content/docs/getting-started/meta.json deleted file mode 100644 index 945dcc9e..00000000 --- a/apps/docs/content/docs/getting-started/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "pages": [ - "truenas-scale", - "docker", - "locally" - ] -} \ No newline at end of file diff --git a/apps/docs/content/docs/getting-started/truenas-scale.mdx b/apps/docs/content/docs/getting-started/truenas-scale.mdx deleted file mode 100644 index e2c5b2cb..00000000 --- a/apps/docs/content/docs/getting-started/truenas-scale.mdx +++ /dev/null @@ -1,248 +0,0 @@ ---- -title: TrueNas Scale -description: Steps to install ParsonLabs Music with TrueNas Scale ---- - -In order to run ParsonLabs Music, we are going to run it in two docker containers. As such, we will need to use [`custom-app` from the premium train](https://truecharts.org/charts/premium/custom-app/) - -## Setting up TrueCharts -TrueCharts is a community driven extended repository containing many popular and up to date applications that TrueNas lacks, while also providing an easy setup experience. - - - -[TrueCharts has provided an official guide to installing the chart and can be found here](https://truecharts.org/scale/) - - -While we add support for ParsonLabs Music directly into - - - ### Head to Apps - - - - - ### Click Discover Apps on the top right - - - - - ### Click Manage Catalog on the top right of the discover page - - - - - ### Click Add Catalog - - - - - ### After reading the iXsystems notice, click Continue and enter the required information - - - - - ### Click save and allow SCALE to refresh its catalog with TrueCharts (this may take a few minutes) - - - -## Setting up ParsonLabs Music on TrueNas Scale -To setup up, we need - -### Using custom-app -The [`custom-app`](https://truecharts.org/charts/premium/custom-app/) application from the TrueCharts premium train allows any deployment to a TrueCharts-like environment. Since music has [Docker containers on the Github registry](https://github.com/WillKirkmanM/music/packages), we are able to deploy music on TrueNas. - - - - #### Create a custom-app instance - Head to the `Discover Apps` section in the `Apps` section and search for `custom-app` and then click install. - - - - #### Set Container Repository and Container Tag - Set the `Container Repository` to `ghcr.io/willkirkmanm/music` and the `Container Tag` to `latest` - - - - #### Disable WebUI Portal - It is recommended but not necessary to uncheck the `Enable WebUI Portal` button in the `Portal Configuration` section of `General` - - - - #### Set Environment Variables - In the `Extra Environment Variables` section, it is required to have these environment variables set: - - - - - Environment Variable - - - - Value - - - - Description - - - - - - - - `BACKEND_PORT` - - - 3001 - - - This is the port that music-backend will run on. - - - - - - `NEXT_AUTH_SECRET` - - - xVcsAVKpo9yOTxbq/JCmHHGYP4MgtIrt9l74s3937xg= - - - `openssl rand -base64 32` or you can visit [Generate Plus](https://generate.plus/en/base64) and set the length to 32 - - - - - - `NEXTAUTH_URL` - - - YOUR DOMAIN - - - This will control how the authentication redirects the user. This will need to either the domain name of the music app with `ingress` as a reverse proxy or the local ip address. - - - - - - `DEPLOYMENT_TYPE` - - - docker - - - This controls how the configuration for music is obtained. For `containerless` environments, the `music.json` configuration is searched in the `/app/Config` instead of the root filesystem. - - - - -
-
- - - ### Set Ports to Listen On - - - - - Port Configuration Name - - - Value - - - - - - - - **Port Type** - - - http - - - - - - **Target Port** - - - 3000 - - - - - - **Port** - - - This is the port you will be accessing music on. - - - -
-
- - - ### Accessing Filesystem (Persistance) - For music to function properly, these three volumes need to be mapped to the host system: - - - - - Storage Name - - - Mount Path - - - Description - - - - - - - - Music path - - - /Music - - - This is the directory where your music is stored. - - - - - - Missing Cover Art - - - /missing_cover_art - - - This is the directory where the missing cover art for albums will be downloaded to. - - - - - Config - - - /Config - - - This is the directory where `music.json` will be stored and accessed. - - - -
-
- - - ### Deploy - -
\ No newline at end of file diff --git a/apps/docs/content/docs/index.mdx b/apps/docs/content/docs/index.mdx deleted file mode 100644 index 7488718e..00000000 --- a/apps/docs/content/docs/index.mdx +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Introduction -description: Welcome to the ParsonLabs Music Documentation ---- - -Welcome to the ParsonLabs Music Documentation, these pages serve to contain guides in setting up, navigating and contributing the repository, along with the philosophies behind design choices. - -## Getting Started - - - - - - - -## Contributing - - - - - - - \ No newline at end of file diff --git a/apps/docs/content/docs/meta.json b/apps/docs/content/docs/meta.json deleted file mode 100644 index 75923823..00000000 --- a/apps/docs/content/docs/meta.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "title": "ParsonLabs Music", - "root": true, - "pages": [ - "index", - "getting-started", - "contributing" - ] -} \ No newline at end of file diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx deleted file mode 100644 index 13136885..00000000 --- a/apps/docs/mdx-components.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { MDXComponents } from 'mdx/types'; -import defaultComponents from 'fumadocs-ui/mdx'; -import { Callout } from "fumadocs-ui/components/callout" -import { Folder, Files, File } from "fumadocs-ui/components/files" -import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; -import { Steps, Step } from "fumadocs-ui/components/steps" -import { Table, TableHeader, TableBody, TableHead, TableCell, TableRow } from "@music/ui/components/table" -import { SquareArrowOutUpRight, CirclePlus } from "lucide-react" - -export function useMDXComponents(components: MDXComponents): MDXComponents { - return { - ...defaultComponents, - ...components, - Callout, - ImageZoom, - Steps, - Step, - Table, - TableHeader, - TableBody, - TableHead, - TableCell, - TableRow, - Folder, - Files, - File, - SquareArrowOutUpRight, - CirclePlus - }; -} diff --git a/apps/docs/next-env.d.ts b/apps/docs/next-env.d.ts deleted file mode 100644 index 4f11a03d..00000000 --- a/apps/docs/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs deleted file mode 100644 index 3e44fbc3..00000000 --- a/apps/docs/next.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import createMDX from 'fumadocs-mdx/config'; - -const withMDX = createMDX(); - -/** @type {import('next').NextConfig} */ -const config = { - reactStrictMode: true, -}; - -export default withMDX(config); diff --git a/apps/docs/package.json b/apps/docs/package.json deleted file mode 100644 index a9d5f1e8..00000000 --- a/apps/docs/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "docs", - "version": "0.0.0", - "private": true, - "scripts": { - "build": "next build", - "dev": "next dev", - "start": "next start" - }, - "dependencies": { - "next": "^14.2.3", - "fumadocs-ui": "^11.1.1", - "lucide-react": "0.378.0", - "fumadocs-core": "^11.1.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "fumadocs-mdx": "^8.2.19" - }, - "devDependencies": { - "@types/react": "^18.3.2", - "@types/react-dom": "^18.3.0", - "@music/ui": "*", - "typescript": "^5.4.5", - "@types/mdx": "^2.0.13", - "autoprefixer": "^10.4.19", - "postcss": "^8.4.38", - "tailwindcss": "^3.4.3" - } -} \ No newline at end of file diff --git a/apps/docs/postcss.config.js b/apps/docs/postcss.config.js deleted file mode 100644 index 12a703d9..00000000 --- a/apps/docs/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/apps/docs/tailwind.config.js b/apps/docs/tailwind.config.js deleted file mode 100644 index a925dcbd..00000000 --- a/apps/docs/tailwind.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import { createPreset } from 'fumadocs-ui/tailwind-plugin'; - -/** @type {import('tailwindcss').Config} */ -export default { - darkMode: "class", - presets: [createPreset()], - content: [ - '../../node_modules/fumadocs-ui/dist/**/*.js', - './components/**/*.{ts,tsx}', - './app/**/*.{ts,tsx}', - './content/**/*.{md,mdx}', - './mdx-components.{ts,tsx}', - './node_modules/fumadocs-ui/dist/**/*.js', - ], -}; diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json deleted file mode 100644 index f90b1421..00000000 --- a/apps/docs/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "target": "ESNext", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "Bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "paths": { - "@/*": ["./*"] - }, - "plugins": [ - { - "name": "next" - } - ] - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["../../node_modules"] -} diff --git a/apps/landing/.gitignore b/apps/landing/.gitignore deleted file mode 100644 index 016b59ea..00000000 --- a/apps/landing/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# build output -dist/ - -# generated types -.astro/ - -# dependencies -node_modules/ - -# logs -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* - -# environment variables -.env -.env.production - -# macOS-specific files -.DS_Store - -# jetbrains setting folder -.idea/ diff --git a/apps/landing/.vscode/extensions.json b/apps/landing/.vscode/extensions.json deleted file mode 100644 index 22a15055..00000000 --- a/apps/landing/.vscode/extensions.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "recommendations": ["astro-build.astro-vscode"], - "unwantedRecommendations": [] -} diff --git a/apps/landing/.vscode/launch.json b/apps/landing/.vscode/launch.json deleted file mode 100644 index d6422097..00000000 --- a/apps/landing/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "command": "./node_modules/.bin/astro dev", - "name": "Development server", - "request": "launch", - "type": "node-terminal" - } - ] -} diff --git a/apps/landing/astro.config.mjs b/apps/landing/astro.config.mjs deleted file mode 100644 index 476e71ab..00000000 --- a/apps/landing/astro.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'astro/config'; -import tailwind from '@astrojs/tailwind'; -import react from "@astrojs/react"; - -// https://astro.build/config -export default defineConfig({ - integrations: [tailwind({ - applyBaseStyles: false - }), react()] -}); \ No newline at end of file diff --git a/apps/landing/package.json b/apps/landing/package.json deleted file mode 100644 index f5f21161..00000000 --- a/apps/landing/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "landing", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "astro dev", - "start": "astro dev", - "build": "astro check && astro build", - "preview": "astro preview", - "astro": "astro" - }, - "dependencies": { - "@astrojs/check": "^0.5.10", - "@astrojs/react": "^3.3.1", - "@astrojs/tailwind": "latest", - "@music/ui": "*", - "@radix-ui/react-icons": "^1.3.0", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.0", - "astro": "^4.7.0", - "astro-font": "^0.0.80", - "autoprefixer": "latest", - "postcss": "latest", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "tailwindcss": "latest", - "typescript": "^5.4.5" - } -} diff --git a/apps/landing/postcss.config.js b/apps/landing/postcss.config.js deleted file mode 100644 index 2734726a..00000000 --- a/apps/landing/postcss.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('@music/ui/postcss.config'); \ No newline at end of file diff --git a/apps/landing/public/favicon.svg b/apps/landing/public/favicon.svg deleted file mode 100644 index f157bd1c..00000000 --- a/apps/landing/public/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/apps/landing/src/components/Navbar.tsx b/apps/landing/src/components/Navbar.tsx deleted file mode 100644 index 58318217..00000000 --- a/apps/landing/src/components/Navbar.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from "react" - -import { cn } from "@music/ui/lib/utils" -import { - NavigationMenu, - NavigationMenuContent, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, - NavigationMenuTrigger, - navigationMenuTriggerStyle, -} from "@music/ui/components/navigation-menu" - -export default function Navbar() { - return ( - - - - Downloads - -
    - - Download the official server for Windows. - - - - Download the official server for Linux. - - -
-
-
- - - Documentation - - -
-
- ) -} - -const ListItem = React.forwardRef< - React.ElementRef<"a">, - React.ComponentPropsWithoutRef<"a"> ->(({ className, title, children, ...props }, ref) => { - return ( -
  • - - -
    {title}
    -

    - {children} -

    -
    -
    -
  • - ) -}) -ListItem.displayName = "ListItem" diff --git a/apps/landing/src/env.d.ts b/apps/landing/src/env.d.ts deleted file mode 100644 index acef35f1..00000000 --- a/apps/landing/src/env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/apps/landing/src/layout/layout.astro b/apps/landing/src/layout/layout.astro deleted file mode 100644 index e11f7046..00000000 --- a/apps/landing/src/layout/layout.astro +++ /dev/null @@ -1,22 +0,0 @@ ---- -import "@music/ui/globals.css" -import { AstroFont } from "astro-font"; ---- - - - - - - \ No newline at end of file diff --git a/apps/landing/src/pages/index.astro b/apps/landing/src/pages/index.astro deleted file mode 100644 index 67ee9663..00000000 --- a/apps/landing/src/pages/index.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import "@music/ui/globals.css" -import { Button } from "@music/ui/button" - -import Layout from "../layout/layout.astro" -import Navbar from "../components/Navbar" -import { GitHubLogoIcon } from "@radix-ui/react-icons" ---- - - -
    -
    - -
    -

    ParsonLabs Music

    -

    Own your Music

    -

    Need something to Listen to?

    -
    - - - - - -
    -
    \ No newline at end of file diff --git a/apps/landing/tailwind.config.js b/apps/landing/tailwind.config.js deleted file mode 100644 index 5049055c..00000000 --- a/apps/landing/tailwind.config.js +++ /dev/null @@ -1,2 +0,0 @@ - -export * from "@music/ui/tailwind.config"; \ No newline at end of file diff --git a/apps/landing/tsconfig.json b/apps/landing/tsconfig.json deleted file mode 100644 index 032ad645..00000000 --- a/apps/landing/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "astro/tsconfigs/strict", - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "react" - } -} \ No newline at end of file diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore new file mode 100644 index 00000000..a874b823 --- /dev/null +++ b/apps/mobile/.gitignore @@ -0,0 +1,35 @@ +node_modules/ + +.expo/ +dist/ +web-build/ +expo-env.d.ts + +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +.metro-health-check* + +npm-debug.* +yarn-debug.* +yarn-error.* + +.DS_Store +*.pem + +.env*.local + +*.tsbuildinfo + +example + +/ios +/android + +/modules/*/android/.gradle/ +/modules/*/android/build/ diff --git a/apps/mobile/.vscode/extensions.json b/apps/mobile/.vscode/extensions.json new file mode 100644 index 00000000..b7ed8377 --- /dev/null +++ b/apps/mobile/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/apps/mobile/.vscode/settings.json b/apps/mobile/.vscode/settings.json new file mode 100644 index 00000000..e2798e42 --- /dev/null +++ b/apps/mobile/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/apps/mobile/LICENSE b/apps/mobile/LICENSE new file mode 100644 index 00000000..8c4e057d --- /dev/null +++ b/apps/mobile/LICENSE @@ -0,0 +1,25 @@ +This notice applies only to portions derived from the Expo project template. +Parson's original source remains licensed under GPL-3.0-only by the repository-root +LICENSE. + +The MIT License (MIT) + +Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/mobile/README.md b/apps/mobile/README.md new file mode 100644 index 00000000..26d9faa7 --- /dev/null +++ b/apps/mobile/README.md @@ -0,0 +1,19 @@ +# Parson Mobile + +Expo client for Parson servers on the local network. + +## Development + +```bash +bun install +bun run start +bun run android +bun run ios +bun run web +bun run lint +bun run type-check +``` + +## Licensing + +GPL-3.0-only. See the root and local `LICENSE` files. diff --git a/apps/mobile/THIRD_PARTY_NOTICES.md b/apps/mobile/THIRD_PARTY_NOTICES.md new file mode 100644 index 00000000..e97000c5 --- /dev/null +++ b/apps/mobile/THIRD_PARTY_NOTICES.md @@ -0,0 +1,33 @@ +# Third-party notices + +Parson Mobile uses `react-native-audio-api` 0.13.2, distributed under the MIT +License: + +> MIT License +> +> Copyright (c) 2024 Software Mansion +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +The dependency also identifies WebKit-derived BSD-licensed source and FFmpeg +under LGPL 2.1 or later. Its upstream notices, license texts, source references, +and relinking instructions are maintained at: + +- https://github.com/software-mansion/react-native-audio-api/blob/0.13.2/THIRD_PARTY.md +- https://github.com/software-mansion/react-native-audio-api/tree/0.13.2/packages/react-native-audio-api/common/cpp/audioapi/libs/ffmpeg diff --git a/apps/mobile/app.json b/apps/mobile/app.json new file mode 100644 index 00000000..b5bea2ad --- /dev/null +++ b/apps/mobile/app.json @@ -0,0 +1,96 @@ +{ + "expo": { + "name": "Parson", + "slug": "parson", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/parson-icon.png", + "scheme": "parson", + "userInterfaceStyle": "dark", + "backgroundColor": "#000000", + "ios": { + "bundleIdentifier": "com.parsonlabs.parson", + "supportsTablet": true, + "infoPlist": { + "NSLocalNetworkUsageDescription": "Parson finds music libraries running on your local network.", + "NSBonjourServices": ["_parson._tcp"] + } + }, + "android": { + "package": "com.parsonlabs.parson", + "adaptiveIcon": { + "backgroundColor": "#000000", + "foregroundImage": "./assets/images/parson-maskable.png", + "monochromeImage": "./assets/images/parson-monochrome.png" + }, + "predictiveBackGestureEnabled": true, + "permissions": [ + "android.permission.INTERNET", + "android.permission.ACCESS_NETWORK_STATE", + "android.permission.CHANGE_WIFI_MULTICAST_STATE", + "android.permission.POST_NOTIFICATIONS" + ], + "blockedPermissions": [ + "android.permission.READ_EXTERNAL_STORAGE", + "android.permission.RECORD_AUDIO", + "android.permission.SYSTEM_ALERT_WINDOW", + "android.permission.WRITE_EXTERNAL_STORAGE" + ] + }, + "web": { + "output": "static", + "favicon": "./assets/images/parson-icon.png" + }, + "plugins": [ + "./plugins/with-fast-player-transitions", + "expo-router", + [ + "expo-navigation-bar", + { + "style": "light", + "hidden": false, + "enforceContrast": false + } + ], + [ + "react-native-audio-api", + { + "iosBackgroundMode": true, + "androidForegroundService": false, + "androidFSTypes": ["mediaPlayback"] + } + ], + [ + "expo-build-properties", + { + "android": { + "usesCleartextTraffic": true, + "enableMinifyInReleaseBuilds": true, + "enableShrinkResourcesInReleaseBuilds": true + } + } + ], + "expo-secure-store", + [ + "expo-splash-screen", + { + "backgroundColor": "#000000", + "image": "./assets/images/parson-icon.png", + "imageWidth": 112 + } + ], + [ + "expo-audio", + { + "recordAudioAndroid": false, + "microphonePermission": false, + "enableBackgroundPlayback": true + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } +} diff --git a/apps/mobile/assets/images/parson-icon.png b/apps/mobile/assets/images/parson-icon.png new file mode 100644 index 00000000..593c0a00 Binary files /dev/null and b/apps/mobile/assets/images/parson-icon.png differ diff --git a/apps/mobile/assets/images/parson-maskable.png b/apps/mobile/assets/images/parson-maskable.png new file mode 100644 index 00000000..92fc6ca0 Binary files /dev/null and b/apps/mobile/assets/images/parson-maskable.png differ diff --git a/apps/mobile/assets/images/parson-monochrome.png b/apps/mobile/assets/images/parson-monochrome.png new file mode 100644 index 00000000..593c0a00 Binary files /dev/null and b/apps/mobile/assets/images/parson-monochrome.png differ diff --git a/apps/mobile/eslint.config.js b/apps/mobile/eslint.config.js new file mode 100644 index 00000000..4566278e --- /dev/null +++ b/apps/mobile/eslint.config.js @@ -0,0 +1,9 @@ +const { defineConfig } = require("eslint/config"); +const expoConfig = require("eslint-config-expo/flat"); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ["dist/*"], + }, +]); diff --git a/apps/mobile/modules/parson-discovery/LICENSE b/apps/mobile/modules/parson-discovery/LICENSE new file mode 100644 index 00000000..8fb9e6b6 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/LICENSE @@ -0,0 +1,25 @@ +This notice applies only to portions derived from the Expo native-module template. +Parson's original source remains licensed under GPL-3.0-only by the repository-root +LICENSE. + +The MIT License (MIT) + +Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/mobile/modules/parson-discovery/android/build.gradle b/apps/mobile/modules/parson-discovery/android/build.gradle new file mode 100644 index 00000000..0968f2b5 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/android/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'com.android.library' + id 'expo-module-gradle-plugin' +} + +group = 'com.parsonlabs.discovery' +version = '0.1.0' + +android { + namespace "com.parsonlabs.discovery" + defaultConfig { + versionCode 1 + versionName "0.1.0" + } + lintOptions { + abortOnError false + } +} diff --git a/apps/mobile/modules/parson-discovery/android/src/main/AndroidManifest.xml b/apps/mobile/modules/parson-discovery/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bdae66c8 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/apps/mobile/modules/parson-discovery/android/src/main/java/com/parsonlabs/discovery/ParsonDiscoveryModule.kt b/apps/mobile/modules/parson-discovery/android/src/main/java/com/parsonlabs/discovery/ParsonDiscoveryModule.kt new file mode 100644 index 00000000..7ef72460 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/android/src/main/java/com/parsonlabs/discovery/ParsonDiscoveryModule.kt @@ -0,0 +1,61 @@ +package com.parsonlabs.discovery + +import android.content.Context +import android.net.nsd.NsdManager +import android.net.nsd.NsdServiceInfo +import android.net.wifi.WifiManager +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +class ParsonDiscoveryModule : Module() { + private var discoveryListener: NsdManager.DiscoveryListener? = null + private var multicastLock: WifiManager.MulticastLock? = null + private val resolved = mutableSetOf() + private val context: Context get() = requireNotNull(appContext.reactContext) + + private fun startDiscovery() { + if (discoveryListener != null) return + val nsd = context.getSystemService(Context.NSD_SERVICE) as NsdManager + val wifi = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + multicastLock = wifi.createMulticastLock("parson-discovery").apply { setReferenceCounted(false); acquire() } + resolved.clear() + val listener = object : NsdManager.DiscoveryListener { + override fun onDiscoveryStarted(type: String) = Unit + override fun onDiscoveryStopped(type: String) = Unit + override fun onStartDiscoveryFailed(type: String, code: Int) { stopDiscovery() } + override fun onStopDiscoveryFailed(type: String, code: Int) { stopDiscovery() } + override fun onServiceLost(service: NsdServiceInfo) { resolved.remove(service.serviceName) } + override fun onServiceFound(service: NsdServiceInfo) { + if (!service.serviceType.startsWith("_parson._tcp") || !resolved.add(service.serviceName)) return + @Suppress("DEPRECATION") + nsd.resolveService(service, object : NsdManager.ResolveListener { + override fun onResolveFailed(info: NsdServiceInfo, code: Int) { resolved.remove(info.serviceName) } + override fun onServiceResolved(info: NsdServiceInfo) { + val host = info.host?.hostAddress ?: info.host?.hostName ?: return + sendEvent("onService", mapOf("name" to info.serviceName, "host" to host, "port" to info.port)) + } + }) + } + } + discoveryListener = listener + nsd.discoverServices("_parson._tcp.", NsdManager.PROTOCOL_DNS_SD, listener) + } + + private fun stopDiscovery() { + val listener = discoveryListener ?: return + discoveryListener = null + runCatching { (context.getSystemService(Context.NSD_SERVICE) as NsdManager).stopServiceDiscovery(listener) } + runCatching { multicastLock?.release() } + multicastLock = null + } + + override fun definition() = ModuleDefinition { + Name("ParsonDiscovery") + Events("onService") + Function("start") { startDiscovery() } + Function("stop") { stopDiscovery() } + OnStartObserving("onService") { startDiscovery() } + OnStopObserving("onService") { stopDiscovery() } + OnDestroy { stopDiscovery() } + } +} diff --git a/apps/mobile/modules/parson-discovery/expo-module.config.json b/apps/mobile/modules/parson-discovery/expo-module.config.json new file mode 100644 index 00000000..537cfc0a --- /dev/null +++ b/apps/mobile/modules/parson-discovery/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["android", "apple"], + "apple": { + "modules": ["ParsonDiscoveryModule"] + }, + "android": { + "modules": ["com.parsonlabs.discovery.ParsonDiscoveryModule"] + } +} diff --git a/apps/mobile/modules/parson-discovery/index.ts b/apps/mobile/modules/parson-discovery/index.ts new file mode 100644 index 00000000..5e52744e --- /dev/null +++ b/apps/mobile/modules/parson-discovery/index.ts @@ -0,0 +1,2 @@ +export { default } from "./src/ParsonDiscoveryModule"; +export * from "./src/ParsonDiscovery.types"; diff --git a/apps/mobile/modules/parson-discovery/ios/ParsonDiscovery.podspec b/apps/mobile/modules/parson-discovery/ios/ParsonDiscovery.podspec new file mode 100644 index 00000000..67a170fb --- /dev/null +++ b/apps/mobile/modules/parson-discovery/ios/ParsonDiscovery.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |s| + s.name = 'ParsonDiscovery' + s.version = '1.0.0' + s.summary = 'A sample project summary' + s.description = 'A sample project description' + s.author = '' + s.homepage = 'https://docs.expo.dev/modules/' + s.platforms = { + :ios => '16.4', + :tvos => '16.4' + } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/apps/mobile/modules/parson-discovery/ios/ParsonDiscoveryModule.swift b/apps/mobile/modules/parson-discovery/ios/ParsonDiscoveryModule.swift new file mode 100644 index 00000000..088aaf60 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/ios/ParsonDiscoveryModule.swift @@ -0,0 +1,32 @@ +import ExpoModulesCore +import Foundation + +private final class ParsonServiceBrowser: NSObject, NetServiceBrowserDelegate, NetServiceDelegate { + let browser = NetServiceBrowser() + var emit: ((String, String, Int) -> Void)? + override init() { super.init(); browser.delegate = self } + func start() { browser.searchForServices(ofType: "_parson._tcp.", inDomain: "local.") } + func stop() { browser.stop() } + func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) { + service.delegate = self + service.resolve(withTimeout: 5) + } + func netServiceDidResolveAddress(_ sender: NetService) { + guard let hostname = sender.hostName else { return } + emit?(sender.name, hostname.hasSuffix(".") ? String(hostname.dropLast()) : hostname, sender.port) + } +} + +public class ParsonDiscoveryModule: Module { + private let discovery = ParsonServiceBrowser() + public func definition() -> ModuleDefinition { + Name("ParsonDiscovery") + Events("onService") + OnCreate { self.discovery.emit = { [weak self] name, host, port in self?.sendEvent("onService", ["name": name, "host": host, "port": port]) } } + Function("start") { self.discovery.start() } + Function("stop") { self.discovery.stop() } + OnStartObserving("onService") { self.discovery.start() } + OnStopObserving("onService") { self.discovery.stop() } + OnDestroy { self.discovery.stop() } + } +} diff --git a/apps/mobile/modules/parson-discovery/src/ParsonDiscovery.types.ts b/apps/mobile/modules/parson-discovery/src/ParsonDiscovery.types.ts new file mode 100644 index 00000000..edfa75e9 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/src/ParsonDiscovery.types.ts @@ -0,0 +1,9 @@ +export type ParsonDiscoveryModuleEvents = { + onService: (params: DiscoveredService) => void; +}; + +export type DiscoveredService = { + name: string; + host: string; + port: number; +}; diff --git a/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.ts b/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.ts new file mode 100644 index 00000000..99e1e8c5 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.ts @@ -0,0 +1,10 @@ +import { NativeModule, requireNativeModule } from "expo"; + +import { ParsonDiscoveryModuleEvents } from "./ParsonDiscovery.types"; + +declare class ParsonDiscoveryModule extends NativeModule { + start(): void; + stop(): void; +} + +export default requireNativeModule("ParsonDiscovery"); diff --git a/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.web.ts b/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.web.ts new file mode 100644 index 00000000..666738f0 --- /dev/null +++ b/apps/mobile/modules/parson-discovery/src/ParsonDiscoveryModule.web.ts @@ -0,0 +1,13 @@ +import { registerWebModule, NativeModule } from "expo"; + +import { ParsonDiscoveryModuleEvents } from "./ParsonDiscovery.types"; + +class ParsonDiscoveryModule extends NativeModule { + start() {} + stop() {} +} + +export default registerWebModule( + ParsonDiscoveryModule, + "ParsonDiscoveryModule", +); diff --git a/apps/mobile/package.json b/apps/mobile/package.json new file mode 100644 index 00000000..f1872e53 --- /dev/null +++ b/apps/mobile/package.json @@ -0,0 +1,64 @@ +{ + "name": "parson-mobile", + "main": "expo-router/entry", + "version": "1.0.0", + "license": "GPL-3.0-only", + "dependencies": { + "@expo/ui": "~57.0.7", + "@parson/music-sdk": "workspace:*", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/slider": "5.2.0", + "@shopify/flash-list": "2.0.2", + "@tanstack/react-query": "^5.101.2", + "expo": "~57.0.7", + "expo-audio": "~57.0.2", + "expo-blur": "~57.0.2", + "expo-build-properties": "~57.0.6", + "expo-constants": "~57.0.6", + "expo-device": "~57.0.1", + "expo-file-system": "~57.0.1", + "expo-font": "~57.0.1", + "expo-glass-effect": "~57.0.1", + "expo-haptics": "~57.0.1", + "expo-image": "~57.0.1", + "expo-linear-gradient": "~57.0.1", + "expo-linking": "~57.0.3", + "expo-navigation-bar": "~57.0.2", + "expo-router": "~57.0.7", + "expo-secure-store": "~57.0.1", + "expo-splash-screen": "~57.0.4", + "expo-status-bar": "~57.0.1", + "expo-symbols": "~57.0.1", + "expo-system-ui": "~57.0.1", + "expo-web-browser": "~57.0.1", + "lucide-react-native": "^1.25.0", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.86.0", + "react-native-audio-api": "^0.13.2", + "react-native-gesture-handler": "~2.32.0", + "react-native-reanimated": "4.5.0", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "4.25.2", + "react-native-svg": "15.15.4", + "react-native-web": "~0.21.0", + "react-native-worklets": "0.10.0" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "eslint": "^9.0.0", + "eslint-config-expo": "~57.0.0", + "typescript": "~6.0.3" + }, + "scripts": { + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web", + "lint": "expo lint", + "type-check": "tsc --noEmit", + "prebuild:android": "expo prebuild --platform android --clean", + "build:android": "expo run:android --variant release" + }, + "private": true +} diff --git a/apps/mobile/patches/react-native-audio-api@0.13.2.patch b/apps/mobile/patches/react-native-audio-api@0.13.2.patch new file mode 100644 index 00000000..53be8d9b --- /dev/null +++ b/apps/mobile/patches/react-native-audio-api@0.13.2.patch @@ -0,0 +1,64 @@ +diff --git a/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp b/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +--- a/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp ++++ b/android/src/main/cpp/audioapi/android/core/AudioPlayer.cpp +@@ -1,4 +1,5 @@ + #include ++#include + #include + #include + #include +@@ -29,15 +30,19 @@ AudioPlayer::AudioPlayer( + context_(context) {} + + bool AudioPlayer::openAudioStream() { ++ const bool useLegacyBuffering = android_get_device_api_level() <= 28; + AudioStreamBuilder builder; + +- builder.setSharingMode(SharingMode::Exclusive) ++ builder.setAudioApi(useLegacyBuffering ? AudioApi::OpenSLES : AudioApi::Unspecified) ++ ->setSharingMode(useLegacyBuffering ? SharingMode::Shared : SharingMode::Exclusive) + ->setFormat(AudioFormat::Float) + ->setFormatConversionAllowed(true) +- ->setPerformanceMode(PerformanceMode::LowLatency) ++ ->setPerformanceMode( ++ useLegacyBuffering ? PerformanceMode::None : PerformanceMode::LowLatency) + ->setChannelCount(channelCount_) + ->setSampleRateConversionQuality(SampleRateConversionQuality::Medium) +- ->setFramesPerDataCallback(RENDER_QUANTUM_SIZE) ++ ->setFramesPerDataCallback( ++ useLegacyBuffering ? RENDER_QUANTUM_SIZE * 8 : RENDER_QUANTUM_SIZE) + ->setDataCallback(shared_from_this()) + ->setErrorCallback(shared_from_this()) + ->setSampleRate(static_cast(sampleRate_)); +diff --git a/src/Audio/Audio.tsx b/src/Audio/Audio.tsx +--- a/src/Audio/Audio.tsx ++++ b/src/Audio/Audio.tsx +@@ -211,6 +211,7 @@ const Audio = React.memo( + () => ({ + play, + pause, ++ getCurrentTime: () => fileSourceRef.current?.getCurrentTime() ?? 0, + seekToTime, + setVolume: setVolumeState, + setMuted: setMutedState, +diff --git a/src/Audio/types.ts b/src/Audio/types.ts +--- a/src/Audio/types.ts ++++ b/src/Audio/types.ts +@@ -29,6 +29,7 @@ export type AudioTagPlaybackState = 'idle' | 'playing' | 'paused'; + export interface AudioTagHandle { + play: () => void; + pause: () => void; ++ getCurrentTime?: () => number; + seekToTime: (seconds: number) => void; + setVolume: (volume: number) => void; + setMuted: (muted: boolean) => void; +diff --git a/lib/typescript/Audio/types.d.ts b/lib/typescript/Audio/types.d.ts +--- a/lib/typescript/Audio/types.d.ts ++++ b/lib/typescript/Audio/types.d.ts +@@ -20,5 +20,6 @@ export interface AudioTagHandle { + play: () => void; + pause: () => void; ++ getCurrentTime?: () => number; + seekToTime: (seconds: number) => void; + setVolume: (volume: number) => void; + setMuted: (muted: boolean) => void; diff --git a/apps/mobile/plugins/with-fast-player-transitions.js b/apps/mobile/plugins/with-fast-player-transitions.js new file mode 100644 index 00000000..3d8664b9 --- /dev/null +++ b/apps/mobile/plugins/with-fast-player-transitions.js @@ -0,0 +1,48 @@ +const { withDangerousMod } = require("expo/config-plugins"); +const fs = require("node:fs/promises"); +const path = require("node:path"); + +const transitionDurationMs = 180; + +const animations = { + "rns_no_animation_medium.xml": ` + +`, + "rns_slide_in_from_bottom.xml": ` + +`, + "rns_slide_out_to_bottom.xml": ` + +`, +}; + +module.exports = (config) => + withDangerousMod(config, [ + "android", + async (nextConfig) => { + const animationDirectory = path.join( + nextConfig.modRequest.platformProjectRoot, + "app", + "src", + "main", + "res", + "anim", + ); + await fs.mkdir(animationDirectory, { recursive: true }); + await Promise.all( + Object.entries(animations).map(([name, contents]) => + fs.writeFile(path.join(animationDirectory, name), contents), + ), + ); + return nextConfig; + }, + ]); diff --git a/apps/mobile/src/app/(tabs)/_layout.tsx b/apps/mobile/src/app/(tabs)/_layout.tsx new file mode 100644 index 00000000..4e146300 --- /dev/null +++ b/apps/mobile/src/app/(tabs)/_layout.tsx @@ -0,0 +1,62 @@ +import { Redirect, Tabs } from "expo-router"; +import { Home, Library, Search, Settings } from "lucide-react-native"; +import { View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { palette } from "@/constants/colors"; +import { useSession } from "@/providers/session-provider"; + +function icon(Icon: typeof Home) { + return function TabBarIcon({ + color, + size, + }: { + color: unknown; + size: number; + }) { + return ; + }; +} + +export default function TabsLayout() { + const session = useSession(); + const insets = useSafeAreaInsets(); + if (session.phase !== "ready" && session.phase !== "offline") + return ; + return ( + + + + + + + + + ); +} diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx new file mode 100644 index 00000000..c36c2517 --- /dev/null +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -0,0 +1,82 @@ +import { getHomeEssentials } from "@parson/music-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { + ActivityIndicator, + RefreshControl, + ScrollView, + StyleSheet, + Text, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { + AlbumRail, + PageTitle, + Screen, + SectionTitle, + SongRow, +} from "@/components/music-ui"; +import { layout, palette } from "@/constants/colors"; + +export default function HomeScreen() { + const home = useQuery({ + queryKey: ["home"], + queryFn: getHomeEssentials, + }); + return ( + + + void home.refetch()} + /> + } + contentContainerStyle={{ + paddingBottom: layout.tabBar + layout.miniPlayer + 34, + }} + > + Home + {home.isPending ? ( + + ) : home.error ? ( + Could not load your music. + ) : home.data ? ( + <> + Recently played + {home.data.continue_listening.slice(0, 8).map((song) => ( + + ))} + Recommended songs + {(home.data.recommended.length + ? home.data.recommended + : home.data.shuffle + ) + .slice(0, 10) + .map((song) => ( + + ))} + Albums you might like + + + ) : null} + + + + ); +} + +const styles = StyleSheet.create({ + error: { color: palette.secondary, textAlign: "center", marginTop: 80 }, +}); diff --git a/apps/mobile/src/app/(tabs)/library.tsx b/apps/mobile/src/app/(tabs)/library.tsx new file mode 100644 index 00000000..5b48e3bb --- /dev/null +++ b/apps/mobile/src/app/(tabs)/library.tsx @@ -0,0 +1,475 @@ +import { + getLibraryCatalog, + getLibraryCatalogArtists, + getFavoriteSongDetails, + getPlaylists, + getSongInfos, + type LibrarySong, + type LibraryAlbum, + type LibraryCatalogAlbum, + type LibraryCatalogArtist, + type PlaylistSummary, +} from "@parson/music-sdk"; +import { + useInfiniteQuery, + useQuery, + useQueryClient, +} from "@tanstack/react-query"; +import { useRouter } from "expo-router"; +import { useState } from "react"; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { Artwork } from "@/components/artwork"; +import { PageTitle, Screen, SongRow } from "@/components/music-ui"; +import { layout, palette } from "@/constants/colors"; +import { playableCatalogSong } from "@/lib/playable-song"; +import { AlbumActions } from "@/components/album-actions"; +import { ArtistActions } from "@/components/artist-actions"; +import { PlaylistActions } from "@/components/playlist-actions"; +import { PlaylistPicker } from "@/components/playlist-picker"; +import { + downloadedRecords, + enrichDownloadedSongs, + groupDownloadedLibrary, + hydrateDownloads, + useDownloadsRevision, +} from "@/lib/downloads"; +import { useSession } from "@/providers/session-provider"; + +const PAGE_SIZE = 100; + +type Section = + "albums" | "songs" | "downloads" | "liked" | "artists" | "playlists"; + +export default function LibraryScreen() { + const session = useSession(); + const client = useQueryClient(); + const [selectedSection, setSection] = useState
    (() => + session.phase === "offline" ? "downloads" : "albums", + ); + const section = session.phase === "offline" ? "downloads" : selectedSection; + const [selectedAlbum, setSelectedAlbum] = + useState(null); + const [selectedArtist, setSelectedArtist] = + useState(null); + const [selectedDownloadedAlbum, setSelectedDownloadedAlbum] = + useState(null); + const [selectedPlaylist, setSelectedPlaylist] = + useState(null); + const [creatingPlaylist, setCreatingPlaylist] = useState(false); + const downloadsRevision = useDownloadsRevision(); + const router = useRouter(); + + const catalog = useInfiniteQuery({ + queryKey: ["library", "catalog", section], + initialPageParam: 0, + queryFn: ({ pageParam }) => + getLibraryCatalog(pageParam, PAGE_SIZE, section as "albums" | "songs"), + getNextPageParam: (lastPage, pages) => { + const next = pages.length * PAGE_SIZE; + const total = + section === "albums" ? lastPage.totalAlbums : lastPage.totalSongs; + return next < total ? next : undefined; + }, + enabled: section === "albums" || section === "songs", + }); + const artists = useInfiniteQuery({ + queryKey: ["library", "catalog", "artists"], + initialPageParam: 0, + queryFn: ({ pageParam }) => getLibraryCatalogArtists(pageParam, PAGE_SIZE), + getNextPageParam: (lastPage, pages) => + lastPage.length === PAGE_SIZE ? pages.length * PAGE_SIZE : undefined, + enabled: section === "artists", + }); + const liked = useInfiniteQuery({ + queryKey: ["favorite-song-details"], + initialPageParam: undefined as + { before_added_at: string; before_song_id: string } | undefined, + queryFn: ({ pageParam }) => + getFavoriteSongDetails({ limit: PAGE_SIZE, ...pageParam }), + getNextPageParam: (lastPage) => { + if (lastPage.length < PAGE_SIZE) return undefined; + const last = lastPage.at(-1); + return last + ? { before_added_at: last.added_at, before_song_id: last.song_id } + : undefined; + }, + enabled: section === "liked", + }); + const playlists = useQuery({ + queryKey: ["playlists"], + queryFn: getPlaylists, + enabled: section === "playlists", + }); + const downloads = useQuery({ + queryKey: ["downloads", downloadsRevision], + queryFn: async () => { + await hydrateDownloads(); + let records = downloadedRecords(); + const missing = records + .filter((record) => !record.song) + .map((record) => record.id); + if (missing.length) { + try { + const resolved = await getSongInfos(missing, false); + await enrichDownloadedSongs(Object.values(resolved) as LibrarySong[]); + records = downloadedRecords(); + } catch { + // Keep downloaded metadata available offline. + } + } + return records.flatMap((record) => (record.song ? [record.song] : [])); + }, + enabled: section === "downloads", + }); + const catalogPages = catalog.data?.pages ?? []; + const albumItems = catalogPages.flatMap((page) => page.albums); + const songItems = catalogPages.flatMap((page) => page.songs); + const playableSongs = songItems.map(playableCatalogSong); + const artistItems = artists.data?.pages.flat() ?? []; + const likedSongs = + liked.data?.pages.flatMap((page) => page.map((item) => item.song)) ?? []; + const downloadedItems = groupDownloadedLibrary(downloads.data ?? []); + const loading = + ((section === "albums" || section === "songs") && catalog.isPending) || + (section === "artists" && artists.isPending) || + (section === "liked" && liked.isPending) || + (section === "downloads" && downloads.isPending) || + (section === "playlists" && playlists.isPending); + return ( + + + Library + {session.phase === "offline" ? ( + + Offline downloads + + Your server is unavailable. Downloaded music still works. + + + ) : null} + + {( + (session.phase === "offline" + ? ["downloads"] + : [ + "albums", + "songs", + "downloads", + "liked", + "artists", + "playlists", + ]) as readonly Section[] + ).map((value) => ( + setSection(value)} + style={[styles.pill, section === value && styles.activePill]} + > + + {value === "liked" + ? "Liked Songs" + : `${value[0]?.toUpperCase()}${value.slice(1)}`} + + + ))} + {section === "playlists" ? ( + setCreatingPlaylist(true)} + > + + New + + ) : null} + + {loading ? ( + + ) : ( + + {section === "albums" && + albumItems.map((album) => ( + { + if (!selectedAlbum) router.push(`/album/${album.id}`); + }} + onLongPress={() => setSelectedAlbum(album)} + > + + + + {album.name} + + + Album • {album.artistName} • {album.songCount} songs + + + + ))} + {section === "songs" && + playableSongs.map((song) => ( + + ))} + {section === "downloads" && + downloadedItems.map((item) => + item.kind === "album" ? ( + { + client.setQueryData(["album", item.album.id], item.album); + router.push(`/album/${item.album.id}`); + }} + onLongPress={() => setSelectedDownloadedAlbum(item.album)} + > + + + + {item.album.name} + + + {item.album.artist_object?.name ?? + item.album.contributing_artists?.[0] ?? + "Unknown artist"} + + + + ) : ( + + ), + )} + {section === "downloads" && downloads.data?.length === 0 ? ( + + No downloads yet + + Downloaded songs and albums will appear here for offline + playback. + + + ) : null} + {section === "artists" && + artistItems.map((artist) => ( + { + if (!selectedArtist) router.push(`/artist/${artist.id}`); + }} + onLongPress={() => setSelectedArtist(artist)} + > + + + {artist.name} + + + Artist • {artist.albumCount} albums + + + + ))} + {section === "liked" && + likedSongs.map((song) => ( + + + + ))} + {(section === "albums" || section === "songs") && + catalog.hasNextPage ? ( + void catalog.fetchNextPage()} + /> + ) : null} + {section === "artists" && artists.hasNextPage ? ( + void artists.fetchNextPage()} + /> + ) : null} + {section === "liked" && liked.hasNextPage ? ( + void liked.fetchNextPage()} + /> + ) : null} + {section === "playlists" && + playlists.data?.map((playlist) => ( + { + if (!selectedPlaylist) + router.push(`/playlist/${playlist.id}`); + }} + onLongPress={() => setSelectedPlaylist(playlist)} + > + + + + {playlist.name} + + + Playlist • {playlist.song_count} songs + + + + ))} + + )} + {selectedAlbum ? ( + setSelectedAlbum(null)} + /> + ) : null} + {selectedDownloadedAlbum ? ( + setSelectedDownloadedAlbum(null)} + /> + ) : null} + {selectedArtist ? ( + setSelectedArtist(null)} + /> + ) : null} + {selectedPlaylist ? ( + setSelectedPlaylist(null)} + /> + ) : null} + setCreatingPlaylist(false)} + /> + + + ); +} + +function LoadMore({ + loading, + onPress, +}: { + loading: boolean; + onPress: () => void; +}) { + return ( + + {loading ? ( + + ) : ( + Load more + )} + + ); +} + +const styles = StyleSheet.create({ + pillScroller: { flexGrow: 0, flexShrink: 0 }, + pills: { + flexDirection: "row", + gap: 8, + paddingHorizontal: 20, + paddingBottom: 14, + }, + results: { flex: 1 }, + newPlaylist: { height: 36, justifyContent: "center", paddingHorizontal: 12 }, + newPlaylistText: { color: "white", fontWeight: "700" }, + pill: { + borderWidth: 1, + borderColor: palette.borderStrong, + paddingHorizontal: 15, + height: 36, + borderRadius: 18, + justifyContent: "center", + }, + activePill: { backgroundColor: "white" }, + pillText: { color: "white", fontSize: 13, fontWeight: "700" }, + activePillText: { color: "black" }, + row: { + minHeight: 72, + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 20, + gap: 13, + }, + labels: { flex: 1 }, + name: { color: "white", fontWeight: "700", fontSize: 15 }, + meta: { color: palette.secondary, fontSize: 13, marginTop: 4 }, + loadMore: { + height: 52, + margin: 20, + borderRadius: 26, + borderWidth: 1, + borderColor: palette.borderStrong, + alignItems: "center", + justifyContent: "center", + }, + emptyDownloads: { alignItems: "center", padding: 40, marginTop: 28 }, + emptyDownloadsTitle: { color: "white", fontSize: 18, fontWeight: "800" }, + emptyDownloadsText: { + color: palette.secondary, + lineHeight: 20, + marginTop: 8, + textAlign: "center", + }, + offlineBanner: { + marginHorizontal: 20, + marginBottom: 14, + padding: 14, + borderRadius: 12, + backgroundColor: "rgba(255,255,255,.08)", + }, + offlineTitle: { color: "white", fontWeight: "800", fontSize: 14 }, + offlineText: { color: palette.secondary, fontSize: 13, marginTop: 4 }, + loadMoreText: { color: "white", fontWeight: "700" }, +}); diff --git a/apps/mobile/src/app/(tabs)/search.tsx b/apps/mobile/src/app/(tabs)/search.tsx new file mode 100644 index 00000000..39b99eec --- /dev/null +++ b/apps/mobile/src/app/(tabs)/search.tsx @@ -0,0 +1,209 @@ +import { + getAlbumInfo, + searchLibrary, + type CombinedItem, + type LibraryAlbum, +} from "@parson/music-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { useRouter } from "expo-router"; +import { Play, Search as SearchIcon, X } from "lucide-react-native"; +import { useDeferredValue, useMemo, useState } from "react"; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { Artwork } from "@/components/artwork"; +import { PageTitle, Screen, SongRow } from "@/components/music-ui"; +import { AlbumActions } from "@/components/album-actions"; +import { ArtistActions } from "@/components/artist-actions"; +import { layout, palette } from "@/constants/colors"; +import { usePlayer } from "@/providers/player-provider"; +import { playableSearchSong } from "@/lib/playable-song"; + +export default function SearchScreen() { + const [query, setQuery] = useState(""); + const deferred = useDeferredValue(query.trim()); + const results = useQuery({ + queryKey: ["search", deferred], + queryFn: () => searchLibrary(deferred), + enabled: deferred.length > 0, + staleTime: 60_000, + }); + const uniqueResults = useMemo( + () => + Array.from( + new Map( + (results.data ?? []).map( + (item) => [`${item.item_type}-${item.id}`, item] as const, + ), + ).values(), + ), + [results.data], + ); + return ( + + + Search + + + + {query ? ( + setQuery("")}> + + + ) : null} + + {results.isFetching ? ( + + ) : deferred && results.data?.length === 0 ? ( + No results for “{deferred}” + ) : null} + + {uniqueResults.map((item, index) => ( + + ))} + + + + ); +} + +function SearchResult({ item }: { item: CombinedItem }) { + const router = useRouter(); + const player = usePlayer(); + const type = item.item_type.toLowerCase(); + const [menu, setMenu] = useState(false); + const [playing, setPlaying] = useState(false); + const path = item.album_object?.cover_url; + const open = () => { + if (menu) return; + if (type.includes("album")) router.push(`/album/${item.id}`); + else if (type.includes("artist")) router.push(`/artist/${item.id}`); + else if (type.includes("song")) { + player.playSong(playableSearchSong(item)); + } + }; + const playAlbum = async () => { + if (playing) return; + setPlaying(true); + try { + const album = (await getAlbumInfo(item.id, false)) as LibraryAlbum; + if (album.songs[0]) player.playSong(album.songs[0], album.songs); + } finally { + setPlaying(false); + } + }; + if (type.includes("song")) return ; + return ( + <> + [styles.result, pressed && { opacity: 0.6 }]} + onPress={open} + onLongPress={() => setMenu(true)} + > + {!type.includes("artist") ? ( + + ) : null} + + + {item.name} + + + {item.item_type} + {item.artist_object?.name ? ` • ${item.artist_object.name}` : ""} + + + {type.includes("album") ? ( + playing ? ( + + ) : ( + { + event.stopPropagation(); + void playAlbum(); + }} + > + + + ) + ) : null} + + {type.includes("album") ? ( + setMenu(false)} + albumId={item.id} + artistId={item.artist_object?.id} + name={item.name} + /> + ) : type.includes("artist") ? ( + setMenu(false)} + artistId={item.id} + name={item.name} + /> + ) : null} + + ); +} + +const styles = StyleSheet.create({ + searchBox: { + marginHorizontal: 20, + height: 50, + borderRadius: 13, + backgroundColor: palette.elevatedStrong, + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 14, + gap: 10, + marginBottom: 12, + }, + input: { flex: 1, color: "white", fontSize: 16, paddingVertical: 0 }, + empty: { color: palette.secondary, textAlign: "center", marginTop: 48 }, + result: { + minHeight: 68, + flexDirection: "row", + alignItems: "center", + gap: 13, + paddingHorizontal: 20, + }, + resultName: { color: "white", fontWeight: "700", fontSize: 15 }, + resultType: { + color: palette.secondary, + fontSize: 13, + marginTop: 4, + textTransform: "capitalize", + }, +}); diff --git a/apps/mobile/src/app/(tabs)/settings.tsx b/apps/mobile/src/app/(tabs)/settings.tsx new file mode 100644 index 00000000..d7889607 --- /dev/null +++ b/apps/mobile/src/app/(tabs)/settings.tsx @@ -0,0 +1,483 @@ +import { + changePassword, + indexLibrary, + refreshCurrentLibrary, + register, + setBitrate, +} from "@parson/music-sdk"; +import { LogOut, Radio, Server } from "lucide-react-native"; +import { useState } from "react"; +import { + Pressable, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { PageTitle, Screen } from "@/components/music-ui"; +import { layout, palette } from "@/constants/colors"; +import { useSession } from "@/providers/session-provider"; + +type Tab = "account" | "playback" | "server" | "library" | "users"; +const labels: Record = { + account: "Account", + playback: "Playback", + server: "Server", + library: "Library", + users: "Users", +}; + +export default function SettingsScreen() { + const session = useSession(); + const admin = session.claims?.role === "admin"; + const tabs: Tab[] = admin + ? ["account", "playback", "server", "library", "users"] + : ["account", "playback", "server"]; + const [tab, setTab] = useState("account"); + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [quality, setQuality] = useState(session.claims?.bitrate ?? 0); + const [message, setMessage] = useState(""); + const [libraryPath, setLibraryPath] = useState(""); + const [newUsername, setNewUsername] = useState(""); + const [newUserPassword, setNewUserPassword] = useState(""); + const [newUserAdmin, setNewUserAdmin] = useState(false); + const savePassword = async () => { + setMessage("Updating password…"); + try { + await changePassword(currentPassword, newPassword); + setCurrentPassword(""); + setNewPassword(""); + setMessage("Password updated."); + } catch { + setMessage("Could not update password."); + } + }; + const saveQuality = async (bitrate: number) => { + setQuality(bitrate); + setMessage("Updating quality…"); + try { + await setBitrate(bitrate); + session.updateBitrate(bitrate); + setMessage("Quality updated."); + } catch { + setMessage("Could not update quality."); + } + }; + const indexFolder = async () => { + if (!libraryPath.trim()) return; + setMessage("Indexing…"); + try { + const result = await indexLibrary(libraryPath.trim()); + setMessage( + `Library updated · ${result.report.indexed_files} songs indexed.`, + ); + } catch { + setMessage("Could not index this folder."); + } + }; + const refreshLibrary = async () => { + setMessage("Refreshing…"); + try { + const result = await refreshCurrentLibrary(); + setMessage( + `Refreshed ${result.refreshed.length} library folder${result.refreshed.length === 1 ? "" : "s"}.`, + ); + } catch { + setMessage("Current library could not be refreshed."); + } + }; + const createUser = async () => { + if (newUsername.trim().length < 2 || newUserPassword.length < 8) return; + setMessage("Creating…"); + try { + const result = await register({ + username: newUsername.trim(), + password: newUserPassword, + role: newUserAdmin ? "admin" : "user", + }); + if (!result.status) { + setMessage("Could not create user."); + return; + } + setNewUsername(""); + setNewUserPassword(""); + setNewUserAdmin(false); + setMessage("User created."); + } catch { + setMessage("Could not create user."); + } + }; + return ( + + + + Settings + + {tabs.map((value) => ( + { + setTab(value); + setMessage(""); + }} + > + + {labels[value]} + + + ))} + + + {tab === "account" ? ( + + ) : tab === "playback" ? ( + + ) : tab === "server" ? ( + + ) : tab === "library" ? ( + + ) : ( + + )} + {message ? {message} : null} + + + + + ); +} + +function AccountSettings({ + currentPassword, + newPassword, + onCurrentPasswordChange, + onLogout, + onNewPasswordChange, + onSave, +}: { + currentPassword: string; + newPassword: string; + onCurrentPasswordChange: (value: string) => void; + onLogout: () => Promise; + onNewPasswordChange: (value: string) => void; + onSave: () => Promise; +}) { + return ( + <> + Change password + + + void onSave()}> + Update password + + + void onLogout()}> + + Log out + + + ); +} + +const qualityOptions = [ + [96, "Low · 96 kbps"], + [128, "Normal · 128 kbps"], + [256, "High · 256 kbps"], + [0, "Original quality"], +] as const; + +function PlaybackSettings({ + onSave, + quality, +}: { + onSave: (bitrate: number) => Promise; + quality: number; +}) { + return ( + <> + Audio quality + {qualityOptions.map(([bitrate, label]) => ( + void onSave(bitrate)} + > + + {quality === bitrate ? "✓ " : ""} + {label} + + + ))} + + ); +} + +function ServerSettings({ + offline, + libraryName, + onChange, + onRetry, + origin, +}: { + offline: boolean; + libraryName: string | null; + onChange: () => Promise; + onRetry: () => Promise; + origin: string | null; +}) { + return ( + <> + + + + + + {libraryName} + + {origin} + + + + {offline ? ( + void onRetry()}> + Reconnect + + ) : null} + void onChange()}> + + Change server + + + ); +} + +function LibrarySettings({ + onIndex, + onPathChange, + onRefresh, + path, +}: { + onIndex: () => Promise; + onPathChange: (value: string) => void; + onRefresh: () => Promise; + path: string; +}) { + return ( + <> + Library + + void onIndex()}> + Index folder + + void onRefresh()}> + + Refresh current library + + + ); +} + +function UserSettings({ + admin, + onAdminChange, + onCreate, + onPasswordChange, + onUsernameChange, + password, + username, +}: { + admin: boolean; + onAdminChange: (value: boolean) => void; + onCreate: () => Promise; + onPasswordChange: (value: string) => void; + onUsernameChange: (value: string) => void; + password: string; + username: string; +}) { + return ( + <> + Create user + + + onAdminChange(!admin)} + > + + {admin ? : null} + + Administrator + + void onCreate()}> + Create user + + + ); +} + +const styles = StyleSheet.create({ + tabs: { + paddingHorizontal: 20, + borderBottomWidth: 1, + borderColor: palette.border, + }, + tab: { height: 44, paddingHorizontal: 14, justifyContent: "center" }, + activeTab: { borderBottomWidth: 2, borderColor: "white" }, + tabText: { color: palette.muted, fontSize: 14, fontWeight: "600" }, + activeTabText: { color: "white" }, + panel: { padding: 20, gap: 12 }, + heading: { color: "white", fontSize: 19, fontWeight: "800", marginBottom: 5 }, + input: { + height: 50, + borderRadius: 10, + backgroundColor: palette.elevatedStrong, + borderWidth: 1, + borderColor: palette.border, + color: "white", + paddingHorizontal: 15, + }, + primary: { + height: 48, + borderRadius: 24, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + }, + primaryText: { color: "black", fontWeight: "800" }, + divider: { height: 1, backgroundColor: palette.border, marginVertical: 12 }, + action: { + minHeight: 54, + flexDirection: "row", + alignItems: "center", + gap: 14, + }, + actionText: { color: "white", fontSize: 15, fontWeight: "600" }, + quality: { + minHeight: 52, + borderRadius: 10, + borderWidth: 1, + borderColor: palette.border, + paddingHorizontal: 15, + justifyContent: "center", + }, + selected: { borderColor: "white", backgroundColor: "#19191d" }, + card: { + padding: 16, + borderRadius: 14, + backgroundColor: palette.elevatedStrong, + flexDirection: "row", + alignItems: "center", + gap: 13, + }, + icon: { + width: 42, + height: 42, + borderRadius: 12, + backgroundColor: "#29292e", + alignItems: "center", + justifyContent: "center", + }, + label: { color: "white", fontWeight: "800" }, + value: { color: palette.secondary, fontSize: 13, marginTop: 4 }, + body: { color: palette.secondary, lineHeight: 21 }, + message: { color: palette.secondary, marginTop: 8 }, + adminToggle: { + minHeight: 48, + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + checkbox: { + width: 22, + height: 22, + borderRadius: 5, + borderWidth: 1, + borderColor: palette.borderStrong, + alignItems: "center", + justifyContent: "center", + }, + checked: { backgroundColor: "white", borderColor: "white" }, + check: { color: "black", fontWeight: "900" }, +}); diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx new file mode 100644 index 00000000..632c5803 --- /dev/null +++ b/apps/mobile/src/app/_layout.tsx @@ -0,0 +1,93 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { DarkTheme, Stack, ThemeProvider, useSegments } from "expo-router"; +import * as SplashScreen from "expo-splash-screen"; +import { StatusBar } from "expo-status-bar"; +import { useEffect, useState } from "react"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { palette } from "@/constants/colors"; +import { MiniPlayer } from "@/components/mini-player"; +import { ActionDrawerProvider } from "@/components/action-drawer"; +import { PlayerProvider } from "@/providers/player-provider"; +import { SessionProvider, useSession } from "@/providers/session-provider"; + +void SplashScreen.preventAutoHideAsync(); + +const theme = { + ...DarkTheme, + colors: { + ...DarkTheme.colors, + background: palette.background, + card: palette.background, + border: palette.border, + primary: palette.text, + }, +}; + +function PersistentMiniPlayer() { + const segments = useSegments(); + const insets = useSafeAreaInsets(); + const session = useSession(); + const route = String(segments[segments.length - 1] ?? ""); + if ( + route === "player" || + (session.phase !== "ready" && session.phase !== "offline") + ) + return null; + const inTabs = String(segments[0]) === "(tabs)"; + return ( + + ); +} + +export default function RootLayout() { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { queries: { staleTime: 30_000, retry: 1 } }, + }), + ); + useEffect(() => { + void SplashScreen.hideAsync(); + }, []); + return ( + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/mobile/src/app/album/[id].tsx b/apps/mobile/src/app/album/[id].tsx new file mode 100644 index 00000000..5bfa6aba --- /dev/null +++ b/apps/mobile/src/app/album/[id].tsx @@ -0,0 +1,188 @@ +import { getAlbumInfo, type LibraryAlbum } from "@parson/music-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { ArrowLeft, MoreHorizontal, Play } from "lucide-react-native"; +import { useState } from "react"; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { Artwork } from "@/components/artwork"; +import { AlbumActions } from "@/components/album-actions"; +import { PauseGlyph } from "@/components/pause-glyph"; +import { Screen, SongRow } from "@/components/music-ui"; +import { palette } from "@/constants/colors"; +import { usePlayer } from "@/providers/player-provider"; +import { formatCollectionDuration } from "@/lib/format"; + +export default function AlbumScreen() { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + const player = usePlayer(); + const album = useQuery({ + queryKey: ["album", id], + queryFn: () => getAlbumInfo(id, false) as Promise, + enabled: !!id, + }); + const [actionsOpen, setActionsOpen] = useState(false); + if (album.isPending) + return ( + + + + ); + if (!album.data) + return ( + + Could not load album. + + ); + const data = album.data; + const artist = + data.artist_object?.name ?? + data.contributing_artists?.[0] ?? + "Unknown artist"; + const totalDuration = formatCollectionDuration( + data.songs.reduce((sum, song) => sum + (song.duration || 0), 0), + ); + const albumPlaying = + player.isPlaying && player.current?.album_object?.id === data.id; + return ( + + + + + + + setActionsOpen(true)}> + + + + + + + {data.name} + + data.artist_object?.id && + router.push(`/artist/${data.artist_object.id}`) + } + > + {artist} + + + {data.songs.length} songs, {totalDuration} + + + + albumPlaying + ? player.toggle() + : data.songs[0] && + player.playSong(data.songs[0], data.songs) + } + > + {albumPlaying ? ( + + ) : ( + + )} + + + + {data.songs.map((song, index) => ( + + ))} + {data.description ? ( + {data.description} + ) : null} + + setActionsOpen(false)} + albumId={data.id} + artistId={data.artist_object?.id} + name={data.name} + loaded={data} + showAlbum={false} + /> + + + ); +} + +const styles = StyleSheet.create({ + nav: { + height: 50, + paddingHorizontal: 20, + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + zIndex: 2, + }, + hero: { paddingHorizontal: 20, alignItems: "flex-start" }, + cover: { + alignSelf: "center", + marginVertical: 12, + shadowColor: "#000", + shadowOpacity: 0.7, + shadowRadius: 18, + elevation: 12, + }, + title: { + color: "white", + fontSize: 30, + lineHeight: 34, + fontWeight: "900", + letterSpacing: -0.8, + marginTop: 14, + }, + artistRow: { + flexDirection: "row", + alignItems: "center", + gap: 8, + marginTop: 9, + }, + artist: { color: "white", fontWeight: "800", fontSize: 15 }, + meta: { color: palette.secondary, fontSize: 13, marginTop: 5 }, + actions: { + flexDirection: "row", + alignItems: "center", + gap: 17, + marginVertical: 22, + }, + circle: { + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + }, + description: { + color: palette.secondary, + fontSize: 14, + lineHeight: 21, + margin: 20, + }, + error: { color: palette.secondary, margin: 30 }, +}); diff --git a/apps/mobile/src/app/artist/[id].tsx b/apps/mobile/src/app/artist/[id].tsx new file mode 100644 index 00000000..deb8d265 --- /dev/null +++ b/apps/mobile/src/app/artist/[id].tsx @@ -0,0 +1,146 @@ +import { getArtistInfo } from "@parson/music-sdk"; +import { useQuery } from "@tanstack/react-query"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { ArrowLeft } from "lucide-react-native"; +import { + ActivityIndicator, + Pressable, + ScrollView, + StyleSheet, + Text, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { Artwork } from "@/components/artwork"; +import { Screen, SectionTitle } from "@/components/music-ui"; +import { AlbumActions } from "@/components/album-actions"; +import { useMemo, useState } from "react"; +import type { Album } from "@parson/music-sdk"; +import { palette } from "@/constants/colors"; + +export default function ArtistScreen() { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + const [selected, setSelected] = useState(null); + const artist = useQuery({ + queryKey: ["artist", id], + queryFn: () => getArtistInfo(id), + enabled: !!id, + }); + const sections = useMemo(() => { + const data = artist.data; + if (!data) return []; + const sort = (albums: Album[]) => + [...albums].sort((a, b) => { + const left = Date.parse(a.first_release_date || ""); + const right = Date.parse(b.first_release_date || ""); + return ( + (Number.isFinite(right) ? right : -Infinity) - + (Number.isFinite(left) ? left : -Infinity) + ); + }); + return data.discography?.length + ? data.discography.map((section) => ({ + ...section, + albums: sort(section.albums), + })) + : [{ key: "albums", title: "Albums", albums: sort(data.albums) }]; + }, [artist.data]); + if (artist.isPending) + return ( + + + + ); + if (!artist.data) return ; + const data = artist.data; + return ( + + + + + + + + + + {data.name} + {data.description ? ( + + {data.description} + + ) : null} + + {sections.map((section) => ( + + {section.title} + + {section.albums.map((album) => ( + { + if (!selected) router.push(`/album/${album.id}`); + }} + onLongPress={() => setSelected(album)} + > + + + {album.name} + + + {album.primary_type || "Album"} + + + ))} + + + ))} + + {selected ? ( + setSelected(null)} + /> + ) : null} + + + ); +} + +const styles = StyleSheet.create({ + nav: { height: 48, paddingHorizontal: 20, justifyContent: "center" }, + hero: { alignItems: "center", paddingHorizontal: 20, paddingBottom: 28 }, + title: { + color: "white", + fontSize: 34, + fontWeight: "900", + letterSpacing: -1, + textAlign: "center", + }, + description: { + color: palette.secondary, + textAlign: "center", + lineHeight: 20, + marginTop: 12, + }, + grid: { + flexDirection: "row", + flexWrap: "wrap", + paddingHorizontal: 20, + gap: 16, + }, + card: { width: 156 }, + albumName: { + color: "white", + fontWeight: "700", + marginTop: 8, + lineHeight: 18, + }, + year: { color: palette.secondary, fontSize: 12, marginTop: 3 }, +}); diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx new file mode 100644 index 00000000..043d398a --- /dev/null +++ b/apps/mobile/src/app/index.tsx @@ -0,0 +1,268 @@ +import { Redirect } from "expo-router"; +import { Image } from "expo-image"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Platform, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { useState } from "react"; + +import { palette } from "@/constants/colors"; +import { useLibraryDiscovery } from "@/hooks/use-library-discovery"; +import { useSession } from "@/providers/session-provider"; + +export default function EntryScreen() { + const session = useSession(); + useLibraryDiscovery(); + const [server, setServer] = useState(session.origin ?? ""); + const [serverEdited, setServerEdited] = useState(false); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [setupCode, setSetupCode] = useState(""); + const [libraryPath, setLibraryPath] = useState(""); + const serverValue = serverEdited ? server : (session.origin ?? server); + if (session.phase === "ready") return ; + if (session.phase === "offline") return ; + + const busy = + session.phase === "loading" || + session.phase === "connecting" || + session.phase === "indexing"; + const heading = + session.phase === "login" + ? "Welcome back" + : session.phase === "indexing" + ? "Preparing your library" + : session.phase === "setup" + ? session.setupStatus?.account_setup_required + ? "Create your account" + : "Choose your music folder" + : "Finding your library"; + const detail = + session.phase === "indexing" + ? "Parson is indexing your music. This screen will update automatically." + : session.phase === "setup" + ? (session.setupStatus?.message ?? + "Finish setting up this Parson library.") + : session.phase === "login" + ? (session.libraryName ?? "Sign in to your Parson library") + : ""; + + return ( + + + + {heading} + {detail ? {detail} : null} + {busy && ( + + )} + {session.phase === "setup" ? ( + + {session.setupStatus?.account_setup_required ? ( + <> + + + {session.setupStatus.setup_code_required ? ( + + ) : null} + + void session.setupAccount(username, password, setupCode) + } + > + Create account + + + ) : ( + <> + + + void session.setupLibrary( + libraryPath || + session.setupStatus?.suggested_library_path || + "", + ) + } + > + Index library + + + )} + + ) : session.phase === "login" ? ( + + + void session.login(username, password)} + /> + void session.login(username, password)} + > + Sign in + + void session.changeServer()}> + Choose another library + + + ) : session.phase === "discovering" || + session.phase === "connecting" || + session.phase === "loading" ? ( + + { + setServerEdited(true); + setServer(value); + }} + onSubmitEditing={() => void session.connect(serverValue)} + /> + void session.connect(serverValue)} + > + + {session.phase === "connecting" ? "Connecting…" : "Connect"} + + + + ) : null} + {session.error && + (session.phase === "login" || + session.phase === "setup" || + session.phase === "discovering") ? ( + + Something went wrong. Please try again. + + ) : null} + + + ); +} + +const styles = StyleSheet.create({ + page: { + flex: 1, + backgroundColor: "black", + justifyContent: "center", + paddingHorizontal: 28, + }, + content: { + alignItems: "center", + width: "100%", + maxWidth: 440, + alignSelf: "center", + }, + logo: { width: 72, height: 72, borderRadius: 18, marginBottom: 26 }, + heading: { + color: "white", + fontSize: 28, + fontWeight: "800", + letterSpacing: -0.6, + textAlign: "center", + }, + detail: { + color: palette.secondary, + textAlign: "center", + fontSize: 15, + lineHeight: 22, + marginTop: 10, + maxWidth: 330, + }, + spinner: { marginTop: 25 }, + form: { width: "100%", gap: 12, marginTop: 28 }, + input: { + height: 52, + borderRadius: 12, + backgroundColor: palette.elevatedStrong, + borderWidth: 1, + borderColor: palette.border, + color: "white", + paddingHorizontal: 16, + fontSize: 16, + }, + primary: { + height: 52, + backgroundColor: "white", + borderRadius: 26, + alignItems: "center", + justifyContent: "center", + }, + primaryText: { color: "black", fontSize: 16, fontWeight: "800" }, + disabledPrimary: { opacity: 0.62 }, + link: { color: "white", textAlign: "center", fontWeight: "700", padding: 12 }, + error: { color: "#fb7185", textAlign: "center", marginTop: 16 }, +}); diff --git a/apps/mobile/src/app/player.tsx b/apps/mobile/src/app/player.tsx new file mode 100644 index 00000000..f25d8cb7 --- /dev/null +++ b/apps/mobile/src/app/player.tsx @@ -0,0 +1,778 @@ +import Slider from "@react-native-community/slider"; +/* eslint-disable react-hooks/immutability -- Reanimated SharedValues are mutated only inside gesture worklets. */ +import { + addFavoriteSong, + findLyrics, + isFavoriteSong, + removeFavoriteSong, +} from "@parson/music-sdk"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { LinearGradient } from "expo-linear-gradient"; +import { useRouter } from "expo-router"; +import { + ChevronDown, + ChevronLeft, + ListMusic, + Disc3, + Download, + ListEnd, + ListPlus, + MoreHorizontal, + Play, + Repeat, + Repeat1, + SkipBack, + SkipForward, + Heart, + Gauge, + AudioLines, + TimerReset, + X, + UserRound, +} from "lucide-react-native"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + Pressable, + ScrollView, + StyleSheet, + Text, + useWindowDimensions, + View, +} from "react-native"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { Artwork } from "@/components/artwork"; +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { SongRow } from "@/components/music-ui"; +import { palette } from "@/constants/colors"; +import { + playerAudioPresets, + usePlayer, + usePlayerPosition, + type AudioPreset, +} from "@/providers/player-provider"; +import { + downloadSong, + isSongDownloaded, + removeDownload, + useDownloadsRevision, +} from "@/lib/downloads"; +import { PlaylistPicker } from "@/components/playlist-picker"; +import { PauseGlyph } from "@/components/pause-glyph"; + +type Panel = "player" | "queue" | "lyrics"; +const AnimatedLinearGradient = Animated.createAnimatedComponent(LinearGradient); +const presetIcons = { + original: AudioLines, + slow: TimerReset, + "deep-slow": TimerReset, + faster: Gauge, +} satisfies Record; + +const time = (seconds: number) => + `${Math.floor(seconds / 60)}:${Math.floor(seconds % 60) + .toString() + .padStart(2, "0")}`; + +type Player = ReturnType; +type PlayerSong = NonNullable; +type Router = ReturnType; +type SyncedLine = { at: number; text: string }; + +function PlayerHeader({ + panel, + onBack, + onActions, +}: { + panel: Panel; + onBack: () => void; + onActions: () => void; +}) { + return ( + + + {panel === "player" ? ( + + ) : ( + + )} + + + {panel === "player" ? "" : panel === "lyrics" ? "Lyrics" : "Queue"} + + {panel === "player" ? ( + + + + ) : ( + + )} + + ); +} + +function NowPlayingPanel({ + player, + song, + router, + currentTime, + duration, + favorite, + onToggleFavorite, + onQueue, + onLyrics, + onSound, +}: { + player: Player; + song: PlayerSong; + router: Router; + currentTime: number; + duration: number; + favorite: boolean; + onToggleFavorite: () => void; + onQueue: () => void; + onLyrics: () => void; + onSound: () => void; +}) { + const [scrubPosition, setScrubPosition] = useState(null); + const settlingSeek = useRef<{ target: number; expiresAt: number } | null>( + null, + ); + useEffect(() => { + const pending = settlingSeek.current; + if (!pending) return; + if ( + Math.abs(currentTime - pending.target) < 0.65 || + Date.now() >= pending.expiresAt + ) { + settlingSeek.current = null; + setScrubPosition(null); + return; + } + const timeout = setTimeout( + () => { + settlingSeek.current = null; + setScrubPosition(null); + }, + Math.max(0, pending.expiresAt - Date.now()), + ); + return () => clearTimeout(timeout); + }, [currentTime]); + const shownTime = scrubPosition ?? currentTime; + const presetLabel = playerAudioPresets.find( + (preset) => preset.id === player.audioPreset, + )?.label; + return ( + + + + + + song.album_object?.id && + router.replace(`/album/${song.album_object.id}`) + } + > + {song.name} + + + song.artist_object?.id && + router.replace(`/artist/${song.artist_object.id}`) + } + > + {song.artist} + + + + + + + + { + setScrubPosition(target); + settlingSeek.current = { + target, + expiresAt: Date.now() + 1_200, + }; + player.seek(target); + }} + minimumTrackTintColor="white" + maximumTrackTintColor="#66666d" + thumbTintColor="white" + style={styles.slider} + /> + + + {time(shownTime)} + + -{time(Math.max(0, duration - shownTime))} + + + + + {player.repeat === "one" ? ( + + ) : ( + + )} + + + + + + {player.isPlaying ? ( + + ) : ( + + )} + + + + + + + + + + + Lyrics + + {player.audioPresetsEnabled ? ( + + + {player.audioPreset === "original" ? "Sound" : presetLabel} + + + ) : null} + + + ); +} + +function QueuePanel({ player }: { player: Player }) { + return ( + + + {player.queue.map((item, index) => ( + + ))} + + + ); +} + +function LyricsPanel({ + pending, + synced, + plainLyrics, + instrumental, + currentTime, + onSeek, +}: { + pending: boolean; + synced: SyncedLine[]; + plainLyrics?: string | null; + instrumental?: boolean; + currentTime: number; + onSeek: (seconds: number) => void; +}) { + const scrollRef = useRef(null); + const lineLayouts = useRef(new Map()); + const lastCenteredLine = useRef(-1); + const [viewportHeight, setViewportHeight] = useState(0); + const [contentHeight, setContentHeight] = useState(0); + const activeLine = synced.reduce( + (found, line, index) => (line.at <= currentTime ? index : found), + -1, + ); + useEffect(() => { + lineLayouts.current.clear(); + lastCenteredLine.current = -1; + }, [synced]); + useEffect(() => { + const layout = lineLayouts.current.get(activeLine); + if (!layout || !viewportHeight) return; + const frame = requestAnimationFrame(() => { + scrollRef.current?.scrollTo({ + y: Math.max(0, layout.y - (viewportHeight - layout.height) / 2), + animated: lastCenteredLine.current >= 0, + }); + lastCenteredLine.current = activeLine; + }); + return () => cancelAnimationFrame(frame); + }, [activeLine, contentHeight, viewportHeight]); + return ( + + + setViewportHeight(nativeEvent.layout.height) + } + onContentSizeChange={(_width, height) => setContentHeight(height)} + contentContainerStyle={[ + styles.lyricsContent, + synced.length + ? { paddingBottom: Math.max(24, viewportHeight / 2) } + : null, + ]} + showsVerticalScrollIndicator={false} + > + {pending ? ( + ••• + ) : synced.length ? ( + synced.map((line, index) => { + const captureLayout = ({ + nativeEvent, + }: { + nativeEvent: { layout: { height: number; y: number } }; + }) => lineLayouts.current.set(index, nativeEvent.layout); + return line.text.trim() ? ( + onSeek(line.at)} + onLayout={captureLayout} + style={[ + styles.lyricLine, + index === activeLine + ? styles.activeLyric + : index < activeLine + ? styles.pastLyric + : styles.futureLyric, + ]} + > + {line.text} + + ) : ( + + ); + }) + ) : plainLyrics ? ( + {plainLyrics} + ) : ( + + + {instrumental + ? "Instrumental" + : "No lyrics are available for this track"} + + + )} + + + ); +} + +function PlayerDrawers({ + player, + song, + router, + actionsOpen, + onActionsClose, + soundOpen, + onSoundClose, +}: { + player: Player; + song: PlayerSong; + router: Router; + actionsOpen: boolean; + onActionsClose: () => void; + soundOpen: boolean; + onSoundClose: () => void; +}) { + const [playlistPicker, setPlaylistPicker] = useState(false); + const [downloading, setDownloading] = useState(false); + return ( + <> + + { + player.addNext(song); + onActionsClose(); + }} + /> + { + player.addToQueue([song]); + onActionsClose(); + }} + /> + {song.album_object?.id ? ( + { + onActionsClose(); + router.replace(`/album/${song.album_object.id}`); + }} + /> + ) : null} + {song.artist_object?.id ? ( + { + onActionsClose(); + router.replace(`/artist/${song.artist_object.id}`); + }} + /> + ) : null} + { + onActionsClose(); + setPlaylistPicker(true); + }} + /> + { + if (downloading) return; + if (isSongDownloaded(song.id)) { + void removeDownload(song.id).then(onActionsClose); + return; + } + setDownloading(true); + void downloadSong(song) + .then(onActionsClose) + .finally(() => setDownloading(false)); + }} + /> + + {player.audioPresetsEnabled ? ( + + {playerAudioPresets.map((preset) => ( + { + player.setAudioPreset(preset.id); + onSoundClose(); + }} + /> + ))} + + ) : null} + setPlaylistPicker(false)} + songId={song.id} + /> + + ); +} + +export default function PlayerScreen() { + const router = useRouter(); + const client = useQueryClient(); + const player = usePlayer(); + const { currentTime, duration } = usePlayerPosition(); + const [panel, setPanel] = useState("player"); + const [actions, setActions] = useState(false); + const [soundOptions, setSoundOptions] = useState(false); + const { height: screenHeight } = useWindowDimensions(); + const translateY = useSharedValue(0); + const dismissPlayer = useCallback(() => router.back(), [router]); + const playerDragStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + const dragDownGesture = useMemo( + () => + Gesture.Pan() + .enabled(panel === "player") + .activeOffsetY(14) + .failOffsetX([-28, 28]) + .onUpdate(({ translationY }) => { + translateY.value = Math.max(0, translationY); + }) + .onEnd(({ translationY, velocityY }) => { + if (translationY > 72 || velocityY > 700) { + translateY.value = withTiming( + screenHeight, + { duration: 170 }, + () => { + runOnJS(dismissPlayer)(); + }, + ); + } else { + translateY.value = withSpring(0, { damping: 22, stiffness: 260 }); + } + }), + [dismissPlayer, panel, screenHeight, translateY], + ); + useDownloadsRevision(); + const lyrics = useQuery({ + queryKey: ["lyrics", player.current?.id], + queryFn: () => findLyrics(player.current!.id), + enabled: panel === "lyrics" && !!player.current, + staleTime: 30 * 60_000, + retry: false, + }); + const favorite = useQuery({ + queryKey: ["favorite-membership", player.current?.id], + queryFn: () => isFavoriteSong(player.current!.id), + enabled: !!player.current, + staleTime: 30_000, + }); + const synced = useMemo(() => { + const source = lyrics.data?.syncedLyrics; + if (!source) return []; + return source.split("\n").flatMap((line) => { + const match = line.match(/^\[(\d+):(\d+(?:\.\d+)?)\]\s*(.*)$/); + return match + ? [ + { + at: Number(match[1]) * 60 + Number(match[2]), + text: match[3] ?? "", + }, + ] + : []; + }); + }, [lyrics.data?.syncedLyrics]); + if (!player.current) + return ( + + + + + + + + ); + const song = player.current; + const toggleFavorite = async () => { + const next = !favorite.data; + client.setQueryData(["favorite-membership", song.id], next); + try { + if (next) await addFavoriteSong(song.id); + else await removeFavoriteSong(song.id); + await client.invalidateQueries({ queryKey: ["favorite-song-details"] }); + } catch { + client.setQueryData(["favorite-membership", song.id], !next); + } + }; + return ( + + + + + panel === "player" ? router.back() : setPanel("player") + } + onActions={() => setActions(true)} + /> + {panel === "player" ? ( + void toggleFavorite()} + onQueue={() => setPanel("queue")} + onLyrics={() => setPanel("lyrics")} + onSound={() => setSoundOptions(true)} + /> + ) : panel === "queue" ? ( + + ) : ( + + )} + setActions(false)} + soundOpen={soundOptions} + onSoundClose={() => setSoundOptions(false)} + /> + + + + ); +} + +const styles = StyleSheet.create({ + page: { flex: 1, backgroundColor: "black" }, + down: { padding: 20 }, + header: { + height: 56, + paddingHorizontal: 20, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + panelHeader: { color: "white", fontSize: 15, fontWeight: "800" }, + player: { flex: 1, paddingHorizontal: 26, justifyContent: "space-evenly" }, + art: { alignSelf: "center", maxWidth: "100%", aspectRatio: 1 }, + songInfo: { + marginTop: 14, + flexDirection: "row", + alignItems: "center", + gap: 14, + }, + title: { + color: "white", + fontSize: 24, + fontWeight: "900", + letterSpacing: -0.5, + }, + artist: { color: palette.secondary, fontSize: 16, marginTop: 5 }, + sliderHitbox: { + width: "100%", + height: 52, + marginTop: 2, + justifyContent: "center", + }, + slider: { width: "100%", height: 52 }, + times: { + flexDirection: "row", + justifyContent: "space-between", + marginTop: -12, + }, + time: { + color: palette.secondary, + fontSize: 11, + fontVariant: ["tabular-nums"], + }, + controls: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + marginVertical: 10, + }, + playButton: { + width: 68, + height: 68, + borderRadius: 34, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + }, + panelButtons: { flexDirection: "row", gap: 8 }, + panelButton: { + flex: 1, + height: 46, + borderRadius: 12, + backgroundColor: "rgba(255,255,255,.1)", + alignItems: "center", + justifyContent: "center", + }, + panelText: { color: "white", fontWeight: "800" }, + panel: { flex: 1, paddingTop: 18 }, + panelTitle: { + color: "white", + fontSize: 30, + fontWeight: "900", + paddingHorizontal: 20, + }, + lyrics: { + color: "white", + fontSize: 24, + lineHeight: 35, + fontWeight: "700", + paddingHorizontal: 24, + textAlign: "center", + }, + lyricLine: { + color: "white", + fontSize: 25, + lineHeight: 34, + fontWeight: "800", + paddingHorizontal: 24, + paddingVertical: 7, + textAlign: "center", + }, + lyricsContent: { + flexGrow: 1, + paddingVertical: 24, + }, + activeLyric: { color: "white", transform: [{ scale: 1.03 }] }, + pastLyric: { color: "rgba(255,255,255,.25)" }, + futureLyric: { color: "rgba(255,255,255,.45)" }, + lyricSpacer: { height: 20 }, + loadingLyrics: { + color: palette.secondary, + fontSize: 24, + textAlign: "center", + marginTop: 40, + letterSpacing: 5, + }, + emptyLyrics: { + padding: 24, + margin: 20, + backgroundColor: "rgba(255,255,255,.08)", + borderRadius: 16, + }, + emptyTitle: { color: "white", fontWeight: "900", fontSize: 18 }, + emptyDetail: { color: palette.secondary, lineHeight: 20, marginTop: 8 }, +}); diff --git a/apps/mobile/src/app/playlist/[id].tsx b/apps/mobile/src/app/playlist/[id].tsx new file mode 100644 index 00000000..0bfa4b03 --- /dev/null +++ b/apps/mobile/src/app/playlist/[id].tsx @@ -0,0 +1,293 @@ +import { + deletePlaylist, + getPlaylist, + removeSongFromPlaylist, + updatePlaylist, +} from "@parson/music-sdk"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { + ArrowLeft, + MoreHorizontal, + Pencil, + Play, + Trash2, +} from "lucide-react-native"; +import { useState } from "react"; +import { + ActivityIndicator, + Alert, + Pressable, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { Artwork } from "@/components/artwork"; +import { Screen, SongRow } from "@/components/music-ui"; +import { palette } from "@/constants/colors"; +import { usePlayer } from "@/providers/player-provider"; +import { formatCollectionDuration } from "@/lib/format"; + +export default function PlaylistScreen() { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + const player = usePlayer(); + const client = useQueryClient(); + const [menu, setMenu] = useState(false); + const [editing, setEditing] = useState(false); + const [savingEdit, setSavingEdit] = useState(false); + const [editName, setEditName] = useState(""); + const [editDescription, setEditDescription] = useState(""); + const query = useQuery({ + queryKey: ["playlist", id], + queryFn: () => getPlaylist(Number(id)), + enabled: !!id, + }); + if (query.isPending) + return ( + + + + ); + if (!query.data) return ; + const data = query.data; + const removeSong = async (songId: string) => { + const previous = data; + const removed = data.songs.find((song) => song.id === songId); + client.setQueryData(["playlist", id], { + ...data, + songs: data.songs.filter((song) => song.id !== songId), + song_count: Math.max(0, data.song_count - 1), + total_duration: Math.max( + 0, + data.total_duration - (removed?.duration ?? 0), + ), + }); + try { + await removeSongFromPlaylist(data.id, songId); + void client.invalidateQueries({ queryKey: ["playlist", id] }); + void client.invalidateQueries({ queryKey: ["playlists"] }); + } catch { + client.setQueryData(["playlist", id], previous); + } + }; + const saveEdit = async () => { + if (!editName.trim() || savingEdit) return; + setSavingEdit(true); + try { + await updatePlaylist(data.id, { + name: editName.trim(), + description: editDescription.trim(), + }); + client.setQueryData(["playlist", id], { + ...data, + name: editName.trim(), + description: editDescription.trim(), + }); + setEditing(false); + void client.invalidateQueries({ queryKey: ["playlist", id] }); + void client.invalidateQueries({ queryKey: ["playlists"] }); + } finally { + setSavingEdit(false); + } + }; + const duration = formatCollectionDuration(data.total_duration); + return ( + + + + + + + setMenu(true)}> + + + + + + + PLAYLIST + {data.name} + {data.description ? ( + {data.description} + ) : null} + + {data.song_count} songs, {duration} + + + data.songs[0] && player.playSong(data.songs[0], data.songs) + } + > + + + + {!data.songs.length ? ( + + This playlist is empty + + Open any song’s actions and choose Add to playlist. + + + ) : null} + {data.songs.map((song, index) => ( + void removeSong(song.id)} + /> + ))} + + setMenu(false)} + title={data.name} + > + { + setMenu(false); + if (data.songs[0]) player.playSong(data.songs[0], data.songs); + }} + /> + { + setMenu(false); + setEditName(data.name); + setEditDescription(data.description ?? ""); + setEditing(true); + }} + /> + { + setMenu(false); + Alert.alert("Delete playlist?", data.name, [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => + void deletePlaylist(data.id).then(() => router.back()), + }, + ]); + }} + /> + + setEditing(false)} + title="Edit playlist" + > + + + + void saveEdit()} + > + + {savingEdit ? "Saving changes…" : "Save changes"} + + + + + + + ); +} + +const styles = StyleSheet.create({ + nav: { + height: 50, + paddingHorizontal: 20, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + hero: { padding: 20, alignItems: "flex-start" }, + type: { + color: palette.secondary, + fontSize: 11, + fontWeight: "800", + letterSpacing: 1.2, + marginTop: 22, + }, + title: { color: "white", fontSize: 32, fontWeight: "900", marginTop: 5 }, + description: { color: palette.secondary, marginTop: 8, lineHeight: 20 }, + meta: { color: palette.secondary, marginTop: 8 }, + play: { + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + marginTop: 20, + }, + editForm: { padding: 10, gap: 10 }, + input: { + minHeight: 50, + borderRadius: 10, + backgroundColor: "#29292e", + color: "white", + paddingHorizontal: 14, + paddingVertical: 12, + }, + descriptionInput: { minHeight: 90, textAlignVertical: "top" }, + save: { + height: 50, + borderRadius: 25, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + }, + saveText: { color: "black", fontWeight: "800" }, + empty: { + margin: 20, + padding: 28, + borderRadius: 14, + borderWidth: 1, + borderColor: palette.border, + alignItems: "center", + }, + emptyTitle: { color: "white", fontSize: 16, fontWeight: "800" }, + emptyText: { + color: palette.secondary, + textAlign: "center", + lineHeight: 20, + marginTop: 7, + }, +}); diff --git a/apps/mobile/src/components/action-drawer.tsx b/apps/mobile/src/components/action-drawer.tsx new file mode 100644 index 00000000..e8413813 --- /dev/null +++ b/apps/mobile/src/components/action-drawer.tsx @@ -0,0 +1,267 @@ +/* eslint-disable react-hooks/immutability, react-hooks/refs -- Reanimated SharedValues are mutated only inside UI-thread gesture worklets. */ +import type { LucideIcon } from "lucide-react-native"; +import { + createContext, + useCallback, + useContext, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, + type PropsWithChildren, +} from "react"; +import { + BackHandler, + Pressable, + StyleSheet, + Text, + useWindowDimensions, + View, +} from "react-native"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + Extrapolation, + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { palette } from "@/constants/colors"; + +type DrawerRequest = { + children: React.ReactNode; + id: symbol; + onClose: () => void; + title?: string; +}; + +type DrawerPortal = { + dismiss: (id: symbol) => void; + present: (request: DrawerRequest) => void; +}; + +const DrawerPortalContext = createContext(null); + +export function ActionDrawerProvider({ children }: PropsWithChildren) { + const [request, setRequest] = useState(null); + const present = useCallback((next: DrawerRequest) => setRequest(next), []); + const dismiss = useCallback( + (id: symbol) => + setRequest((current) => (current?.id === id ? null : current)), + [], + ); + const portal = useMemo(() => ({ dismiss, present }), [dismiss, present]); + return ( + + + {children} + + + + ); +} + +export function ActionDrawer({ + children, + onClose, + open, + title, +}: { + children: React.ReactNode; + onClose: () => void; + open: boolean; + title?: string; +}) { + const portal = useContext(DrawerPortalContext); + const id = useRef(Symbol("action-drawer")); + useLayoutEffect(() => { + if (!portal) return; + if (open) portal.present({ children, id: id.current, onClose, title }); + else portal.dismiss(id.current); + }, [children, onClose, open, portal, title]); + useEffect( + () => () => { + portal?.dismiss(id.current); + }, + [portal], + ); + return null; +} + +function ActionDrawerHost({ request }: { request: DrawerRequest | null }) { + const insets = useSafeAreaInsets(); + const { height: screenHeight } = useWindowDimensions(); + const offscreenDistance = Math.max(900, screenHeight); + const entranceDistance = 64; + const translateY = useSharedValue(offscreenDistance); + const requestRef = useRef(request); + const requestId = request?.id; + const closeDrawer = useCallback(() => { + const active = requestRef.current; + if (active && active.id === requestId) active.onClose(); + }, [requestId]); + + useEffect(() => { + requestRef.current = request; + }, [request]); + + useLayoutEffect(() => { + if (requestId) { + translateY.value = entranceDistance; + translateY.value = withTiming(0, { duration: 120 }); + } + }, [requestId, translateY]); + + const dismissDrawer = useCallback(() => { + translateY.value = withTiming(offscreenDistance, { duration: 140 }, () => { + runOnJS(closeDrawer)(); + }); + }, [closeDrawer, offscreenDistance, translateY]); + + const drawerStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + })); + const backdropStyle = useAnimatedStyle(() => ({ + opacity: interpolate( + translateY.value, + [0, Math.min(300, offscreenDistance)], + [0.54, 0], + Extrapolation.CLAMP, + ), + })); + useEffect(() => { + if (!request) return; + const subscription = BackHandler.addEventListener( + "hardwareBackPress", + () => { + dismissDrawer(); + return true; + }, + ); + return () => subscription.remove(); + }, [dismissDrawer, request]); + const dragGesture = useMemo( + () => + Gesture.Pan() + .activeOffsetY(8) + .failOffsetX([-30, 30]) + .onUpdate(({ translationY }) => { + translateY.value = Math.max(0, translationY); + }) + .onEnd(({ translationY, velocityY }) => { + if (translationY > 110 || velocityY > 850) { + translateY.value = withTiming( + offscreenDistance, + { duration: 140 }, + () => { + runOnJS(closeDrawer)(); + }, + ); + } else { + translateY.value = withSpring(0, { + damping: 22, + stiffness: 260, + }); + } + }), + [closeDrawer, offscreenDistance, translateY], + ); + if (!request) return null; + return ( + + + + + + + {request.title ? ( + + {request.title} + + ) : null} + {request.children} + + + + ); +} + +export function DrawerAction({ + icon: Icon, + label, + onPress, +}: { + icon?: LucideIcon; + label: string; + onPress: () => void; +}) { + return ( + [styles.action, pressed && { opacity: 0.55 }]} + onPress={onPress} + > + {Icon ? : null} + {label} + + ); +} + +const styles = StyleSheet.create({ + provider: { flex: 1 }, + modal: { + ...StyleSheet.absoluteFill, + justifyContent: "flex-end", + zIndex: 1000, + elevation: 1000, + }, + backdrop: { + position: "absolute", + inset: 0, + backgroundColor: "black", + }, + drawer: { + backgroundColor: "#18181b", + borderTopLeftRadius: 22, + borderTopRightRadius: 22, + paddingTop: 10, + paddingHorizontal: 10, + }, + handle: { + width: 38, + height: 4, + borderRadius: 2, + backgroundColor: "#66666d", + alignSelf: "center", + marginBottom: 10, + }, + title: { + color: palette.secondary, + fontSize: 13, + fontWeight: "700", + paddingHorizontal: 14, + paddingVertical: 9, + }, + action: { + minHeight: 53, + borderRadius: 12, + paddingHorizontal: 14, + flexDirection: "row", + alignItems: "center", + gap: 15, + }, + label: { color: "white", fontSize: 16, fontWeight: "600" }, +}); diff --git a/apps/mobile/src/components/album-actions.tsx b/apps/mobile/src/components/album-actions.tsx new file mode 100644 index 00000000..03042cfb --- /dev/null +++ b/apps/mobile/src/components/album-actions.tsx @@ -0,0 +1,322 @@ +import { + editAlbumMetadata, + getAlbumInfo, + type LibraryAlbum, +} from "@parson/music-sdk"; +import { useQueryClient } from "@tanstack/react-query"; +import { + Disc3, + Download, + ListEnd, + ListPlus, + Play, + Pencil, + UserRound, + X, +} from "lucide-react-native"; +import { useRouter } from "expo-router"; +import { useEffect, useState } from "react"; +import { + ActivityIndicator, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; + +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { + downloadAlbum, + isSongDownloaded, + removeDownloads, + useDownloadsRevision, +} from "@/lib/downloads"; +import { usePlayer } from "@/providers/player-provider"; +import { PlaylistPicker } from "@/components/playlist-picker"; +import { useSession } from "@/providers/session-provider"; +import { palette } from "@/constants/colors"; + +export function AlbumActions({ + albumId, + artistId, + name, + onClose, + open, + loaded, + showAlbum = true, + showArtist = true, +}: { + albumId: string; + artistId?: string; + name?: string; + onClose: () => void; + open: boolean; + loaded?: LibraryAlbum; + showAlbum?: boolean; + showArtist?: boolean; +}) { + const router = useRouter(); + const player = usePlayer(); + const session = useSession(); + const client = useQueryClient(); + const [pickingPlaylist, setPickingPlaylist] = useState(false); + const [editing, setEditing] = useState(false); + const [editorLoading, setEditorLoading] = useState(false); + const [editorSaving, setEditorSaving] = useState(false); + const [downloading, setDownloading] = useState(false); + const [editName, setEditName] = useState(""); + const [editArtist, setEditArtist] = useState(""); + const [editDate, setEditDate] = useState(""); + const [editType, setEditType] = useState(""); + const [editDescription, setEditDescription] = useState(""); + const [resolvedAlbum, setResolvedAlbum] = useState(null); + useDownloadsRevision(); + useEffect(() => { + if (loaded || !open) return; + void (getAlbumInfo(albumId, false) as Promise) + .then(setResolvedAlbum) + .catch(() => {}); + }, [albumId, loaded, open]); + const album = loaded ?? resolvedAlbum; + const albumDownloaded = + !!album?.songs.length && + album.songs.every((song) => isSongDownloaded(song.id)); + const load = () => + loaded + ? Promise.resolve(loaded) + : album + ? Promise.resolve(album) + : (getAlbumInfo(albumId, false) as Promise).then( + (album) => { + setResolvedAlbum(album); + return album; + }, + ); + const closeThen = (run: () => void | Promise) => { + onClose(); + void run(); + }; + const openEditor = async () => { + setEditing(true); + setEditorLoading(true); + try { + const album = await load(); + setEditName(album.name); + setEditArtist(album.artist_object.name); + setEditDate(album.first_release_date ?? ""); + setEditType(album.primary_type ?? ""); + setEditDescription(album.description ?? ""); + } finally { + setEditorLoading(false); + } + }; + const saveEditor = async () => { + if (!editName.trim() || !editArtist.trim() || editorSaving) return; + setEditorSaving(true); + try { + await editAlbumMetadata(albumId, { + album: { + name: editName.trim(), + first_release_date: editDate.trim(), + primary_type: editType.trim(), + description: editDescription.trim(), + }, + artist: { name: editArtist.trim() }, + }); + setEditing(false); + onClose(); + void Promise.all([ + client.invalidateQueries({ queryKey: ["album", albumId] }), + client.invalidateQueries({ queryKey: ["home"] }), + client.invalidateQueries({ queryKey: ["library"] }), + ]); + } finally { + setEditorSaving(false); + } + }; + return ( + <> + + + closeThen(async () => { + const album = await load(); + if (album.songs[0]) player.playSong(album.songs[0], album.songs); + }) + } + /> + + closeThen(async () => { + const album = await load(); + player.addToQueue(album.songs); + }) + } + /> + {showAlbum ? ( + closeThen(() => router.push(`/album/${albumId}`))} + /> + ) : null} + { + if (downloading) return; + if (albumDownloaded && album) { + onClose(); + void removeDownloads(album.songs.map((song) => song.id)); + return; + } + setDownloading(true); + void (async () => { + const album = await load(); + await downloadAlbum(album.name, album.songs); + setDownloading(false); + onClose(); + })().catch(() => setDownloading(false)); + }} + /> + setPickingPlaylist(true)} + /> + {artistId && showArtist ? ( + closeThen(() => router.push(`/artist/${artistId}`))} + /> + ) : null} + {session.claims?.role === "admin" ? ( + void openEditor()} + /> + ) : null} + + { + setPickingPlaylist(false); + onClose(); + }} + /> + { + setEditing(false); + onClose(); + }} + title="Edit album" + > + {editorLoading ? ( + + + Loading album… + + ) : ( + + + + + + + + + void saveEditor()} + > + + {editorSaving ? "Saving changes…" : "Save changes"} + + + + )} + + + ); +} + +const styles = StyleSheet.create({ + editorLoading: { + minHeight: 120, + alignItems: "center", + justifyContent: "center", + gap: 12, + }, + editorLoadingText: { color: palette.secondary }, + form: { padding: 10, gap: 9 }, + row: { flexDirection: "row", gap: 9 }, + flex: { flex: 1 }, + input: { + minHeight: 48, + borderRadius: 10, + backgroundColor: "#29292e", + color: "white", + paddingHorizontal: 13, + paddingVertical: 11, + }, + description: { minHeight: 88, textAlignVertical: "top" }, + save: { + height: 50, + borderRadius: 25, + backgroundColor: "white", + alignItems: "center", + justifyContent: "center", + }, + saveText: { color: "black", fontWeight: "800" }, +}); diff --git a/apps/mobile/src/components/artist-actions.tsx b/apps/mobile/src/components/artist-actions.tsx new file mode 100644 index 00000000..da119267 --- /dev/null +++ b/apps/mobile/src/components/artist-actions.tsx @@ -0,0 +1,44 @@ +import { getArtistInfo } from "@parson/music-sdk"; +import { Play, UserRound } from "lucide-react-native"; +import { useRouter } from "expo-router"; + +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { usePlayer } from "@/providers/player-provider"; + +export function ArtistActions({ + artistId, + name, + onClose, + open, +}: { + artistId: string; + name?: string; + onClose: () => void; + open: boolean; +}) { + const router = useRouter(); + const player = usePlayer(); + return ( + + { + onClose(); + void getArtistInfo(artistId).then((artist) => { + const songs = artist.albums.flatMap((album) => album.songs); + if (songs[0]) player.playSong(songs[0], songs); + }); + }} + /> + { + onClose(); + router.push(`/artist/${artistId}`); + }} + /> + + ); +} diff --git a/apps/mobile/src/components/artwork.tsx b/apps/mobile/src/components/artwork.tsx new file mode 100644 index 00000000..474ea6b6 --- /dev/null +++ b/apps/mobile/src/components/artwork.tsx @@ -0,0 +1,54 @@ +import { Image } from "expo-image"; +import { Music2 } from "lucide-react-native"; +import { useState } from "react"; +import { StyleSheet, View, type ViewStyle } from "react-native"; + +import { palette } from "@/constants/colors"; +import { imageUrl } from "@/lib/runtime"; + +export function Artwork({ + path, + size, + rounded = 8, + style, +}: { + path?: string | null; + size: number; + rounded?: number; + style?: ViewStyle; +}) { + const uri = imageUrl(path); + const [failedUri, setFailedUri] = useState(null); + return ( + + {uri && failedUri !== uri ? ( + setFailedUri(uri)} + transition={160} + /> + ) : ( + + )} + + ); +} + +const styles = StyleSheet.create({ + frame: { + backgroundColor: palette.elevatedStrong, + alignItems: "center", + justifyContent: "center", + overflow: "hidden", + }, +}); diff --git a/apps/mobile/src/components/mini-player.tsx b/apps/mobile/src/components/mini-player.tsx new file mode 100644 index 00000000..4ac220cb --- /dev/null +++ b/apps/mobile/src/components/mini-player.tsx @@ -0,0 +1,132 @@ +/* eslint-disable react-hooks/immutability -- Reanimated SharedValues are mutated only inside gesture worklets. */ +import { useRouter } from "expo-router"; +import { Play, SkipForward } from "lucide-react-native"; +import { useCallback, useMemo, useState } from "react"; +import { Pressable, StyleSheet, Text, View } from "react-native"; +import { Gesture, GestureDetector } from "react-native-gesture-handler"; +import Animated, { + Extrapolation, + interpolate, + runOnJS, + useAnimatedStyle, + useSharedValue, + withSpring, +} from "react-native-reanimated"; + +import { Artwork } from "@/components/artwork"; +import { PauseGlyph } from "@/components/pause-glyph"; +import { layout, palette } from "@/constants/colors"; +import { usePlayer, usePlayerPosition } from "@/providers/player-provider"; + +export function MiniPlayer({ bottom }: { bottom: number }) { + const router = useRouter(); + const { current, isPlaying, toggle, next } = usePlayer(); + const { currentTime, duration } = usePlayerPosition(); + const [opening, setOpening] = useState(false); + const expansion = useSharedValue(0); + const openPlayerFromDrag = useCallback(() => { + setOpening(true); + router.push("/player"); + }, [router]); + const openPlayerFromTap = useCallback(() => { + router.push("/player"); + }, [router]); + const dragStyle = useAnimatedStyle(() => ({ + borderRadius: interpolate( + expansion.value, + [0, 180], + [12, 0], + Extrapolation.CLAMP, + ), + height: layout.miniPlayer + expansion.value, + left: interpolate(expansion.value, [0, 180], [8, 0], Extrapolation.CLAMP), + right: interpolate(expansion.value, [0, 180], [8, 0], Extrapolation.CLAMP), + })); + const dragUpGesture = useMemo( + () => + Gesture.Pan() + .activeOffsetY(-12) + .failOffsetX([-28, 28]) + .onUpdate(({ translationY }) => { + expansion.value = Math.max(0, -translationY); + }) + .onEnd(({ translationY, velocityY }) => { + if (translationY < -36 || velocityY < -600) { + runOnJS(openPlayerFromDrag)(); + } else { + expansion.value = withSpring(0, { + damping: 22, + stiffness: 260, + }); + } + }), + [expansion, openPlayerFromDrag], + ); + if (!current || opening) return null; + const progress = duration > 0 ? Math.min(1, currentTime / duration) : 0; + return ( + + + + + + + {current.name} + + + {current.artist} + + + + + {isPlaying ? ( + + ) : ( + + )} + + + + + + + + ); +} + +const styles = StyleSheet.create({ + shell: { + position: "absolute", + left: 8, + right: 8, + height: layout.miniPlayer, + borderRadius: 12, + backgroundColor: "#202024", + flexDirection: "row", + alignItems: "center", + padding: 8, + zIndex: 20, + overflow: "hidden", + }, + info: { flex: 1, flexDirection: "row", alignItems: "center", gap: 11 }, + labels: { flex: 1 }, + title: { color: palette.text, fontWeight: "700", fontSize: 14 }, + artist: { color: palette.secondary, fontSize: 12, marginTop: 2 }, + control: { + width: 44, + height: 44, + alignItems: "center", + justifyContent: "center", + }, + progress: { + position: "absolute", + left: 0, + bottom: 0, + height: 2, + backgroundColor: "white", + }, +}); diff --git a/apps/mobile/src/components/music-ui.tsx b/apps/mobile/src/components/music-ui.tsx new file mode 100644 index 00000000..f55fae0e --- /dev/null +++ b/apps/mobile/src/components/music-ui.tsx @@ -0,0 +1,299 @@ +import type { LibrarySong, ResponseAlbum } from "@parson/music-sdk"; +import { useRouter } from "expo-router"; +import { + Disc3, + Download, + ListEnd, + ListPlus, + MoreHorizontal, + Play, + X, + UserRound, +} from "lucide-react-native"; +import { useState } from "react"; +import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native"; + +import { Artwork } from "@/components/artwork"; +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { palette } from "@/constants/colors"; +import { usePlayer } from "@/providers/player-provider"; +import { + downloadSong, + isSongDownloaded, + removeDownload, + useDownloadsRevision, +} from "@/lib/downloads"; +import { AlbumActions } from "@/components/album-actions"; +import { PlaylistPicker } from "@/components/playlist-picker"; + +export function Screen({ children }: { children?: React.ReactNode }) { + return {children}; +} + +export function PageTitle({ + children, + subtitle, +}: { + children: React.ReactNode; + subtitle?: string; +}) { + return ( + + {children} + {subtitle ? {subtitle} : null} + + ); +} + +export function SectionTitle({ children }: { children: React.ReactNode }) { + return {children}; +} + +export function SongRow({ + song, + queue, + index, + onRemove, + showAlbum = true, +}: { + song: LibrarySong; + queue?: LibrarySong[]; + index?: number; + onRemove?: () => void; + showAlbum?: boolean; +}) { + const player = usePlayer(); + const router = useRouter(); + const [menu, setMenu] = useState(false); + const [playlistPicker, setPlaylistPicker] = useState(false); + const [downloading, setDownloading] = useState(false); + useDownloadsRevision(); + const active = player.current?.id === song.id; + return ( + [styles.songRow, pressed && styles.pressed]} + onPress={() => { + if (menu) return; + if (active) player.toggle(); + else player.playSong(song, queue); + }} + onLongPress={() => setMenu(true)} + delayLongPress={220} + > + {index !== undefined ? ( + {index + 1} + ) : ( + + )} + + + {song.name} + + + {song.artist} + {showAlbum && song.album_object?.name + ? ` • ${song.album_object.name}` + : ""} + + + { + event.stopPropagation(); + setMenu(true); + }} + style={styles.rowMenuButton} + > + + + setMenu(false)} + title={`${song.name} • ${song.artist}`} + > + { + setMenu(false); + player.playSong(song, queue); + }} + /> + { + setMenu(false); + player.addNext(song); + }} + /> + { + setMenu(false); + player.addToQueue([song]); + }} + /> + {song.album_object?.id ? ( + { + setMenu(false); + router.push(`/album/${song.album_object.id}`); + }} + /> + ) : null} + {song.artist_object?.id ? ( + { + setMenu(false); + router.push(`/artist/${song.artist_object.id}`); + }} + /> + ) : null} + { + setMenu(false); + setPlaylistPicker(true); + }} + /> + { + if (downloading) return; + if (isSongDownloaded(song.id)) { + void removeDownload(song.id).then(() => setMenu(false)); + return; + } + setDownloading(true); + void downloadSong(song) + .then(() => setMenu(false)) + .finally(() => setDownloading(false)); + }} + /> + {onRemove ? ( + { + setMenu(false); + onRemove(); + }} + /> + ) : null} + + setPlaylistPicker(false)} + songId={song.id} + /> + + ); +} + +export function AlbumRail({ albums }: { albums: ResponseAlbum[] }) { + const router = useRouter(); + const [selected, setSelected] = useState(null); + return ( + <> + + {albums.map((album) => ( + { + if (!selected) router.push(`/album/${album.id}`); + }} + onLongPress={() => setSelected(album)} + > + + + {album.name} + + + {album.artist_object?.name ?? + album.contributing_artists?.[0] ?? + "Album"} + + + ))} + + {selected ? ( + setSelected(null)} + /> + ) : null} + + ); +} + +const styles = StyleSheet.create({ + screen: { flex: 1, backgroundColor: palette.background }, + titleBlock: { paddingHorizontal: 20, paddingTop: 12, paddingBottom: 14 }, + pageTitle: { + color: "white", + fontSize: 30, + lineHeight: 36, + fontWeight: "900", + letterSpacing: -1, + }, + subtitle: { color: palette.secondary, marginTop: 4, fontSize: 14 }, + sectionTitle: { + color: "white", + fontSize: 21, + fontWeight: "800", + letterSpacing: -0.4, + marginHorizontal: 20, + marginTop: 18, + marginBottom: 13, + }, + songRow: { + minHeight: 64, + paddingHorizontal: 20, + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + pressed: { opacity: 0.62 }, + trackNumber: { + color: palette.secondary, + width: 24, + textAlign: "center", + fontVariant: ["tabular-nums"], + }, + songLabels: { flex: 1, justifyContent: "center" }, + songTitle: { color: "white", fontSize: 15, fontWeight: "600" }, + songArtist: { color: palette.secondary, fontSize: 13, marginTop: 3 }, + rowMenuButton: { + width: 36, + height: 44, + alignItems: "center", + justifyContent: "center", + }, + rail: { paddingHorizontal: 20, gap: 15 }, + card: { width: 154 }, + cardTitle: { color: "white", fontWeight: "700", fontSize: 14, marginTop: 9 }, + cardSubtitle: { color: palette.secondary, fontSize: 13, marginTop: 3 }, +}); diff --git a/apps/mobile/src/components/pause-glyph.tsx b/apps/mobile/src/components/pause-glyph.tsx new file mode 100644 index 00000000..eb012f7f --- /dev/null +++ b/apps/mobile/src/components/pause-glyph.tsx @@ -0,0 +1,35 @@ +import { StyleSheet, View } from "react-native"; + +export function PauseGlyph({ + color = "white", + size = 24, +}: { + color?: string; + size?: number; +}) { + const barWidth = Math.max(4, Math.round(size * 0.28)); + return ( + + + + + ); +} + +const styles = StyleSheet.create({ + row: { + flexDirection: "row", + alignItems: "stretch", + justifyContent: "center", + }, + bar: { height: "100%", borderRadius: 1 }, +}); diff --git a/apps/mobile/src/components/playlist-actions.tsx b/apps/mobile/src/components/playlist-actions.tsx new file mode 100644 index 00000000..b9f5cfdf --- /dev/null +++ b/apps/mobile/src/components/playlist-actions.tsx @@ -0,0 +1,44 @@ +import { getPlaylist } from "@parson/music-sdk"; +import { ListMusic, Play } from "lucide-react-native"; +import { useRouter } from "expo-router"; + +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { usePlayer } from "@/providers/player-provider"; + +export function PlaylistActions({ + playlistId, + name, + onClose, + open, +}: { + playlistId: number; + name?: string; + onClose: () => void; + open: boolean; +}) { + const router = useRouter(); + const player = usePlayer(); + return ( + + { + onClose(); + void getPlaylist(playlistId).then((playlist) => { + if (playlist.songs[0]) + player.playSong(playlist.songs[0], playlist.songs); + }); + }} + /> + { + onClose(); + router.push(`/playlist/${playlistId}`); + }} + /> + + ); +} diff --git a/apps/mobile/src/components/playlist-picker.tsx b/apps/mobile/src/components/playlist-picker.tsx new file mode 100644 index 00000000..9fbe300d --- /dev/null +++ b/apps/mobile/src/components/playlist-picker.tsx @@ -0,0 +1,153 @@ +import { + addAlbumToPlaylist, + addSongToPlaylist, + createPlaylist, + getPlaylists, +} from "@parson/music-sdk"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { ListPlus, Plus, RefreshCw } from "lucide-react-native"; +import { useState } from "react"; +import { + ActivityIndicator, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; + +import { ActionDrawer, DrawerAction } from "@/components/action-drawer"; +import { palette } from "@/constants/colors"; + +export function PlaylistPicker({ + albumId, + onClose, + open, + songId, +}: { + albumId?: string; + onClose: () => void; + open: boolean; + songId?: string; +}) { + const client = useQueryClient(); + const [creating, setCreating] = useState(false); + const [addingTo, setAddingTo] = useState(null); + const [creatingPlaylist, setCreatingPlaylist] = useState(false); + const [name, setName] = useState(""); + const playlists = useQuery({ + queryKey: ["playlists"], + queryFn: getPlaylists, + enabled: open, + }); + const add = async (id: number) => { + if (addingTo !== null) return; + setAddingTo(id); + try { + if (songId) await addSongToPlaylist(id, songId); + else if (albumId) await addAlbumToPlaylist(id, albumId); + onClose(); + void client.invalidateQueries({ queryKey: ["playlists"] }); + } finally { + setAddingTo(null); + } + }; + const create = async () => { + if (!name.trim() || creatingPlaylist) return; + setCreatingPlaylist(true); + try { + await createPlaylist( + name.trim(), + songId ? [songId] : [], + songId ? undefined : albumId, + ); + setName(""); + setCreating(false); + onClose(); + void client.invalidateQueries({ queryKey: ["playlists"] }); + } finally { + setCreatingPlaylist(false); + } + }; + return ( + + {playlists.isPending ? ( + + + Loading playlists… + + ) : null} + {playlists.isError ? ( + void playlists.refetch()} + /> + ) : null} + {playlists.data?.map((playlist) => ( + void add(playlist.id)} + /> + ))} + {creating ? ( + + void create()} + /> + void create()} + > + + {creatingPlaylist ? "Creating…" : "Create"} + + + + ) : ( + setCreating(true)} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + loading: { + minHeight: 54, + flexDirection: "row", + alignItems: "center", + gap: 12, + paddingHorizontal: 16, + }, + loadingText: { color: palette.secondary }, + create: { flexDirection: "row", gap: 8, padding: 10 }, + input: { + flex: 1, + height: 48, + borderRadius: 10, + backgroundColor: "#29292e", + color: "white", + paddingHorizontal: 13, + }, + button: { + height: 48, + borderRadius: 24, + backgroundColor: "white", + paddingHorizontal: 18, + justifyContent: "center", + }, + buttonText: { color: "black", fontWeight: "800" }, +}); diff --git a/apps/mobile/src/constants/colors.ts b/apps/mobile/src/constants/colors.ts new file mode 100644 index 00000000..49e08c59 --- /dev/null +++ b/apps/mobile/src/constants/colors.ts @@ -0,0 +1,21 @@ +export const palette = { + background: "#000000", + elevated: "#0c0c0e", + elevatedStrong: "#151518", + border: "rgba(255,255,255,0.10)", + borderStrong: "rgba(255,255,255,0.18)", + text: "#ffffff", + secondary: "#a1a1aa", + muted: "#5f5f69", + accent: "#ffffff", + accentText: "#000000", + danger: "#ef4444", + success: "#22c55e", +} as const; + +export const layout = { + horizontal: 20, + tabBar: 76, + miniPlayer: 68, + radius: 16, +} as const; diff --git a/apps/mobile/src/hooks/use-library-discovery.ts b/apps/mobile/src/hooks/use-library-discovery.ts new file mode 100644 index 00000000..1ba68b71 --- /dev/null +++ b/apps/mobile/src/hooks/use-library-discovery.ts @@ -0,0 +1,29 @@ +import { useEffect, useRef } from "react"; + +import ParsonDiscovery from "../../modules/parson-discovery"; +import { useSession } from "@/providers/session-provider"; + +export function useLibraryDiscovery() { + const { connect, phase } = useSession(); + const attempted = useRef(new Set()); + useEffect(() => { + if (phase !== "discovering") return; + const subscription = ParsonDiscovery.addListener( + "onService", + ({ host, port }) => { + const origin = `http://${host.includes(":") ? `[${host}]` : host}:${port}`; + if (attempted.current.has(origin)) return; + attempted.current.add(origin); + void connect(origin).then((connected) => { + if (!connected) + setTimeout(() => attempted.current.delete(origin), 5000); + }); + }, + ); + ParsonDiscovery.start(); + return () => { + subscription.remove(); + ParsonDiscovery.stop(); + }; + }, [connect, phase]); +} diff --git a/apps/mobile/src/lib/downloads.ts b/apps/mobile/src/lib/downloads.ts new file mode 100644 index 00000000..58761a8b --- /dev/null +++ b/apps/mobile/src/lib/downloads.ts @@ -0,0 +1,309 @@ +import type { LibraryAlbum, LibrarySong } from "@parson/music-sdk"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Directory, File, Paths } from "expo-file-system"; +import { useSyncExternalStore } from "react"; + +import { authorizationHeaders, streamUrl } from "@/lib/runtime"; + +const safe = (value: string) => + value + .replace(/[\\/:*?"<>|]/g, "_") + .trim() + .slice(0, 100) || "Music"; + +type DownloadRecord = { + albumDownload?: { albumId: string; songIds: string[] }; + id: string; + uri: string; + song?: LibrarySong; +}; + +const DOWNLOADS_KEY = "parson.downloaded-songs"; +const downloaded = new Map(); +const listeners = new Set<() => void>(); +let hydrated = false; +let hydrationPromise: Promise | null = null; +let revision = 0; + +const snapshot = () => revision; +const subscribe = (listener: () => void) => { + listeners.add(listener); + return () => listeners.delete(listener); +}; +const changed = () => { + revision += 1; + listeners.forEach((listener) => listener()); +}; +const persist = () => + AsyncStorage.setItem(DOWNLOADS_KEY, JSON.stringify([...downloaded.values()])); + +export function useDownloadsRevision() { + return useSyncExternalStore(subscribe, snapshot, snapshot); +} + +export async function hydrateDownloads() { + if (hydrated) return; + if (!hydrationPromise) { + hydrationPromise = (async () => { + try { + const stored = await AsyncStorage.getItem(DOWNLOADS_KEY); + const raw = stored ? (JSON.parse(stored) as unknown[]) : []; + let needsMigration = false; + for (const item of raw) { + const legacy = Array.isArray(item) ? item : null; + const value = legacy + ? { + albumDownload: undefined, + id: legacy[0], + song: undefined, + uri: legacy[1], + } + : (item as Partial); + if ( + typeof value.id === "string" && + typeof value.uri === "string" && + new File(value.uri).exists + ) { + downloaded.set(value.id, { + albumDownload: value.albumDownload, + id: value.id, + uri: value.uri, + song: "song" in value ? value.song : undefined, + }); + needsMigration ||= !!legacy; + } else { + needsMigration = true; + } + } + if (needsMigration) await persist(); + } catch { + } finally { + hydrated = true; + changed(); + } + })(); + } + await hydrationPromise; +} + +export function downloadedSongUri(songId: string) { + return downloaded.get(songId)?.uri ?? null; +} + +export function isSongDownloaded(songId: string) { + return downloaded.has(songId); +} + +export function downloadedRecords() { + return [...downloaded.values()]; +} + +export type DownloadedLibraryItem = + { kind: "album"; album: LibraryAlbum } | { kind: "song"; song: LibrarySong }; + +const downloadParent = (uri: string) => { + const path = uri.split("?", 1)[0]?.replace(/\/+$/, "") ?? ""; + return path.slice(0, Math.max(0, path.lastIndexOf("/"))); +}; + +const isIndividualSongsFolder = (parent: string) => + /\/Parson\/Songs$/i.test(decodeURI(parent)); + +export function groupDownloadedLibrary( + songs: LibrarySong[], +): DownloadedLibraryItem[] { + const downloadedById = new Map(songs.map((song) => [song.id, song])); + const declaredAlbums = new Map(); + downloaded.forEach((record) => { + const batch = record.albumDownload; + if ( + batch?.albumId && + batch.songIds.length && + batch.songIds.every((id) => downloadedById.has(id)) + ) { + declaredAlbums.set(batch.albumId, batch.songIds); + } + }); + const legacyAlbumFolders = new Map>(); + downloaded.forEach((record) => { + const song = record.song; + const albumId = song?.album_object?.id; + const parent = downloadParent(record.uri); + if ( + !song || + !albumId || + !downloadedById.has(song.id) || + !parent || + isIndividualSongsFolder(parent) + ) + return; + const folders = + legacyAlbumFolders.get(albumId) ?? new Map(); + const songIds = folders.get(parent) ?? []; + songIds.push(song.id); + folders.set(parent, songIds); + legacyAlbumFolders.set(albumId, folders); + }); + legacyAlbumFolders.forEach((folders, albumId) => { + if (declaredAlbums.has(albumId)) return; + const largestFolder = [...folders.values()].sort( + (left, right) => right.length - left.length, + )[0]; + // Legacy album downloads share a folder but have no batch marker. + if (largestFolder && largestFolder.length > 1) { + declaredAlbums.set( + albumId, + largestFolder.sort( + (left, right) => + (downloadedById.get(left)?.track_number ?? 0) - + (downloadedById.get(right)?.track_number ?? 0), + ), + ); + } + }); + const completeAlbums = new Map(); + + for (const song of songs) { + const album = song.album_object; + if (!album?.id || completeAlbums.has(album.id)) continue; + const declaredSongIds = declaredAlbums.get(album.id); + const albumSongIds = album.songs?.map((track) => track.id) ?? []; + const completeSongIds = declaredSongIds ?? albumSongIds; + if ( + !completeSongIds.length || + !completeSongIds.every((id) => downloadedById.has(id)) + ) + continue; + completeAlbums.set(album.id, { + ...album, + artist_object: song.artist_object, + songs: completeSongIds.flatMap((id) => { + const track = downloadedById.get(id); + return track ? [track] : []; + }), + }); + } + + const emittedAlbums = new Set(); + return songs.flatMap((song): DownloadedLibraryItem[] => { + const albumId = song.album_object?.id; + const album = albumId ? completeAlbums.get(albumId) : undefined; + if (!album) return [{ kind: "song", song }]; + if (emittedAlbums.has(album.id)) return []; + emittedAlbums.add(album.id); + return [{ kind: "album", album }]; + }); +} + +export async function enrichDownloadedSongs(songs: LibrarySong[]) { + let didChange = false; + for (const song of songs) { + const record = downloaded.get(song.id); + if (record && !record.song) { + downloaded.set(song.id, { ...record, song }); + didChange = true; + } + } + if (didChange) { + await persist(); + changed(); + } +} + +async function rememberDownload( + song: LibrarySong, + uri: string, + notify = true, + albumDownload?: DownloadRecord["albumDownload"], +) { + downloaded.set(song.id, { albumDownload, id: song.id, uri, song }); + if (notify) { + await persist(); + changed(); + } +} + +export async function removeDownload(songId: string) { + await removeDownloads([songId]); +} + +export async function removeDownloads(songIds: string[]) { + const records = songIds.flatMap((id) => { + const record = downloaded.get(id); + if (!record) return []; + downloaded.delete(id); + return [record]; + }); + if (!records.length) return; + changed(); + for (const record of records) { + try { + const file = new File(record.uri); + if (file.exists) file.delete(); + } catch {} + } + await persist(); +} + +export async function downloadAlbum( + name: string, + songs: LibrarySong[], + onProgress?: (done: number) => void, +) { + const directory = new Directory(Paths.document, "Parson", safe(name)); + const albumDownload = songs[0]?.album_object?.id + ? { + albumId: songs[0].album_object.id, + songIds: songs.map((song) => song.id), + } + : undefined; + directory.create({ intermediates: true, idempotent: true }); + try { + for (let index = 0; index < songs.length; index += 1) { + const song = songs[index]; + if (!song) continue; + const extension = + song.path + .split(".") + .pop() + ?.toLowerCase() + .match(/^[a-z0-9]{2,5}$/)?.[0] ?? "mp3"; + const destination = new File( + directory, + `${String(index + 1).padStart(2, "0")} ${safe(song.name)}.${extension}`, + ); + const saved = await File.downloadFileAsync( + streamUrl(song.id), + destination, + { headers: authorizationHeaders(), idempotent: true }, + ); + await rememberDownload(song, saved.uri, false, albumDownload); + onProgress?.(index + 1); + } + } finally { + await persist(); + changed(); + } + return directory.uri; +} + +export async function downloadSong(song: LibrarySong) { + const directory = new Directory(Paths.document, "Parson", "Songs"); + directory.create({ intermediates: true, idempotent: true }); + const extension = + song.path + .split(".") + .pop() + ?.toLowerCase() + .match(/^[a-z0-9]{2,5}$/)?.[0] ?? "mp3"; + const destination = new File( + directory, + `${safe(song.artist)} - ${safe(song.name)}.${extension}`, + ); + const saved = await File.downloadFileAsync(streamUrl(song.id), destination, { + headers: authorizationHeaders(), + idempotent: true, + }); + await rememberDownload(song, saved.uri); + return saved; +} diff --git a/apps/mobile/src/lib/format.ts b/apps/mobile/src/lib/format.ts new file mode 100644 index 00000000..8ff3bc8b --- /dev/null +++ b/apps/mobile/src/lib/format.ts @@ -0,0 +1,7 @@ +export function formatCollectionDuration(seconds: number) { + const minutes = Math.max(1, Math.round(seconds / 60)); + if (minutes < 60) return `${minutes} min`; + const hours = Math.floor(minutes / 60); + const remainder = minutes % 60; + return remainder ? `${hours} hr ${remainder} min` : `${hours} hr`; +} diff --git a/apps/mobile/src/lib/playable-song.ts b/apps/mobile/src/lib/playable-song.ts new file mode 100644 index 00000000..d7f3f905 --- /dev/null +++ b/apps/mobile/src/lib/playable-song.ts @@ -0,0 +1,66 @@ +import type { CombinedItem, LibrarySong } from "@parson/music-sdk"; + +type CatalogSong = { + id: string; + name: string; + artistId?: string; + artistName: string; + albumId?: string; + albumName: string; + coverPath?: string; + duration?: number; + path?: string; +}; + +export function playableCatalogSong(song: CatalogSong): LibrarySong { + const artistId = song.artistId ?? ""; + const albumId = song.albumId ?? ""; + return { + id: song.id, + album_id: albumId, + artist_id: artistId, + name: song.name, + artist: song.artistName, + contributing_artists: [], + contributing_artist_ids: [], + track_number: 0, + path: song.path ?? "", + duration: song.duration ?? 0, + artist_object: { + id: artistId, + name: song.artistName, + icon_url: "", + followers: 0, + albums: [], + featured_on_album_ids: [], + description: "", + }, + album_object: { + id: albumId, + name: song.albumName, + cover_url: song.coverPath ?? "", + songs: [], + first_release_date: "", + musicbrainz_id: "", + wikidata_id: null, + primary_type: "Album", + description: "", + contributing_artists: [], + contributing_artists_ids: [], + }, + }; +} + +export function playableSearchSong(item: CombinedItem): LibrarySong { + return playableCatalogSong({ + id: item.id, + name: item.song_object?.name ?? item.name, + duration: item.song_object?.duration, + path: item.song_object?.path, + artistId: item.artist_object?.id, + artistName: item.artist_object?.name ?? "Unknown artist", + albumId: item.album_object?.id, + albumName: item.album_object?.name ?? "Unknown album", + coverPath: item.album_object?.cover_url, + }); +} diff --git a/apps/mobile/src/lib/runtime.ts b/apps/mobile/src/lib/runtime.ts new file mode 100644 index 00000000..30e03d99 --- /dev/null +++ b/apps/mobile/src/lib/runtime.ts @@ -0,0 +1,74 @@ +import { configureApiRuntime } from "@parson/music-sdk"; + +type RuntimeSnapshot = { + origin: string | null; + token: string | null; + unauthorized: (() => void) | null; + tokenChanged: ((token: string) => void) | null; +}; + +const runtime: RuntimeSnapshot = { + origin: null, + token: null, + unauthorized: null, + tokenChanged: null, +}; + +configureApiRuntime({ + getAccessToken: () => runtime.token, + getServerUrl: () => runtime.origin, + onAccessToken: (token) => { + runtime.token = token; + runtime.tokenChanged?.(token); + }, + onUnauthorized: () => runtime.unauthorized?.(), +}); + +export function configureNativeRuntime(next: Partial) { + Object.assign(runtime, next); +} + +export function normalizeOrigin(value: string) { + const candidate = value.trim().replace(/\/+$/, ""); + const withScheme = /^https?:\/\//i.test(candidate) + ? candidate + : `http://${candidate}`; + const parsed = new URL(withScheme); + return parsed.origin; +} + +export function apiUrl(path: string) { + if (!runtime.origin) throw new Error("No Parson library is connected."); + return `${runtime.origin}/api/v1/${path.replace(/^\/+/, "")}`; +} + +export function authenticatedUrl(path: string) { + return apiUrl(path); +} + +export function imageUrl(path?: string | null) { + const image = path?.trim(); + const normalized = image?.toLowerCase() ?? ""; + if ( + !runtime.origin || + !image || + normalized === "snf.png" || + normalized.endsWith("/snf.png") || + normalized.includes("snf.png?") + ) + return null; + if (/^https?:\/\//i.test(image)) return image; + return `${runtime.origin}/media/images/${encodeURIComponent(image)}`; +} + +export function streamUrl(songId: string, bitrate = 0) { + const url = new URL( + apiUrl(`media/songs/${encodeURIComponent(songId)}/stream`), + ); + url.searchParams.set("bitrate", String(bitrate)); + return url.toString(); +} + +export function authorizationHeaders(): Record { + return runtime.token ? { Authorization: `Bearer ${runtime.token}` } : {}; +} diff --git a/apps/mobile/src/providers/player-provider.tsx b/apps/mobile/src/providers/player-provider.tsx new file mode 100644 index 00000000..cebbc715 --- /dev/null +++ b/apps/mobile/src/providers/player-provider.tsx @@ -0,0 +1,861 @@ +import { + fillReverbImpulseChannel, + getPlayerAudioPreset, + playerAudioPresets, + reverbImpulseLength, + type LibrarySong, + type PlayerAudioPresetId, +} from "@parson/music-sdk"; +import * as Haptics from "expo-haptics"; +import { + createAudioPlayer, + setAudioModeAsync, + type AudioPlayer as ExpoAudioPlayer, +} from "expo-audio"; +import { + Audio, + AudioContext, + AudioManager, + PlaybackNotificationManager, + type AudioTagHandle, + type ConvolverNode, + type GainNode, + type MediaElementAudioSourceNode, +} from "react-native-audio-api"; +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type Dispatch, + type PropsWithChildren, + type RefObject, + type SetStateAction, +} from "react"; +import { Platform } from "react-native"; + +import { authorizationHeaders, imageUrl, streamUrl } from "@/lib/runtime"; +import { downloadedSongUri, hydrateDownloads } from "@/lib/downloads"; +import { useSession } from "@/providers/session-provider"; + +type RepeatMode = "none" | "one" | "all"; +export type AudioPreset = PlayerAudioPresetId; + +type PlaybackSource = { + autoplay: boolean; + key: number; + retryCount: number; + value: { uri: string; headers?: Record }; +}; + +type NativeAudioGraph = { + convolver: ConvolverNode; + dryGain: GainNode; + source: MediaElementAudioSourceNode | null; + wetConnected: boolean; + wetGain: GainNode; +}; + +type PlaybackControls = { + cleanup: () => void; + interrupt: () => boolean; + next: () => void; + pause: () => void; + play: () => void; + previous: () => void; + seek: (seconds: number) => void; +}; + +type PlayerContextValue = { + current: LibrarySong | null; + currentIndex: number; + isBuffering: boolean; + isPlaying: boolean; + queue: LibrarySong[]; + repeat: RepeatMode; + audioPreset: AudioPreset; + audioPresetsEnabled: boolean; + playSong: (song: LibrarySong, queue?: LibrarySong[]) => void; + playAt: (index: number) => void; + addNext: (song: LibrarySong) => void; + addToQueue: (songs: LibrarySong[]) => void; + toggle: () => void; + next: () => void; + previous: () => void; + seek: (seconds: number) => void; + cycleRepeat: () => void; + setAudioPreset: (preset: AudioPreset) => void; +}; + +const PlayerContext = createContext(null); +const PlayerPositionContext = createContext({ currentTime: 0, duration: 0 }); + +function buildReverbImpulse(context: AudioContext) { + const length = reverbImpulseLength(context.sampleRate); + const impulse = context.createBuffer(2, length, context.sampleRate); + for (let channel = 0; channel < impulse.numberOfChannels; channel += 1) { + fillReverbImpulseChannel(impulse.getChannelData(channel), channel); + } + return impulse; +} + +function disconnectSource(graph: NativeAudioGraph) { + if (!graph.source) return; + try { + graph.source.disconnect(); + } catch {} + graph.source = null; + graph.wetConnected = false; +} + +function useNativePlaybackControls(controls: PlaybackControls) { + const controlsRef = useRef(controls); + const interruptedWhilePlaying = useRef(false); + + useEffect(() => { + controlsRef.current = controls; + }, [controls]); + + useEffect(() => { + void hydrateDownloads(); + if (Platform.OS === "android") return; + AudioManager.setAudioSessionOptions({ + iosCategory: "playback", + iosMode: "default", + iosNotifyOthersOnDeactivation: true, + }); + AudioManager.observeAudioInterruptions("gain"); + + const interruption = AudioManager.addSystemEventListener( + "interruption", + ({ type, shouldResume }) => { + if (type === "began") { + interruptedWhilePlaying.current = controlsRef.current.interrupt(); + return; + } + if (shouldResume && interruptedWhilePlaying.current) { + interruptedWhilePlaying.current = false; + controlsRef.current.play(); + } + }, + ); + const subscriptions = [ + interruption, + PlaybackNotificationManager.addEventListener( + "playbackNotificationPlay", + () => controlsRef.current.play(), + ), + PlaybackNotificationManager.addEventListener( + "playbackNotificationPause", + () => controlsRef.current.pause(), + ), + PlaybackNotificationManager.addEventListener( + "playbackNotificationNextTrack", + () => controlsRef.current.next(), + ), + PlaybackNotificationManager.addEventListener( + "playbackNotificationPreviousTrack", + () => controlsRef.current.previous(), + ), + PlaybackNotificationManager.addEventListener( + "playbackNotificationSeekTo", + ({ value }) => controlsRef.current.seek(value), + ), + ]; + + return () => { + subscriptions.forEach((subscription) => subscription?.remove()); + controlsRef.current.cleanup(); + void PlaybackNotificationManager.hide().catch(() => {}); + void AudioManager.setAudioSessionActivity(false).catch(() => {}); + }; + }, []); +} + +function usePlaybackNotification({ + current, + currentTime, + duration, + isPlaying, + rate, +}: { + current: LibrarySong | null; + currentTime: number; + duration: number; + isPlaying: boolean; + rate: number; +}) { + const controlsEnabled = useRef(false); + const permissionRequested = useRef(false); + const notificationUpdates = useRef>(Promise.resolve()); + const elapsedTime = Math.floor(currentTime / 30) * 30; + + useEffect(() => { + if (Platform.OS === "android") return; + const artwork = current + ? imageUrl(current.album_object?.cover_url) + : undefined; + + // Older Android versions require serialized notification updates. + notificationUpdates.current = notificationUpdates.current + .catch(() => {}) + .then(async () => { + if (!current) { + controlsEnabled.current = false; + await PlaybackNotificationManager.hide(); + return; + } + if (Platform.OS === "android" && !permissionRequested.current) { + permissionRequested.current = true; + await AudioManager.requestNotificationPermissions(); + } + if (!controlsEnabled.current) { + await PlaybackNotificationManager.enableControl("play", true); + await PlaybackNotificationManager.enableControl("pause", true); + await PlaybackNotificationManager.enableControl( + "previousTrack", + true, + ); + await PlaybackNotificationManager.enableControl("nextTrack", true); + await PlaybackNotificationManager.enableControl("seekTo", true); + controlsEnabled.current = true; + } + await PlaybackNotificationManager.show({ + title: current.name, + artist: current.artist, + album: current.album_object?.name, + artwork: artwork ? { uri: artwork } : undefined, + duration, + elapsedTime, + speed: rate, + state: isPlaying ? "playing" : "paused", + }); + }) + .catch(() => { + controlsEnabled.current = false; + }); + }, [current, duration, elapsedTime, isPlaying, rate]); +} + +function schedulePlaybackRetry( + source: PlaybackSource, + desiredPlayingRef: RefObject, + sourceSequenceRef: RefObject, + setPlaybackSource: Dispatch>, +) { + if ( + !desiredPlayingRef.current || + source.retryCount >= 2 || + !/^https?:\/\//i.test(source.value.uri) + ) { + return false; + } + const failedKey = source.key; + setTimeout( + () => { + setPlaybackSource((activeSource) => + activeSource?.key === failedKey && desiredPlayingRef.current + ? { + ...activeSource, + key: ++sourceSequenceRef.current, + retryCount: activeSource.retryCount + 1, + } + : activeSource, + ); + }, + 600 * (source.retryCount + 1), + ); + return true; +} + +function useAndroidAudioOutput({ + current, + playbackSource, + androidPlayerRef, + playbackSourceRef, + desiredPlayingRef, + endedHandlerRef, + sourceSequenceRef, + setPlaybackSource, + setCurrentTime, + setDuration, + setIsBuffering, + setIsPlaying, +}: { + current: LibrarySong | null; + playbackSource: PlaybackSource | null; + androidPlayerRef: RefObject; + playbackSourceRef: RefObject; + desiredPlayingRef: RefObject; + endedHandlerRef: RefObject<() => void>; + sourceSequenceRef: RefObject; + setPlaybackSource: Dispatch>; + setCurrentTime: Dispatch>; + setDuration: Dispatch>; + setIsBuffering: Dispatch>; + setIsPlaying: Dispatch>; +}) { + useEffect(() => { + if (Platform.OS !== "android") return; + void setAudioModeAsync({ + interruptionMode: "doNotMix", + playsInSilentMode: true, + shouldPlayInBackground: true, + }).catch((cause) => + console.error("Could not configure Android audio", cause), + ); + + const player = createAudioPlayer(null, { + keepAudioSessionActive: true, + preferredForwardBufferDuration: 15, + updateInterval: 250, + }); + androidPlayerRef.current = player; + let finishedSourceKey = -1; + const subscription = player.addListener( + "playbackStatusUpdate", + (status) => { + if (Number.isFinite(status.currentTime)) + setCurrentTime(status.currentTime); + if (Number.isFinite(status.duration) && status.duration > 0) { + setDuration(status.duration); + } + setIsBuffering( + status.isBuffering || (desiredPlayingRef.current && !status.isLoaded), + ); + // Ignore stale native state while replacing the source. + if (status.playing) setIsPlaying(true); + else if (!desiredPlayingRef.current) setIsPlaying(false); + + const source = playbackSourceRef.current; + if (status.error && source) { + console.error("Could not load Android audio source", status.error); + if ( + !schedulePlaybackRetry( + source, + desiredPlayingRef, + sourceSequenceRef, + setPlaybackSource, + ) + ) { + desiredPlayingRef.current = false; + setIsBuffering(false); + setIsPlaying(false); + } + } + if ( + status.didJustFinish && + source && + finishedSourceKey !== source.key + ) { + finishedSourceKey = source.key; + endedHandlerRef.current(); + } + }, + ); + return () => { + subscription.remove(); + player.clearLockScreenControls(); + player.remove(); + androidPlayerRef.current = null; + }; + }, [ + androidPlayerRef, + desiredPlayingRef, + endedHandlerRef, + playbackSourceRef, + setCurrentTime, + setDuration, + setIsBuffering, + setIsPlaying, + setPlaybackSource, + sourceSequenceRef, + ]); + + useEffect(() => { + if (Platform.OS !== "android" || !playbackSource) return; + const player = androidPlayerRef.current; + if (!player) return; + setIsBuffering(true); + player.replace(playbackSource.value); + player.loop = false; + if (current) { + player.setActiveForLockScreen(true, { + albumTitle: current.album_object?.name, + artist: current.artist, + artworkUrl: imageUrl(current.album_object?.cover_url) ?? undefined, + title: current.name, + }); + } + if (playbackSource.autoplay && desiredPlayingRef.current) player.play(); + }, [ + androidPlayerRef, + current, + desiredPlayingRef, + playbackSource, + setIsBuffering, + ]); +} + +function BrowserAudioOutput({ + playbackSource, + currentRate, + preservePitch, + audioRef, + audioContext, + desiredPlayingRef, + sourceSequenceRef, + setPlaybackSource, + setCurrentTime, + setIsBuffering, + setIsPlaying, + routeLoadedSource, + handleEnded, +}: { + playbackSource: PlaybackSource; + currentRate: number; + preservePitch: boolean; + audioRef: RefObject; + audioContext: AudioContext; + desiredPlayingRef: RefObject; + sourceSequenceRef: RefObject; + setPlaybackSource: Dispatch>; + setCurrentTime: Dispatch>; + setIsBuffering: Dispatch>; + setIsPlaying: Dispatch>; + routeLoadedSource: () => void; + handleEnded: () => void; +}) { + return ( +