zwischenstand

This commit is contained in:
2025-07-10 10:07:29 +02:00
parent 63f6e2d32f
commit 41472e02ad
28 changed files with 818 additions and 209 deletions

View File

@ -56,6 +56,7 @@
"@vue/compiler-sfc": "^3.5.17", "@vue/compiler-sfc": "^3.5.17",
"drizzle-kit": "^0.31.2", "drizzle-kit": "^0.31.2",
"globals": "^16.2.0", "globals": "^16.2.0",
"prettier": "3.6.2",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"packageManager": "pnpm@10.12.2", "packageManager": "pnpm@10.12.2",

10
pnpm-lock.yaml generated
View File

@ -123,6 +123,9 @@ importers:
globals: globals:
specifier: ^16.2.0 specifier: ^16.2.0
version: 16.2.0 version: 16.2.0
prettier:
specifier: 3.6.2
version: 3.6.2
typescript: typescript:
specifier: ^5.8.3 specifier: ^5.8.3
version: 5.8.3 version: 5.8.3
@ -4374,6 +4377,11 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
prettier@3.6.2:
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
engines: {node: '>=14'}
hasBin: true
pretty-bytes@6.1.1: pretty-bytes@6.1.1:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0} engines: {node: ^14.13.1 || >=16.0.0}
@ -10003,6 +10011,8 @@ snapshots:
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
prettier@3.6.2: {}
pretty-bytes@6.1.1: {} pretty-bytes@6.1.1: {}
process-nextick-args@2.0.1: {} process-nextick-args@2.0.1: {}

262
src-tauri/Cargo.lock generated
View File

@ -285,6 +285,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -342,9 +351,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "7.0.0" version = "8.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@ -353,9 +362,9 @@ dependencies = [
[[package]] [[package]]
name = "brotli-decompressor" name = "brotli-decompressor"
version = "4.0.2" version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
@ -665,15 +674,15 @@ dependencies = [
[[package]] [[package]]
name = "cssparser" name = "cssparser"
version = "0.27.2" version = "0.29.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa"
dependencies = [ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa 0.4.8", "itoa",
"matches", "matches",
"phf 0.8.0", "phf 0.10.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"smallvec", "smallvec",
@ -1545,12 +1554,14 @@ dependencies = [
"sqlparser", "sqlparser",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-android-fs",
"tauri-plugin-dialog", "tauri-plugin-dialog",
"tauri-plugin-fs", "tauri-plugin-fs",
"tauri-plugin-http", "tauri-plugin-http",
"tauri-plugin-notification", "tauri-plugin-notification",
"tauri-plugin-opener", "tauri-plugin-opener",
"tauri-plugin-os", "tauri-plugin-os",
"tauri-plugin-persisted-scope",
"tauri-plugin-store", "tauri-plugin-store",
"tokio", "tokio",
"uhlc", "uhlc",
@ -1606,16 +1617,14 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "html5ever" name = "html5ever"
version = "0.26.0" version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
dependencies = [ dependencies = [
"log", "log",
"mac", "mac",
"markup5ever", "markup5ever",
"proc-macro2", "match_token",
"quote",
"syn 1.0.109",
] ]
[[package]] [[package]]
@ -1626,7 +1635,7 @@ checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
"itoa 1.0.15", "itoa",
] ]
[[package]] [[package]]
@ -1683,7 +1692,7 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"itoa 1.0.15", "itoa",
"pin-project-lite", "pin-project-lite",
"smallvec", "smallvec",
"tokio", "tokio",
@ -1936,6 +1945,17 @@ dependencies = [
"cfb", "cfb",
] ]
[[package]]
name = "io-uring"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.11.0" version = "2.11.0"
@ -1961,12 +1981,6 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.15" version = "1.0.15"
@ -2063,14 +2077,13 @@ dependencies = [
[[package]] [[package]]
name = "kuchikiki" name = "kuchikiki"
version = "0.8.2" version = "0.8.8-speedreader"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2"
dependencies = [ dependencies = [
"cssparser", "cssparser",
"html5ever", "html5ever",
"indexmap 1.9.3", "indexmap 2.8.0",
"matches",
"selectors", "selectors",
] ]
@ -2132,9 +2145,9 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.34.0" version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15" checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [ dependencies = [
"cc", "cc",
"openssl-sys", "openssl-sys",
@ -2202,18 +2215,29 @@ dependencies = [
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.11.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
dependencies = [ dependencies = [
"log", "log",
"phf 0.10.1", "phf 0.11.3",
"phf_codegen 0.10.0", "phf_codegen 0.11.3",
"string_cache", "string_cache",
"string_cache_codegen", "string_cache_codegen",
"tendril", "tendril",
] ]
[[package]]
name = "match_token"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]] [[package]]
name = "matches" name = "matches"
version = "0.1.10" version = "0.1.10"
@ -2274,9 +2298,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.16.1" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@ -2758,9 +2782,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros 0.8.0",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack",
] ]
[[package]] [[package]]
@ -2769,7 +2791,9 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [ dependencies = [
"phf_macros 0.10.0",
"phf_shared 0.10.0", "phf_shared 0.10.0",
"proc-macro-hack",
] ]
[[package]] [[package]]
@ -2794,12 +2818,12 @@ dependencies = [
[[package]] [[package]]
name = "phf_codegen" name = "phf_codegen"
version = "0.10.0" version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [ dependencies = [
"phf_generator 0.10.0", "phf_generator 0.11.3",
"phf_shared 0.10.0", "phf_shared 0.11.3",
] ]
[[package]] [[package]]
@ -2834,12 +2858,12 @@ dependencies = [
[[package]] [[package]]
name = "phf_macros" name = "phf_macros"
version = "0.8.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
dependencies = [ dependencies = [
"phf_generator 0.8.0", "phf_generator 0.10.0",
"phf_shared 0.8.0", "phf_shared 0.10.0",
"proc-macro-hack", "proc-macro-hack",
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3433,9 +3457,9 @@ dependencies = [
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.36.0" version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"fallible-iterator", "fallible-iterator",
@ -3591,22 +3615,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "selectors" name = "selectors"
version = "0.22.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cssparser", "cssparser",
"derive_more", "derive_more",
"fxhash", "fxhash",
"log", "log",
"matches",
"phf 0.8.0", "phf 0.8.0",
"phf_codegen 0.8.0", "phf_codegen 0.8.0",
"precomputed-hash", "precomputed-hash",
"servo_arc", "servo_arc",
"smallvec", "smallvec",
"thin-slice",
] ]
[[package]] [[package]]
@ -3666,7 +3688,7 @@ version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa 1.0.15", "itoa",
"memchr", "memchr",
"ryu", "ryu",
"serde", "serde",
@ -3699,7 +3721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"itoa 1.0.15", "itoa",
"ryu", "ryu",
"serde", "serde",
] ]
@ -3758,9 +3780,9 @@ dependencies = [
[[package]] [[package]]
name = "servo_arc" name = "servo_arc"
version = "0.1.1" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741"
dependencies = [ dependencies = [
"nodrop", "nodrop",
"stable_deref_trait", "stable_deref_trait",
@ -4071,9 +4093,9 @@ dependencies = [
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.33.0" version = "0.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e59c1f38e657351a2e822eadf40d6a2ad4627b9c25557bc1180ec1b3295ef82" checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"core-foundation 0.10.0", "core-foundation 0.10.0",
@ -4127,17 +4149,16 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.5.1" version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7b0bc1aec81bda6bc455ea98fcaed26b3c98c1648c627ad6ff1c704e8bf8cbc" checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"dirs", "dirs",
"dunce", "dunce",
"embed_plist", "embed_plist",
"futures-util", "getrandom 0.3.2",
"getrandom 0.2.15",
"glob", "glob",
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
@ -4179,9 +4200,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a0350f0df1db385ca5c02888a83e0e66655c245b7443db8b78a70da7d7f8fc" checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@ -4201,9 +4222,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93f035551bf7b11b3f51ad9bc231ebbe5e085565527991c16cf326aa38cdf47" checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"brotli", "brotli",
@ -4228,9 +4249,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.2.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8db4df25e2d9d45de0c4c910da61cd5500190da14ae4830749fee3466dddd112" checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -4242,9 +4263,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.1.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9972871fcbddf16618f70412d965d4d845cd4b76d03fff168709961ef71e5cdf" checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@ -4258,10 +4279,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tauri-plugin-dialog" name = "tauri-plugin-android-fs"
version = "2.2.0" version = "9.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b59fd750551b1066744ab956a1cd6b1ea3e1b3763b0b9153ac27a044d596426" checksum = "70913be3272e29ada966e8158ffb4f1b3985041635bf4563baade6178309231a"
dependencies = [
"percent-encoding",
"serde",
"serde_json",
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror 2.0.12",
]
[[package]]
name = "tauri-plugin-dialog"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28"
dependencies = [ dependencies = [
"log", "log",
"raw-window-handle", "raw-window-handle",
@ -4277,9 +4313,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da" checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dunce", "dunce",
@ -4299,10 +4335,12 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.4.2" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696ef548befeee6c6c17b80ef73e7c41205b6c2204e87ef78ccc231212389a5c" checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7"
dependencies = [ dependencies = [
"bytes",
"cookie_store",
"data-url", "data-url",
"http", "http",
"regex", "regex",
@ -4321,9 +4359,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-notification" name = "tauri-plugin-notification"
version = "2.2.2" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c474c7cc524385e682ccc1e149e13913a66fd8586ac4c2319cf01b78f070d309" checksum = "cfe06ed89cff6d0ec06ff4f544fb961e4718348a33309f56ccb2086e77bc9116"
dependencies = [ dependencies = [
"log", "log",
"notify-rust", "notify-rust",
@ -4340,9 +4378,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8983f50326d34437142a6d560b5c3426e91324297519b6eeb32ed0a1d1e0f2" checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321"
dependencies = [ dependencies = [
"dunce", "dunce",
"glob", "glob",
@ -4362,9 +4400,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-os" name = "tauri-plugin-os"
version = "2.2.1" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "424f19432397850c2ddd42aa58078630c15287bbce3866eb1d90e7dbee680637" checksum = "05bccb4c6de4299beec5a9b070878a01bce9e2c945aa7a75bcea38bcba4c675d"
dependencies = [ dependencies = [
"gethostname", "gethostname",
"log", "log",
@ -4379,10 +4417,26 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tauri-plugin-store" name = "tauri-plugin-persisted-scope"
version = "2.2.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0c08fae6995909f5e9a0da6038273b750221319f2c0f3b526d6de1cde21505" checksum = "7380eff2525adcf7f6b1cf3de191ccd3fdbe2a42281e4659604a26749c77bfbd"
dependencies = [
"aho-corasick",
"bincode",
"log",
"serde",
"serde_json",
"tauri",
"tauri-plugin-fs",
"thiserror 2.0.12",
]
[[package]]
name = "tauri-plugin-store"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916c609664a56c82aeaefffca9851fd072d4d41f73d63f22ee3ee451508194f"
dependencies = [ dependencies = [
"dunce", "dunce",
"serde", "serde",
@ -4396,9 +4450,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.6.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00f004905d549854069e6774533d742b03cacfd6f03deb08940a8677586cbe39" checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@ -4418,9 +4472,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.6.0" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f85d056f4d4b014fe874814034f3416d57114b617a493a4fe552580851a3f3a2" checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@ -4445,9 +4499,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.4.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2900399c239a471bcff7f15c4399eb1a8c4fe511ba2853e07c996d771a5e0a4" checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brotli", "brotli",
@ -4527,12 +4581,6 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "thin-slice"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@ -4580,7 +4628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa 1.0.15", "itoa",
"num-conv", "num-conv",
"powerfmt", "powerfmt",
"serde", "serde",
@ -4631,16 +4679,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.45.0" version = "1.46.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
"io-uring",
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
@ -4797,9 +4847,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
@ -5214,9 +5264,9 @@ dependencies = [
[[package]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.37.0" version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b542b5cfbd9618c46c2784e4d41ba218c336ac70d44c55e47b251033e7d85601" checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4"
dependencies = [ dependencies = [
"webview2-com-macros", "webview2-com-macros",
"webview2-com-sys", "webview2-com-sys",
@ -5239,9 +5289,9 @@ dependencies = [
[[package]] [[package]]
name = "webview2-com-sys" name = "webview2-com-sys"
version = "0.37.0" version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae2d11c4a686e4409659d7891791254cf9286d3cfe0eef54df1523533d22295" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
dependencies = [ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
"windows", "windows",
@ -5762,9 +5812,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.51.2" version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886a0a9d2a94fd90cfa1d929629b79cfefb1546e2c7430c63a47f0664c0e4e2" checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"block2 0.6.0", "block2 0.6.0",

View File

@ -18,14 +18,14 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.2", features = [] } tauri-build = { version = "2.2", features = [] }
[dependencies] [dependencies]
rusqlite = { version = "0.36.0", features = [ rusqlite = { version = "0.37.0", features = [
"load_extension", "load_extension",
"bundled-sqlcipher-vendored-openssl", "bundled-sqlcipher-vendored-openssl",
"functions", "functions",
] } ] }
#libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] } #libsqlite3-sys = { version = "0.31", features = ["bundled-sqlcipher"] }
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] } #sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
tokio = { version = "1.45", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.46", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
hex = "0.4" hex = "0.4"
serde_json = "1" serde_json = "1"
@ -35,12 +35,16 @@ mime = "0.3"
fs_extra = "1.3.0" fs_extra = "1.3.0"
sqlparser = { version = "0.57.0", features = ["visitor"] } sqlparser = { version = "0.57.0", features = ["visitor"] }
uhlc = "0.8" uhlc = "0.8"
tauri = { version = "2.5", features = ["protocol-asset", "devtools"] } tauri = { version = "2.6.2", features = ["protocol-asset", "devtools"] }
tauri-plugin-dialog = "2.2" tauri-plugin-dialog = "2.3"
tauri-plugin-fs = "2.3.0" tauri-plugin-fs = "2.4.0"
tauri-plugin-opener = "2.3.0" tauri-plugin-opener = "2.4.0"
tauri-plugin-os = "2" tauri-plugin-os = "2.3"
tauri-plugin-store = "2" tauri-plugin-store = "2.3"
tauri-plugin-http = "2.4" tauri-plugin-http = "2.5"
tauri-plugin-notification = "2" tauri-plugin-notification = "2.3"
tauri-plugin-persisted-scope = "2.0.0"
tauri-plugin-android-fs = "9.5.0"
#tauri-plugin-sql = { version = "2", features = ["sqlite"] } #tauri-plugin-sql = { version = "2", features = ["sqlite"] }

View File

@ -19,8 +19,17 @@
"fs:allow-appdata-read-recursive", "fs:allow-appdata-read-recursive",
"fs:allow-appdata-write-recursive", "fs:allow-appdata-write-recursive",
"fs:allow-read-file", "fs:allow-read-file",
"fs:allow-read-dir",
"fs:allow-resource-read-recursive", "fs:allow-resource-read-recursive",
"fs:allow-resource-write-recursive",
"fs:allow-download-read-recursive",
"fs:allow-download-write-recursive",
"fs:default", "fs:default",
"android-fs:default",
{
"identifier": "fs:scope",
"allow": [{ "path": "**" }]
},
"http:allow-fetch-send", "http:allow-fetch-send",
"http:allow-fetch", "http:allow-fetch",
"http:default", "http:default",

View File

@ -19,7 +19,7 @@ android {
defaultConfig { defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false" manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "space.haex.hub" applicationId = "space.haex.hub"
minSdk = 24 minSdk = 30
targetSdk = 34 targetSdk = 34
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- AndroidTV support --> <!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />
@ -9,7 +11,8 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.haex_hub" android:theme="@style/Theme.haex_hub"
android:usesCleartextTraffic="${usesCleartextTraffic}"> android:usesCleartextTraffic="${usesCleartextTraffic}"
android:requestLegacyExternalStorage="true">
<activity <activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:launchMode="singleTask" android:launchMode="singleTask"
@ -25,13 +28,13 @@
</activity> </activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data <meta-data
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>

View File

@ -1,3 +1,20 @@
package space.haex.hub package space.haex.hub
import android.os.Build
import android.os.Bundle
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
class MainActivity : TauriActivity() class MainActivity : TauriActivity(){
/* override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivity(
Intent(
Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
Uri.parse("package:${BuildConfig.APPLICATION_ID}")
)
)
} */
}

View File

@ -0,0 +1,17 @@
package space.haex.hub
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.Settings
class SettingsOpener {
companion object {
@JvmStatic
fun openManageAllFilesAccessSettings(context: Context) {
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:" + context.packageName)
context.startActivity(intent)
}
}
}

Binary file not shown.

View File

@ -0,0 +1,94 @@
#[cfg(target_os = "android")]
#[tauri::command]
pub async fn request_storage_permission(app_handle: tauri::AppHandle) -> Result<String, String> {
Ok("Settings opened - Enable 'Allow management of all files'".to_string())
/* use tauri_plugin_opener::OpenerExt;
// Korrekte Android Settings Intent
let intent_uri = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
match app.opener().open_url(intent_uri, None::<&str>) {
Ok(_) => Ok("Settings opened - Enable 'Allow management of all files'".to_string()),
Err(_) => {
// Fallback: App-spezifische Settings
let app_settings = format!(
"android.settings.APPLICATION_DETAILS_SETTINGS?package={}",
app.config().identifier
);
match app.opener().open_url(&app_settings, None::<&str>) {
Ok(_) => Ok("App settings opened - Go to Permissions > Files and media".to_string()),
Err(_) => Ok("Manually go to: Settings > Apps > Special app access > All files access > HaexHub > Allow".to_string())
}
}
}*/
}
#[cfg(target_os = "android")]
#[tauri::command]
pub async fn has_storage_permission() -> Result<bool, String> {
use std::path::Path;
// Teste Schreibzugriff auf externen Speicher
let test_paths = [
"/storage/emulated/0/Android",
"/sdcard/Android",
"/storage/emulated/0",
];
for path in &test_paths {
if Path::new(path).exists() {
// Versuche Testdatei zu erstellen
let test_file = format!("{}/haex_test.tmp", path);
match std::fs::write(&test_file, "test") {
Ok(_) => {
let _ = std::fs::remove_file(&test_file);
return Ok(true);
}
Err(_) => continue,
}
}
}
Ok(false)
}
#[cfg(target_os = "android")]
#[tauri::command]
pub async fn get_external_storage_paths() -> Result<Vec<String>, String> {
let mut paths = Vec::new();
let common_paths = [
"/storage/emulated/0",
"/sdcard",
"/storage/emulated/0/Download",
"/storage/emulated/0/Documents",
"/storage/emulated/0/Pictures",
"/storage/emulated/0/DCIM",
];
for path in &common_paths {
if std::path::Path::new(path).exists() {
paths.push(path.to_string());
}
}
Ok(paths)
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
pub async fn request_storage_permission(_app: tauri::AppHandle) -> Result<String, String> {
Ok("aaaaaaaa".to_string())
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
pub async fn has_storage_permission() -> Result<bool, String> {
Ok(true)
}
#[cfg(not(target_os = "android"))]
#[tauri::command]
pub async fn get_external_storage_paths() -> Result<Vec<String>, String> {
Ok(vec![])
}

22
src-tauri/src/crdt/log.rs Normal file
View File

@ -0,0 +1,22 @@
// src/entities/crdt_log.rs
use sea_orm::entity::prelude::*;
#[sea_orm(table_name = "crdt_log")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = true)]
pub id: i64,
pub hlc_timestamp: String,
pub op_type: String,
pub table_name: String,
pub row_pk: String, // Wird als JSON-String gespeichert
#[sea_orm(nullable)]
pub column_name: Option<String>,
#[sea_orm(nullable)]
pub value: Option<String>,
#[sea_orm(nullable)]
pub old_value: Option<String>,
}
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

165
src-tauri/src/crdt/proxy.rs Normal file
View File

@ -0,0 +1,165 @@
// In src-tauri/src/sql_proxy.rs
use rusqlite::Connection;
use sqlparser::ast::Statement;
use sqlparser::ast::{ColumnDef, DataType, Expr, Ident, Query, Statement, TableWithJoins, Value};
use sqlparser::dialect::SQLiteDialect;
use sqlparser::parser::Parser;
use sqlparser::visit_mut::{self, VisitorMut};
use std::ops::ControlFlow;
// Der Name der Tombstone-Spalte als Konstante, um "Magic Strings" zu vermeiden.
pub const TOMBSTONE_COLUMN_NAME: &str = "tombstone";
const EXCLUDED_TABLES: &[&str] = &["crdt_log"];
// Die Hauptstruktur unseres Proxys.
// Sie ist zustandslos, da wir uns gegen einen Schema-Cache entschieden haben.
pub struct SqlProxy;
impl SqlProxy {
pub fn new() -> Self {
Self {}
}
// Die zentrale Ausführungsfunktion
pub fn execute(&self, sql: &str, conn: &Connection) -> Result<(), String> {
// 1. Parsen des SQL-Strings in einen oder mehrere ASTs.
// Ein String kann mehrere, durch Semikolon getrennte Anweisungen enthalten.
let dialect = SQLiteDialect {};
let mut ast_vec =
Parser::parse_sql(&dialect, sql).map_err(|e| format!("SQL-Parse-Fehler: {}", e))?;
// 2. Wir durchlaufen und transformieren jedes einzelne Statement im AST-Vektor.
for statement in &mut ast_vec {
self.transform_statement(statement)?;
}
// 3. Ausführen der (möglicherweise modifizierten) Anweisungen in einer einzigen Transaktion.
// Dies stellt sicher, dass alle Operationen atomar sind.
let tx = conn.transaction().map_err(|e| e.to_string())?;
for statement in ast_vec {
let final_sql = statement.to_string();
tx.execute(&final_sql)
.map_err(|e| format!("DB-Ausführungsfehler bei '{}': {}", final_sql, e))?;
// Wenn es ein CREATE/ALTER TABLE war, müssen die Trigger neu erstellt werden.
// Dies geschieht innerhalb derselben Transaktion.
if let Statement::CreateTable { name, .. } | Statement::AlterTable { name, .. } =
statement
{
let table_name = name.0.last().unwrap().value.clone();
let trigger_manager = crate::trigger_manager::TriggerManager::new(&tx);
trigger_manager
.setup_triggers_for_table(&table_name)
.map_err(|e| {
format!("Trigger-Setup-Fehler für Tabelle '{}': {}", table_name, e)
})?;
}
}
tx.commit().map_err(|e| e.to_string())?;
Ok(())
}
// Diese Methode wendet die Transformation auf ein einzelnes Statement an.
fn transform_statement(&self, statement: &mut Statement) -> Result<(), String> {
let mut visitor = TombstoneVisitor;
// `visit` durchläuft den AST und ruft die entsprechenden `visit_*_mut` Methoden auf.
statement.visit(&mut visitor);
Ok(())
}
}
struct TombstoneVisitor;
impl TombstoneVisitor {
fn is_audited_table(&self, table_name: &str) -> bool {
!EXCLUDED_TABLES.contains(&table_name.to_lowercase().as_str())
}
}
impl VisitorMut for TombstoneVisitor {
type Break = ();
// Diese Methode wird für jedes Statement im AST aufgerufen
fn visit_statement_mut(&mut self, stmt: &mut Statement) -> ControlFlow<Self::Break> {
match stmt {
// Fall 1: CREATE TABLE
Statement::CreateTable { name, columns, .. } => {
let table_name = name.0.last().unwrap().value.as_str();
if self.is_audited_table(table_name) {
// Füge die 'tombstone'-Spalte hinzu, wenn sie nicht existiert
if !columns
.iter()
.any(|c| c.name.value.to_lowercase() == TOMBSTONE_COLUMN_NAME)
{
columns.push(ColumnDef {
name: Ident::new(TOMBSTONE_COLUMN_NAME),
data_type: DataType::Integer,
collation: None,
options: vec![], // Default ist 0
});
}
}
}
// Fall 2: DELETE
Statement::Delete(del_stmt) => {
// Wandle das DELETE-Statement in ein UPDATE-Statement um
let new_update = Statement::Update {
table: del_stmt.from.clone(),
assignments: vec![],
value: Box::new(Expr::Value(Value::Number("1".to_string(), false))),
from: None,
selection: del_stmt.selection.clone(),
returning: None,
};
// Ersetze das aktuelle Statement im AST
*stmt = new_update;
}
_ => {}
}
// Setze die Traversierung für untergeordnete Knoten fort (z.B. SELECTs)
visit_mut::walk_statement_mut(self, stmt)
}
// Diese Methode wird für jede Query (auch Subqueries) aufgerufen
fn visit_query_mut(&mut self, query: &mut Query) -> ControlFlow<Self::Break> {
// Zuerst rekursiv in die Tiefe gehen, um innere Queries zuerst zu bearbeiten
visit_mut::walk_query_mut(self, query);
// Dann die WHERE-Klausel der aktuellen Query anpassen
if let Some(from_clause) = query.body.as_select_mut().map(|s| &mut s.from) {
// (Hier würde eine komplexere Logik zur Analyse der Joins und Tabellen stehen)
// Vereinfacht nehmen wir an, wir fügen es für die erste Tabelle hinzu.
let table_name = if let Some(relation) = from_clause.get_mut(0) {
// Diese Logik muss verfeinert werden, um Aliase etc. zu behandeln
relation.relation.to_string()
} else {
"".to_string()
};
if self.is_audited_table(&table_name) {
let tombstone_check = Expr::BinaryOp {
left: Box::new(Expr::Identifier(Ident::new(TOMBSTONE_COLUMN_NAME))),
op: sqlparser::ast::BinaryOperator::Eq,
right: Box::new(Expr::Value(Value::Number("0".to_string(), false))),
};
let existing_selection = query.selection.take();
let new_selection = match existing_selection {
Some(expr) => Expr::BinaryOp {
left: Box::new(expr),
op: sqlparser::ast::BinaryOperator::And,
right: Box::new(tombstone_check),
},
None => tombstone_check,
};
query.selection = Some(Box::new(new_selection));
}
}
ControlFlow::Continue(())
}
}

View File

@ -0,0 +1,124 @@
// In src-tauri/src/trigger_manager.rs -> impl<'a> TriggerManager<'a>
// In einem neuen Modul, z.B. src-tauri/src/trigger_manager.rs
use crate::sql_proxy::ColumnInfo;
use rusqlite::{Result, Transaction};
pub struct TriggerManager<'a> {
tx: &'a Transaction<'a>,
}
impl<'a> TriggerManager<'a> {
pub fn new(tx: &'a Transaction<'a>) -> Self {
Self { tx }
}
// Die Hauptfunktion, die alles einrichtet
pub fn setup_triggers_for_table(&self, table_name: &str) -> Result<()> {
let columns = self.get_table_schema(table_name)?;
let pk_cols: Vec<_> = columns
.iter()
.filter(|c| c.is_pk)
.map(|c| c.name.as_str())
.collect();
let other_cols: Vec<_> = columns
.iter()
.filter(|c| !c.is_pk && c.name != "tombstone")
.map(|c| c.name.as_str())
.collect();
let drop_sql = self.generate_drop_triggers_sql(table_name);
let insert_sql = self.generate_insert_trigger_sql(table_name, &pk_cols, &other_cols);
let update_sql = self.generate_update_trigger_sql(table_name, &pk_cols, &other_cols);
self.tx.execute_batch(&drop_sql)?;
self.tx.execute_batch(&insert_sql)?;
self.tx.execute_batch(&update_sql)?;
Ok(())
}
fn get_table_schema(&self, table_name: &str) -> Result<Vec<ColumnInfo>> {
let sql = format!("PRAGMA table_info('{}')", table_name);
let mut stmt = self.tx.prepare(&sql)?;
let rows = stmt.query_map(|row| {
let pk_val: i64 = row.get(5)?;
Ok(ColumnInfo {
name: row.get(1)?,
is_pk: pk_val > 0,
})
})?;
let mut columns = Vec::new();
for row_result in rows {
columns.push(row_result?);
}
Ok(columns)
}
//... Implementierung der SQL-Generierungsfunktionen...
fn generate_update_trigger_sql(&self, table_name: &str, pks: &[&str], cols: &[&str]) -> String {
// Erstellt dynamisch die Key-Value-Paare für das JSON-Objekt des Primärschlüssels.
let pk_json_payload_new = pks
.iter()
.map(|pk| format!("'{}', NEW.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
let pk_json_payload_old = pks
.iter()
.map(|pk| format!("'{}', OLD.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
// Erstellt die einzelnen INSERT-Anweisungen für jede Spalte
let column_updates = cols.iter().map(|col| format!(
r#"
-- Protokolliere die Spaltenänderung, wenn sie stattgefunden hat und es kein Soft-Delete ist
INSERT INTO crdt_log (hlc_timestamp, op_type, table_name, row_pk, column_name, value, old_value)
SELECT
'placeholder_hlc', -- TODO: HLC-Funktion hier aufrufen
'UPDATE',
'{table}',
json_object({pk_payload_new}),
'{column}',
json_object('value', NEW."{column}"),
json_object('value', OLD."{column}")
WHERE
NEW."{column}" IS NOT OLD."{column}"
"#,
table = table_name,
pk_payload_new = pk_json_payload_new,
column = col
)).collect::<Vec<_>>().join("\n");
// Erstellt die Logik für den Soft-Delete
let delete_logic = format!(
r#"
-- Protokolliere den Soft-Delete
INSERT INTO crdt_log (hlc_timestamp, op_type, table_name, row_pk)
SELECT
'placeholder_hlc', -- TODO: HLC-Funktion hier aufrufen
'DELETE',
'{table}',
json_object({pk_payload_old})
WHERE
OLD.{tombstone_col} = 0
"#,
table = table_name,
pk_payload_old = pk_json_payload_old
);
// Kombiniert alles zu einem einzigen Trigger
format!(
"CREATE TRIGGER IF NOT EXISTS {table_name}_crdt_update
AFTER UPDATE ON {table_name}
FOR EACH ROW
BEGIN
{column_updates}
{delete_logic}
END;"
)
}
}

View File

@ -6,8 +6,8 @@ use rusqlite::{
Connection, OpenFlags, ToSql, Connection, OpenFlags, ToSql,
}; };
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use std::fs;
use std::path::Path; use std::path::Path;
use std::{fs, path::PathBuf};
use tauri::State; use tauri::State;
// --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht --- // --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht ---
// Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes. // Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes.
@ -168,7 +168,13 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connectio
OpenFlags::SQLITE_OPEN_READ_WRITE OpenFlags::SQLITE_OPEN_READ_WRITE
}; };
let conn = Connection::open_with_flags(path, flags).map_err(|e| e.to_string())?; let conn = Connection::open_with_flags(path, flags).map_err(|e| {
format!(
"Dateiii gibt es nicht: {}. Habe nach {} gesucht",
e.to_string(),
path
)
})?;
conn.pragma_update(None, "key", key) conn.pragma_update(None, "key", key)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;

View File

@ -3,6 +3,7 @@ pub mod core;
use rusqlite::Connection; use rusqlite::Connection;
use serde_json::Value as JsonValue; use serde_json::Value as JsonValue;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
@ -58,9 +59,14 @@ pub fn create_encrypted_database(
app_handle.path().resource_dir() app_handle.path().resource_dir()
); );
/* let resource_path = app_handle
.path()
.resolve("database/vault.db", BaseDirectory::Resource)
.map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?; */
let resource_path = app_handle let resource_path = app_handle
.path() .path()
.resolve("database/vault.db", BaseDirectory::Resource) .resolve("temp_vault.db", BaseDirectory::AppLocalData)
.map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?; .map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?;
// Prüfen, ob die Ressourcendatei existiert // Prüfen, ob die Ressourcendatei existiert
@ -72,12 +78,16 @@ pub fn create_encrypted_database(
} }
// Sicherstellen, dass das Zielverzeichnis existiert // Sicherstellen, dass das Zielverzeichnis existiert
if let Some(parent) = Path::new(&path).parent() { /* if let Some(parent) = Path::new(&path).parent() {
if !parent.exists() { if !parent.exists() {
std::fs::create_dir_all(parent) std::fs::create_dir_all(parent).map_err(|e| {
.map_err(|e| format!("Fehler beim Erstellen des Zielverzeichnisses: {}", e))?; format!(
"Fehler beim Erstellen des Zielverzeichnisses: {}\n mit Fehler {}",
path, e
)
})?;
} }
} } */
let target = Path::new(&path); let target = Path::new(&path);
if target.exists() & target.is_file() { if target.exists() & target.is_file() {
@ -167,14 +177,23 @@ pub fn create_encrypted_database(
Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",)) Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",))
} }
use tauri_plugin_dialog::{Dialog, DialogExt, MessageDialogKind};
#[tauri::command] #[tauri::command]
pub fn open_encrypted_database( pub fn open_encrypted_database(
app_handle: AppHandle,
path: String, path: String,
key: String, key: String,
state: State<'_, DbConnection>, state: State<'_, DbConnection>,
) -> Result<String, String> { ) -> Result<String, String> {
/* let vault_path = app_handle
.path()
.resolve(format!("vaults/{}", path), BaseDirectory::AppLocalData)
.map_err(|e| format!("Fehler {}", e))?
.into_os_string()
.into_string()
.unwrap(); */
if !std::path::Path::new(&path).exists() { if !std::path::Path::new(&path).exists() {
return Err("File not found ".into()); return Err(format!("File not found {}", path).into());
} }
let conn = let conn =

View File

@ -1,63 +0,0 @@
// src-tauri/src/sql_proxy.rs
use rusqlite::Connection;
use sqlparser::ast::Statement;
use sqlparser::dialect::SQLiteDialect;
use sqlparser::parser::Parser;
use std::sync::{Arc, Mutex};
// Der Schema-Cache wird später benötigt, um zu wissen, welche Tabellen eine 'tombstone'-Spalte haben.
// Für den Anfang lassen wir ihn leer.
pub struct SchemaCache {
// TODO: z.B. HashMap<String, Vec<String>> für Tabellen und ihre Spalten
}
impl SchemaCache {
pub fn new() -> Self {
Self {}
}
// TODO: Methoden zum Befüllen und Abfragen des Caches
}
// Die Hauptstruktur unseres Proxys
pub struct SqlProxy {
// Wir benötigen eine threadsichere Referenz auf den Schema-Cache
schema_cache: Arc<Mutex<SchemaCache>>,
}
impl SqlProxy {
pub fn new(schema_cache: Arc<Mutex<SchemaCache>>) -> Self {
Self { schema_cache }
}
// Die zentrale Ausführungsfunktion
pub fn execute(&self, sql: &str, conn: &Connection) -> Result<(), String> {
// 1. Parsen des SQL-Strings in einen AST
let dialect = SQLiteDialect {};
let mut ast =
Parser::parse_sql(&dialect, sql).map_err(|e| format!("SQL-Parse-Fehler: {}", e))?;
// Sicherstellen, dass wir nur eine Anweisung haben
if ast.len() != 1 {
return Err("Nur einzelne SQL-Anweisungen werden unterstützt.".to_string());
}
let statement = &mut ast;
// 2. Umschreiben des AST (Logik folgt in Abschnitt 2)
self.transform_statement(statement)?;
// 3. Ausführen der (möglicherweise modifizierten) Anweisung
let final_sql = statement.to_string();
conn.execute(&final_sql)
.map_err(|e| format!("DB-Ausführungsfehler: {}", e))?;
Ok(())
}
// Platzhalter für die Transformationslogik
fn transform_statement(&self, statement: &mut Statement) -> Result<(), String> {
// HIER KOMMT DIE MAGIE HIN
// TODO: Implementierung der `CREATE TABLE`, `DELETE` und `SELECT` Transformationen
Ok(())
}
}

View File

@ -1,4 +1,5 @@
//mod browser; //mod browser;
mod android_storage;
mod database; mod database;
mod extension; mod extension;
mod models; mod models;
@ -44,13 +45,15 @@ pub fn run() {
}) })
.manage(DbConnection(Arc::new(Mutex::new(None)))) .manage(DbConnection(Arc::new(Mutex::new(None))))
.manage(ExtensionState::default()) .manage(ExtensionState::default())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_os::init()) .plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_persisted_scope::init())
.plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_android_fs::init())
//.plugin(tauri_plugin_sql::Builder::new().build()) //.plugin(tauri_plugin_sql::Builder::new().build())
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
database::create_encrypted_database, database::create_encrypted_database,
@ -63,6 +66,9 @@ pub fn run() {
extension::copy_directory, extension::copy_directory,
extension::database::extension_sql_execute, extension::database::extension_sql_execute,
extension::database::extension_sql_select, extension::database::extension_sql_select,
android_storage::request_storage_permission,
android_storage::has_storage_permission,
android_storage::get_external_storage_paths,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -55,7 +55,7 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"resources": ["database/vault.db", "resources/"], "resources": ["database/vault.db"],
"linux": { "linux": {
"appimage": { "appimage": {

View File

@ -69,6 +69,9 @@ import { save } from '@tauri-apps/plugin-dialog'
import { onKeyStroke } from '@vueuse/core' import { onKeyStroke } from '@vueuse/core'
import { useVaultStore } from '~/stores/vault' import { useVaultStore } from '~/stores/vault'
import { vaultDatabaseSchema } from './schema' import { vaultDatabaseSchema } from './schema'
import { BaseDirectory, readFile, writeFile } from '@tauri-apps/plugin-fs'
import { resolveResource } from '@tauri-apps/api/path'
//import { convertFileSrc } from "@tauri-apps/api/tauri";
onKeyStroke('Enter', (e) => { onKeyStroke('Enter', (e) => {
e.preventDefault() e.preventDefault()
@ -123,12 +126,22 @@ const onCreateAsync = async () => {
open.value = false open.value = false
try { try {
const template_vault_path = await resolveResource('database/vault.db')
const template_vault = await readFile(template_vault_path)
database.path = await save({ database.path = await save({
defaultPath: database.name.endsWith('.db') defaultPath: database.name.endsWith('.db')
? database.name ? database.name
: `${database.name}.db`, : `${database.name}.db`,
}) })
if (!database.path) return
await writeFile('temp_vault.db', template_vault, {
baseDir: BaseDirectory.AppLocalData,
})
console.log('data', database) console.log('data', database)
if (database.path && database.password) { if (database.path && database.password) {
@ -146,7 +159,7 @@ const onCreateAsync = async () => {
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
add({ type: 'error', text: JSON.stringify(error) }) add({ type: 'error', text: `${error}` })
} }
} }

View File

@ -18,7 +18,7 @@
<UiTextGradient>HaexVault</UiTextGradient> <UiTextGradient>HaexVault</UiTextGradient>
</template> </template>
</i18n-t> </i18n-t>
Path1: {{ test }}
<div class="text-sm">{{ props.path ?? database.path }}</div> <div class="text-sm">{{ props.path ?? database.path }}</div>
</template> </template>
@ -41,6 +41,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { open as openVault } from '@tauri-apps/plugin-dialog' import { open as openVault } from '@tauri-apps/plugin-dialog'
import { vaultDatabaseSchema } from './schema' import { vaultDatabaseSchema } from './schema'
import {
BaseDirectory,
copyFile,
mkdir,
exists,
readFile,
writeFile,
stat,
} from '@tauri-apps/plugin-fs'
const { t } = useI18n() const { t } = useI18n()
@ -96,6 +105,7 @@ const onLoadDatabase = async () => {
], ],
}) })
console.log('onLoadDatabase', database.path)
if (!database.path) return if (!database.path) return
open.value = true open.value = true
@ -118,13 +128,17 @@ const onOpenDatabase = async () => {
database.password, database.password,
) )
if (!pathCheck.success || !passwordCheck.success || !path) { /* if (!pathCheck.success || !passwordCheck.success || !path) {
add({ add({
type: 'error', type: 'error',
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`, text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
}) })
return return
} } */
//const vaultName = await copyDatabaseInAppDirectoryAsync(path)
//if (!vaultName) return
const vaultId = await openAsync({ const vaultId = await openAsync({
path, path,
@ -159,6 +173,31 @@ const onOpenDatabase = async () => {
} }
} }
const test = ref()
const copyDatabaseInAppDirectoryAsync = async (source: string) => {
const vaultName = getFileName(source)
const vaultsDirExists = await exists('vaults', {
baseDir: BaseDirectory.AppLocalData,
})
//convertFileSrc
if (!vaultsDirExists) {
await mkdir('vaults', {
baseDir: BaseDirectory.AppLocalData,
})
}
test.value = `source: ${source} - target: vaults/${vaultName}`
console.log('copyDatabaseInAppDirectoryAsync', `vaults/${vaultName}`)
const sourceFile = await readFile(source)
await writeFile(`vaults/HaexVault.db`, sourceFile, {
baseDir: BaseDirectory.AppLocalData,
})
/* await copyFile(source, `vaults/HaexVault.db`, {
toPathBaseDir: BaseDirectory.AppLocalData,
}) */
return vaultName
}
const onAbort = () => { const onAbort = () => {
initDatabase() initDatabase()
open.value = false open.value = false

View File

@ -21,6 +21,9 @@
v-model:open="passwordPromptOpen" v-model:open="passwordPromptOpen"
:path="vaultPath" :path="vaultPath"
/> />
<UiButton @click="requesty()">Storage Request</UiButton>
res: {{ res }}
</div> </div>
<div <div
@ -41,7 +44,7 @@
> >
<button <button
class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4" class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4"
@click=";(passwordPromptOpen = true), (vaultPath = vault.path)" @click=";((passwordPromptOpen = true), (vaultPath = vault.path))"
> >
<span class="block md:hidden"> <span class="block md:hidden">
{{ vault.name }} {{ vault.name }}
@ -91,6 +94,18 @@ const { syncLastVaultsAsync, removeVaultAsync } = useLastVaultStore()
const { lastVaults } = storeToRefs(useLastVaultStore()) const { lastVaults } = storeToRefs(useLastVaultStore())
await syncLastVaultsAsync() await syncLastVaultsAsync()
const res = ref()
const storage = useAndroidStorage()
const requesty = async () => {
try {
res.value = await storage.requestStoragePermission()
res.value += ' wat the fuk'
} catch (error) {
res.value = error
}
}
</script> </script>
<i18n lang="json"> <i18n lang="json">

View File

@ -5,6 +5,8 @@
v-bind="extension" v-bind="extension"
:key="extension.id" :key="extension.id"
/> />
<UiButton @click="requesty()">Storage Request</UiButton>
res: {{ res }}
</div> </div>
</template> </template>
@ -13,5 +15,17 @@ definePageMeta({
name: 'vaultOverview', name: 'vaultOverview',
}) })
const storage = useAndroidStorage()
const extensionStore = useExtensionsStore() const extensionStore = useExtensionsStore()
const res = ref()
const requesty = async () => {
try {
res.value = await storage.requestStoragePermission()
res.value += ' wat the fuk'
} catch (error) {
res.value = error
}
}
</script> </script>

View File

@ -51,8 +51,8 @@ export const useVaultStore = defineStore('vaultStore', () => {
if (result !== 'success') throw new Error(result) if (result !== 'success') throw new Error(result)
const vaultId = await getVaultIdAsync(path) const vaultId = await getVaultIdAsync(path)
const seperator = platform() === 'windows' ? '\\' : '/'
const fileName = path.split(seperator).pop() const fileName = getFileName(path)
openVaults.value = { openVaults.value = {
...openVaults.value, ...openVaults.value,

View File

@ -0,0 +1,38 @@
import { invoke } from '@tauri-apps/api/core'
export const useAndroidStorage = () => {
const requestStoragePermission = async (): Promise<string> => {
try {
return await invoke(
'plugin:android-fs|openManageAllFilesAccessPermissionSettings',
)
} catch (error) {
console.error('Failed to request storage permission:', error)
throw error
}
}
const hasStoragePermission = async (): Promise<boolean> => {
try {
return await invoke('has_storage_permission')
} catch (error) {
console.error('Failed to check storage permission:', error)
return false
}
}
const getExternalStoragePaths = async (): Promise<string[]> => {
try {
return await invoke('get_external_storage_paths')
} catch (error) {
console.error('Failed to get storage paths:', error)
return []
}
}
return {
requestStoragePermission,
hasStoragePermission,
getExternalStoragePaths,
}
}

View File

@ -1,3 +1,4 @@
import { platform } from '@tauri-apps/plugin-os'
import type { LocationQueryValue, RouteLocationRawI18n } from 'vue-router' import type { LocationQueryValue, RouteLocationRawI18n } from 'vue-router'
export const bytesToBase64DataUrlAsync = async ( export const bytesToBase64DataUrlAsync = async (
@ -67,7 +68,7 @@ export const readableFileSize = (sizeInByte: number | string = 0) => {
export const getSingleRouteParam = ( export const getSingleRouteParam = (
param: string | string[] | LocationQueryValue | LocationQueryValue[], param: string | string[] | LocationQueryValue | LocationQueryValue[],
): string => { ): string => {
const _param = Array.isArray(param) ? param.at(0) ?? '' : param ?? '' const _param = Array.isArray(param) ? (param.at(0) ?? '') : (param ?? '')
//console.log('getSingleRouteParam found:', _param, param) //console.log('getSingleRouteParam found:', _param, param)
return decodeURIComponent(_param) return decodeURIComponent(_param)
} }
@ -230,3 +231,8 @@ export const areObjectsEqual = (valueA: unknown, valueB: unknown): boolean => {
// 5. Wenn die Schleife durchläuft, sind die Objekte gleich // 5. Wenn die Schleife durchläuft, sind die Objekte gleich
return true return true
} }
export const getFileName = (fullPath: string) => {
const seperator = platform() === 'windows' ? '\\' : '/'
return fullPath.split(seperator).pop()
}