diff --git a/.gitignore b/.gitignore index f7d82ce..64e623e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -/target -/logs +target +logs .idea mirrorSphere.db db.lock diff --git a/Cargo.lock b/Cargo.lock index 427a315..8ce6a29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,18 +6,24 @@ version = 4 name = "MirrorSphere" version = "0.1.0" dependencies = [ - "anyhow", "async-trait", + "bincode", "blake2", "blake3", "chrono", + "crossbeam-queue", "dashmap", "digest", + "eframe", + "egui-file-dialog", "fs4", "futures", - "log", + "libc", "log-panics", + "macros", "md-5", + "memmap2", + "paste", "privilege", "serde", "serde_json", @@ -36,6 +42,112 @@ dependencies = [ "windows-acl", ] +[[package]] +name = "ab_glyph" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" + +[[package]] +name = "accesskit" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25ae84c0260bdf5df07796d7cc4882460de26a2b406ec0e6c42461a723b271b" + +[[package]] +name = "accesskit_atspi_common" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bd41de2e54451a8ca0dd95ebf45b54d349d29ebceb7f20be264eee14e3d477" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "thiserror 1.0.69", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bfae7c152994a31dc7d99b8eeac7784a919f71d1b306f4b83217e110fd3824c" +dependencies = [ + "accesskit", + "hashbrown 0.15.4", +] + +[[package]] +name = "accesskit_macos" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692dd318ff8a7a0ffda67271c4bd10cf32249656f4e49390db0b26ca92b095f2" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.15.4", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f7474c36606d0fe4f438291d667bae7042ea2760f506650ad2366926358fc8" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a042b62c9c05bf7b616f015515c17d2813f3ba89978d6f4fc369735d60700a" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.15.4", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1f0d3d13113d8857542a4f8d1a1c24d1dc1527b77aee8426127f4901588708" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -47,9 +159,22 @@ dependencies = [ [[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] [[package]] name = "aho-corasick" @@ -62,9 +187,36 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.1", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" [[package]] name = "android-tzdata" @@ -82,10 +234,24 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.98" +name = "arboard" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", + "parking_lot", + "percent-encoding", + "windows-sys 0.59.0", + "x11rb", +] [[package]] name = "arrayref" @@ -99,6 +265,134 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c74e56284d2188cabb6ad99603d1ace887a5d7e7b695d01b728155ed9ed427" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.7", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.0.7", + "tracing", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.0.7", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.88" @@ -120,16 +414,72 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.4.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83247582e7508838caf5f316c00791eee0e15c0bf743e6880585b867e16815c" +dependencies = [ + "atspi-common", + "atspi-connection", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dfc05e7cdf90988a197803bf24f5788f94f7c94a69efa95683e8ffe76cfdfb" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-connection" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4193d51303d8332304056ae0004714256b46b6635a5c556109b319c0d3784938" +dependencies = [ + "atspi-common", + "atspi-proxies", + "futures-lite", + "zbus", +] + +[[package]] +name = "atspi-proxies" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2eebcb9e7e76f26d0bcfd6f0295e1cd1e6f33bedbc5698a971db8dc43d7751c" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -148,15 +498,56 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.6.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -194,10 +585,52 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.17.0" +name = "block2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "byteorder" @@ -206,25 +639,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bytes" -version = "1.8.0" +name = "byteorder-lite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] [[package]] name = "cc" -version = "1.2.1" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ + "jobserver", + "libc", "shlex", ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] [[package]] name = "chrono" @@ -236,10 +724,41 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -261,6 +780,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -268,19 +807,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "cpufeatures" -version = "0.2.15" +name = "core-graphics" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -292,28 +855,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] -name = "crossbeam-channel" -version = "0.5.13" +name = "crc32fast" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -325,6 +903,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "dashmap" version = "6.1.0" @@ -341,9 +925,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -352,9 +936,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -371,6 +955,43 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.60.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -382,6 +1003,24 @@ dependencies = [ "syn", ] +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -389,30 +1028,249 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "either" -version = "1.13.0" +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecolor" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a631732d995184114016fab22fc7e3faf73d6841c2d7650395fe251fbcd9285" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "eframe" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790ccfbb3dd556588342463454b2b2b13909e5fdce5bc2a1432a8aa69c8b7a" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winapi", + "windows-sys 0.59.0", + "winit", +] + +[[package]] +name = "egui" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8470210c95a42cc985d9ffebfd5067eea55bdb1c3f7611484907db9639675e28" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.9.1", + "emath", + "epaint", + "log", + "nohash-hasher", + "profiling", + "smallvec", + "unicode-segmentation", +] + +[[package]] +name = "egui-file-dialog" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ae40a3a02bb2f617dd0f6b41d9fe64713667684f68d52694362cad22828e74" +dependencies = [ + "directories", + "dunce", + "egui", + "serde", + "sysinfo", +] + +[[package]] +name = "egui-wgpu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14de9942d8b9e99e2d830403c208ab1a6e052e925a7456a4f6f66d567d90de1d" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 1.0.69", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c490804a035cec9c826082894a3e1ecf4198accd3817deb10f7919108ebafab0" +dependencies = [ + "accesskit_winit", + "ahash", + "arboard", + "bytemuck", + "egui", + "log", + "profiling", + "raw-window-handle", + "smithay-clipboard", + "web-time", + "webbrowser", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44f3fd4fdc5f960c9e9ef7327c26647edc3141abf96102980647129d49358e6" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "emath" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "45f057b141e7e46340c321400be74b793543b1b213036f0f989c35d35957c32e" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "epaint" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cca02195f0552c17cabdc02f39aa9ab6fbd815dac60ab1cd3d5b0aa6f9551c" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "log", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8495e11ed527dff39663b8c36b6c2b2799d7e4287fb90556e455d72eca0b4d3" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "etcetera" version = "0.8.0" @@ -426,15 +1284,40 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "field-offset" version = "0.3.6" @@ -445,6 +1328,16 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.1" @@ -458,9 +1351,36 @@ dependencies = [ [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" @@ -541,6 +1461,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -593,26 +1526,36 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "windows-targets 0.48.5", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -621,6 +1564,106 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.9.1", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.1", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -629,9 +1672,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ "allocator-api2", "equivalent", @@ -644,7 +1687,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.4", ] [[package]] @@ -655,9 +1698,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -665,6 +1708,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + [[package]] name = "hkdf" version = "0.12.4" @@ -685,25 +1734,26 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core", ] [[package]] @@ -717,21 +1767,22 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", "litemap", @@ -740,31 +1791,11 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -772,67 +1803,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" version = "1.0.3" @@ -846,29 +1864,91 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] -name = "indexmap" -version = "2.6.0" +name = "image" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.4", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", ] [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "jpeg-decoder" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -889,6 +1969,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + [[package]] name = "lazy_static" version = "1.5.0" @@ -900,15 +1986,36 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.169" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.13", +] [[package]] name = "libsqlite3-sys" @@ -923,9 +2030,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" @@ -935,15 +2042,21 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -965,6 +2078,15 @@ dependencies = [ "log", ] +[[package]] +name = "macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "matchers" version = "0.1.0" @@ -986,9 +2108,18 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] [[package]] name = "memoffset" @@ -1001,25 +2132,98 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "hermit-abi", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] +[[package]] +name = "naga" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.15.4", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "strum", + "thiserror 2.0.12", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1084,19 +2288,347 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.5" +name = "num_enum" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.1", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2 0.6.1", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" +dependencies = [ + "bitflags 2.9.1", + "objc2 0.6.1", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] [[package]] name = "overload" @@ -1104,6 +2636,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "owned_ttf_parser" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" +dependencies = [ + "ttf-parser", +] + [[package]] name = "parking" version = "2.2.1" @@ -1112,9 +2653,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1122,17 +2663,23 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1149,10 +2696,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project-lite" -version = "0.2.15" +name = "pin-project" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1160,6 +2727,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -1183,9 +2761,52 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.7", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1195,11 +2816,11 @@ 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 0.7.35", + "zerocopy", ] [[package]] @@ -1215,23 +2836,63 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.90" +name = "proc-macro-crate" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e1ced3fe749df87a909c23e9607ab9a09c8f0bedb7e03b8146f4c08c298673" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.37" +name = "profiling" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 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 = "rand" version = "0.8.5" @@ -1245,13 +2906,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.0", - "zerocopy 0.8.16", + "rand_core 0.9.3", ] [[package]] @@ -1271,7 +2931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1280,26 +2940,51 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] name = "rand_core" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", - "zerocopy 0.8.16", + "getrandom 0.3.3", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags", + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", ] [[package]] @@ -1347,10 +3032,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "rsa" -version = "0.9.6" +name = "renderdoc-sys" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rsa" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -1368,9 +3059,21 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -1383,15 +3086,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] @@ -1400,7 +3103,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.9.4", @@ -1409,15 +3112,30 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1425,6 +3143,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "security-framework-sys" version = "2.14.0" @@ -1437,9 +3168,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" @@ -1474,10 +3205,21 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.8" +name = "serde_repr" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -1507,9 +3249,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1543,9 +3285,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] @@ -1561,28 +3303,85 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.9" +name = "simd-adler32" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" dependencies = [ - "autocfg", + "version_check", ] [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.9.1", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1628,6 +3427,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "base64", "bytes", + "chrono", "crc", "crossbeam-queue", "either", @@ -1636,7 +3436,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.1", + "hashbrown 0.15.4", "hashlink", "indexmap", "log", @@ -1701,9 +3501,10 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.9.1", "byteorder", "bytes", + "chrono", "crc", "digest", "dotenvy", @@ -1744,8 +3545,9 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.9.1", "byteorder", + "chrono", "crc", "dotenvy", "etcetera", @@ -1781,6 +3583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", + "chrono", "flume", "futures-channel", "futures-core", @@ -1805,6 +3608,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "stringprep" version = "0.1.5" @@ -1816,6 +3631,28 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1824,9 +3661,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.88" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8e9a4e1639f47f655bf8e5198232f05615d5fb7e864ef5c4f5abdaf8ad3b8f4" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1835,15 +3672,49 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "sysinfo" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aab138f5c1bb35231de19049060a87977ad23e04f2303e953bc5c2947ac7dec4" +dependencies = [ + "libc", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1886,19 +3757,29 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 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.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -1911,25 +3792,50 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tiny-skia" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -1937,9 +3843,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -1952,17 +3858,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "1140bb80481756a8cbe10541f37433b459c5aa1e727b4c020fbfebdc25bf3ec4" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -1992,9 +3900,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -2004,18 +3912,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", @@ -2027,9 +3935,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "tracing" @@ -2057,9 +3965,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -2068,9 +3976,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -2106,22 +4014,48 @@ dependencies = [ ] [[package]] -name = "typenum" -version = "1.17.0" +name = "ttf-parser" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.1", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -2139,22 +4073,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] -name = "url" -version = "2.5.3" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -2163,13 +4109,15 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.1", - "rand 0.9.0", + "getrandom 0.3.3", + "js-sys", + "rand 0.9.1", "serde", + "wasm-bindgen", ] [[package]] @@ -2191,16 +4139,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "virtue" +version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -2237,6 +4201,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -2269,6 +4246,254 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wayland-backend" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" +dependencies = [ + "cc", + "downcast-rs", + "rustix 0.38.44", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" +dependencies = [ + "bitflags 2.9.1", + "rustix 0.38.44", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.9.1", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" +dependencies = [ + "rustix 0.38.44", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" +dependencies = [ + "bitflags 2.9.1", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webbrowser" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf4f3c0ba838e82b4e5ccc4157003fb8c324ee24c058470ffb82820becbde98" +dependencies = [ + "core-foundation 0.10.1", + "jni", + "log", + "ndk-context", + "objc2 0.6.1", + "objc2-foundation 0.3.1", + "url", + "web-sys", +] + +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "wgpu" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.4", + "js-sys", + "log", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "hashbrown 0.15.4", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17" +dependencies = [ + "bitflags 2.9.1", + "cfg_aliases", + "libloading", + "log", + "naga", + "parking_lot", + "portable-atomic", + "raw-window-handle", + "renderdoc-sys", + "thiserror 2.0.12", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc" +dependencies = [ + "bitflags 2.9.1", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.12", + "web-sys", +] + [[package]] name = "which" version = "4.4.2" @@ -2278,16 +4503,16 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.41", + "rustix 0.38.44", ] [[package]] name = "whoami" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ - "redox_syscall", + "redox_syscall 0.5.13", "wasite", ] @@ -2313,6 +4538,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2321,12 +4555,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.61.1" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link", "windows-numerics", @@ -2350,16 +4584,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -2381,7 +4606,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", "windows-threading", ] @@ -2410,9 +4635,9 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" @@ -2420,7 +4645,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link", ] @@ -2442,6 +4667,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2469,6 +4703,30 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2493,13 +4751,29 @@ 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", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows-threading" version = "0.1.0" @@ -2509,6 +4783,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -2521,6 +4801,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -2533,6 +4825,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -2545,12 +4849,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -2563,6 +4885,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -2575,6 +4909,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -2587,6 +4933,18 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -2600,40 +4958,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.10" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winit" +version = "0.30.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4409c10174df8779dc29a4788cac85ed84024ccbc1743b776b21a520ee1aaf4" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] -name = "write16" -version = "1.0.0" +name = "writeable" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] -name = "writeable" -version = "0.5.5" +name = "x11-dl" +version = "2.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.1", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "yoke" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -2643,9 +5116,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", @@ -2654,40 +5127,116 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "zbus" +version = "5.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "windows-sys 0.59.0", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] -name = "zerocopy" -version = "0.8.16" +name = "zbus-lockstep" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8c07a70861ce02bad1607b5753ecb2501f67847b9f9ada7c160fff0ec6300c" +checksum = "29e96e38ded30eeab90b6ba88cb888d70aef4e7489b6cd212c5e5b5ec38045b6" dependencies = [ - "zerocopy-derive 0.8.16", + "zbus_xml", + "zvariant", ] [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "zbus-lockstep-macros" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "dc6821851fa840b708b4cbbaf6241868cabc85a2dc22f426361b0292bfc0b836" dependencies = [ "proc-macro2", "quote", "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589e9a02bfafb9754bb2340a9e3b38f389772684c63d9637e76b1870377bec29" +dependencies = [ + "quick-xml 0.36.2", + "serde", + "static_assertions", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.16" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5226bc9a9a9836e7428936cde76bb6b22feea1a8bfdbc0d241136e4d13417e25" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", @@ -2696,18 +5245,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", @@ -2722,10 +5271,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -2734,11 +5294,52 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zvariant" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "static_assertions", + "syn", + "winnow", +] diff --git a/Cargo.toml b/Cargo.toml index 83fc749..10628eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,41 @@ [package] name = "MirrorSphere" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] -log = "0.4.27" -toml = "0.8.22" -anyhow = "1.0.98" -dashmap = "6.1.0" -tracing = "0.1.41" -privilege = "0.3.0" -thiserror = "2.0.12" -tracing-appender = "0.2.3" -fs4 = { version = "0.13.1", features = ["tokio"] } -tokio = { version = "1.45.0", features = ["full"] } -serde = { version = "1.0.219", features = ["derive"] } -sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "uuid", "derive"] } -log-panics = { version = "2.1.0", features = ["with-backtrace"] } -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } -serde_json = "1.0.140" -futures = "0.3.31" async-trait = "0.1.88" -tokio-stream = "0.1.17" -sha3 = "0.10.8" -sha2 = "0.10.8" +bincode = { version = "2.0.1", features = ["serde"] } blake2 = "0.10.6" +blake3 = { version = "1.8.2", features = ["traits-preview"] } +chrono = { version = "0.4.41", features = ["serde"] } +crossbeam-queue = "0.3.11" +dashmap = "6.1.0" digest = "0.10.7" +eframe = "0.32.0" +egui-file-dialog = "0.11.0" +fs4 = { version = "0.13.1", features = ["sync", "tokio"] } +futures = "0.3.31" +libc = "0.2.169" +log-panics = { version = "2.1.0", features = ["with-backtrace"] } +macros = { path = "macros" } md-5 = "0.10.6" -chrono = "0.4.41" - -[dependencies.blake3] -version = "1.8.2" -features = ["traits-preview"] - -[dependencies.uuid] -version = "1.16.0" -features = ["v4", "fast-rng", "serde"] +memmap2 = "0.9.5" +paste = "1.0.15" +privilege = "0.3.0" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +sha2 = "0.10.8" +sha3 = "0.10.8" +sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite", "uuid", "derive", "chrono"] } +thiserror = "2.0.12" +tokio-stream = "0.1.17" +tokio = { version = "1.45.1", features = ["full"] } +toml = "0.8.22" +tracing = "0.1.41" +tracing-appender = "0.2.3" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +uuid = { version = "1.17.0", features = ["v4", "fast-rng", "serde"] } [target.'cfg(windows)'.dependencies.windows-acl] version = "0.3.0" @@ -43,7 +43,7 @@ version = "0.3.0" [target.'cfg(windows)'.dependencies.windows] version = "0.61.1" features = [ - "Win32", "Win32_System", "Win32_System_Memory", "Win32_System_Threading", "Win32_System_Time", "Win32_System_Registry", - "Win32_System_Com", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage", "Win32_Storage_FileSystem", - "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation" + "Win32", "Win32_System", "Win32_System_Memory", "Win32_System_IO", "Win32_System_Threading", "Win32_System_Time", + "Win32_System_Registry", "Win32_System_Com", "Win32_Security", "Win32_Security_Authorization", "Win32_Storage", + "Win32_Storage_FileSystem", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_Foundation" ] diff --git a/config.toml b/config.toml index eed6bf4..7924332 100644 --- a/config.toml +++ b/config.toml @@ -1,3 +1,5 @@ [Config] +internal_timestamp = 20 +default_wakeup_time = 10 max_concurrency = 16 max_file_operations = 100 diff --git a/devnote.txt b/devnote.txt index 82dc4f3..86d8d86 100644 --- a/devnote.txt +++ b/devnote.txt @@ -1,10 +1,5 @@ -TODO - - 使用執行緒安全的 queue 來實作演算法 可以實現演算法的平行化 - -一些想法 (皆需要直接調用 Windows API) +一些想法 (需要直接調用 Windows API) - 用 USN Journal 來追蹤檔案系統變更來達到快速掃描的目的 (NTFS 限定) - - 支援 Windows NTFS ACL (Access Control List) - - 支援額外的檔案跟檔案屬性 平行化版本 Initialize: @@ -23,9 +18,6 @@ for i in 1..=worker_count: queue.extend(current_level_visited.clear()) continue - if need_resume && not reach_checkpoint: - continue - process current_dir: - check/create directory structure - spawn file copy tasks @@ -43,6 +35,6 @@ for i in 1..=worker_count: queue.push(sub_dir) - update progress + update progress (save queue, set state) save checkpoint periodically join all workers diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..5a60ab1 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..29fb807 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,20 @@ +mod log; +mod loggable; +mod traceable; + +use proc_macro::TokenStream; + +#[proc_macro] +pub fn log(input: TokenStream) -> TokenStream { + log::log_impl(input) +} + +#[proc_macro] +pub fn loggable(input: TokenStream) -> TokenStream { + loggable::loggable_impl(input) +} + +#[proc_macro] +pub fn traceable(input: TokenStream) -> TokenStream { + traceable::traceable_impl(input) +} diff --git a/macros/src/log.rs b/macros/src/log.rs new file mode 100644 index 0000000..ff56b3f --- /dev/null +++ b/macros/src/log.rs @@ -0,0 +1,66 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{parse_macro_input, Expr, Token}; + +struct LogInput { + error: Expr, + debug_info: Option, +} + +impl Parse for LogInput { + fn parse(input: ParseStream) -> syn::Result { + let error = input.parse::()?; + + let debug_info = if input.peek(Token![,]) { + input.parse::()?; + Some(input.parse::()?) + } else { + None + }; + + Ok(LogInput { error, debug_info }) + } +} + +pub fn log_impl(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LogInput); + + let error_expr = &input.error; + + if let Some(debug_info) = &input.debug_info { + quote! { + { + let error = #error_expr; + let level = error.level(); + let message = error.to_string(); + let debug_info = #debug_info; + + match level { + tracing::Level::ERROR => tracing::error!(message = %message, debug = ?debug_info), + tracing::Level::WARN => tracing::warn!(message = %message, debug = ?debug_info), + tracing::Level::INFO => tracing::info!(message = %message, debug = ?debug_info), + tracing::Level::DEBUG => tracing::debug!(message = %message, debug = ?debug_info), + tracing::Level::TRACE => tracing::trace!(message = %message, debug = ?debug_info), + } + } + } + } else { + quote! { + { + let error = #error_expr; + let level = error.level(); + let message = error.to_string(); + + match level { + tracing::Level::ERROR => tracing::error!("{}", message), + tracing::Level::WARN => tracing::warn!("{}", message), + tracing::Level::INFO => tracing::info!("{}", message), + tracing::Level::DEBUG => tracing::debug!("{}", message), + tracing::Level::TRACE => tracing::trace!("{}", message), + } + } + } + } + .into() +} diff --git a/macros/src/loggable.rs b/macros/src/loggable.rs new file mode 100644 index 0000000..8be0323 --- /dev/null +++ b/macros/src/loggable.rs @@ -0,0 +1,143 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, parse_macro_input, Attribute, Fields, Ident, LitStr, Token, Type, Visibility}; + +struct LoggableVariant { + error_message: LitStr, + name: Ident, + fields: Fields, + level: syn::Expr, +} + +struct LoggableInput { + enum_name: Ident, + variants: Vec, +} + +impl Parse for LoggableInput { + fn parse(input: ParseStream) -> syn::Result { + let enum_name = input.parse::()?; + + let content; + braced!(content in input); + + let mut variants = Vec::new(); + + while !content.is_empty() { + let attrs = content.call(Attribute::parse_outer)?; + let error_attr = attrs + .iter() + .find(|attr| attr.path().is_ident("error")) + .ok_or_else(|| content.error("Expected #[error(...)] attribute"))?; + + let error_message = error_attr.parse_args::()?; + + let name = content.parse::()?; + + let fields = if content.peek(syn::token::Brace) { + let field_content; + braced!(field_content in content); + + let mut named_fields = syn::punctuated::Punctuated::new(); + while !field_content.is_empty() { + let field_name = field_content.parse::()?; + field_content.parse::()?; + let field_type = field_content.parse::()?; + + named_fields.push(syn::Field { + attrs: vec![], + vis: Visibility::Inherited, + mutability: syn::FieldMutability::None, + ident: Some(field_name), + colon_token: Some(Default::default()), + ty: field_type, + }); + + if field_content.peek(Token![,]) { + field_content.parse::()?; + } + } + + Fields::Named(syn::FieldsNamed { + brace_token: Default::default(), + named: named_fields, + }) + } else { + Fields::Unit + }; + + content.parse::]>()?; + let level = content.parse::()?; + + if content.peek(Token![,]) { + content.parse::()?; + } + + variants.push(LoggableVariant { + error_message, + name, + fields, + level, + }); + } + + Ok(LoggableInput { + enum_name, + variants, + }) + } +} + +pub fn loggable_impl(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LoggableInput); + + let enum_name = &input.enum_name; + let variants = &input.variants; + + let enum_variants = variants.iter().map(|variant| { + let name = &variant.name; + let error_attr = &variant.error_message; + let fields = &variant.fields; + + quote! { + #[error(#error_attr)] + #name #fields + } + }); + + let level_match_arms = variants.iter().map(|variant| { + let name = &variant.name; + let level = &variant.level; + let field_pattern = match &variant.fields { + Fields::Unit => quote! {}, + Fields::Named(fields) => { + let field_names = fields.named.iter().map(|f| &f.ident); + quote! { { #(#field_names: _),* } } + } + Fields::Unnamed(_) => quote! { (..) }, + }; + + quote! { + Self::#name #field_pattern => #level + } + }); + + quote! { + #[allow(dead_code)] + #[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)] + pub enum #enum_name { + #(#enum_variants,)* + } + + impl #enum_name { + #[allow(dead_code)] + pub fn level(&self) -> tracing::Level { + match self { + #(#level_match_arms,)* + } + } + } + } + .into() +} diff --git a/macros/src/traceable.rs b/macros/src/traceable.rs new file mode 100644 index 0000000..051d79b --- /dev/null +++ b/macros/src/traceable.rs @@ -0,0 +1,231 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, parse_macro_input, spanned::Spanned, Attribute, Error, Expr, Ident, LitStr, + Result, + Token, + Type, +}; + +struct ErrorVariant { + attributes: Vec, + error_msg: LitStr, + name: Ident, + fields: Vec<(Ident, Type)>, + level: Expr, +} + +impl ErrorVariant { + fn has_no_source(&self) -> bool { + self.attributes + .iter() + .any(|attr| attr.path().is_ident("no_source")) + } + + fn should_generate_constructor(&self) -> bool { + if self.has_no_source() { + !self.fields.is_empty() + } else { + true + } + } +} + +struct TraceableInput { + enum_name: Ident, + variants: Vec, +} + +impl Parse for TraceableInput { + fn parse(input: ParseStream) -> Result { + let enum_name = input.parse::()?; + + let content; + syn::braced!(content in input); + + let mut variants = Vec::new(); + + while !content.is_empty() { + let mut attributes = Vec::new(); + + while content.peek(Token![#]) { + attributes.push(content.call(Attribute::parse_outer)?); + } + + let attributes: Vec<_> = attributes.into_iter().flatten().collect(); + + let error_attr = attributes + .iter() + .find(|attr| attr.path().is_ident("error")) + .ok_or_else(|| Error::new(content.span(), "Missing #[error] attribute"))?; + + let error_msg = match &error_attr.meta { + syn::Meta::List(list) => syn::parse2::(list.tokens.clone())?, + _ => { + return Err(Error::new( + error_attr.span(), + "Invalid error attribute format", + )); + } + }; + + let name = content.parse::()?; + + let mut fields = Vec::new(); + if content.peek(syn::token::Brace) { + let fields_content; + syn::braced!(fields_content in content); + + while !fields_content.is_empty() { + let field_name = fields_content.parse::()?; + fields_content.parse::()?; + let field_type = fields_content.parse::()?; + fields.push((field_name, field_type)); + + if !fields_content.is_empty() { + fields_content.parse::()?; + } + } + } + + content.parse::]>()?; + let level = content.parse::()?; + + if !content.is_empty() { + content.parse::()?; + } + + variants.push(ErrorVariant { + attributes, + error_msg, + name, + fields, + level, + }); + } + + Ok(TraceableInput { + enum_name, + variants, + }) + } +} + +pub fn traceable_impl(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as TraceableInput); + + let enum_name = &input.enum_name; + let variants = &input.variants; + + let enum_variants = variants.iter().map(|variant| { + let name = &variant.name; + let error_msg = &variant.error_msg; + let fields = &variant.fields; + + let field_definitions = fields.iter().map(|(name, ty)| { + quote! { #name: #ty } + }); + + if variant.has_no_source() { + if variant.fields.is_empty() { + quote! { + #[error(#error_msg)] + #name + } + } else { + quote! { + #[error(#error_msg)] + #name { #(#field_definitions,)* } + } + } + } else { + quote! { + #[error(#error_msg)] + #name { + #(#field_definitions,)* + err: String + } + } + } + }); + + let level_match_arms = variants.iter().map(|variant| { + let name = &variant.name; + let level = &variant.level; + + if variant.has_no_source() { + if variant.fields.is_empty() { + quote! { + Self::#name => #level + } + } else { + quote! { + Self::#name { .. } => #level + } + } + } else { + quote! { + Self::#name { err: _, .. } => #level + } + } + }); + + let constructors = variants.iter().filter_map(|variant| { + if !variant.should_generate_constructor() { + return None; + } + + let name = &variant.name; + let fields = &variant.fields; + + let params = fields.iter().map(|(field_name, field_type)| { + quote! { #field_name: impl Into<#field_type> } + }); + + let field_assignments = fields.iter().map(|(field_name, _)| { + quote! { #field_name: #field_name.into() } + }); + + if variant.has_no_source() { + Some(quote! { + #[allow(non_snake_case)] + pub fn #name(#(#params),*) -> Self { + Self::#name { + #(#field_assignments,)* + } + } + }) + } else { + Some(quote! { + #[allow(non_snake_case)] + pub fn #name(#(#params,)* source: impl std::fmt::Display) -> Self { + Self::#name { + #(#field_assignments,)* + err: source.to_string() + } + } + }) + } + }); + + let expanded = quote! { + #[allow(dead_code)] + #[derive(Debug, Clone, thiserror::Error, serde::Serialize, serde::Deserialize)] + pub enum #enum_name { + #(#enum_variants,)* + } + + impl #enum_name { + #[allow(dead_code)] + pub fn level(&self) -> tracing::Level { + match self { + #(#level_match_arms,)* + } + } + + #(#constructors)* + } + }; + + TokenStream::from(expanded) +} diff --git a/src/core/app_config.rs b/src/core/app_config.rs index 6af554a..d842ccf 100644 --- a/src/core/app_config.rs +++ b/src/core/app_config.rs @@ -1,55 +1,37 @@ use crate::model::config::{Config, ConfigTable}; -use crate::utils::log_entry::system::SystemEntry; +use crate::model::error::system::SystemError; +use crate::model::error::Error; +use crate::model::log::system::SystemLog; +use macros::log; use std::fs; -use std::sync::{OnceLock, RwLock as SyncRwLock}; -use tokio::sync::RwLock as AsyncRwLock; -use tracing::info; +use std::ops::Deref; -static SYNC_CONFIG: OnceLock> = OnceLock::new(); -static ASYNC_CONFIG: OnceLock> = OnceLock::new(); - -pub struct AppConfig; +pub struct AppConfig { + config: Config, +} impl AppConfig { - pub async fn initialization() { - info!("{}", SystemEntry::Initializing); - let config = Self::load_config(); - SYNC_CONFIG.get_or_init(|| SyncRwLock::new(config.clone())); - ASYNC_CONFIG.get_or_init(move || AsyncRwLock::new(config)); - info!("{}", SystemEntry::InitializeComplete); + pub fn new() -> Result { + log!(SystemLog::Initializing); + let config = Self::load_config_file()?; + log!(SystemLog::InitializeComplete); + Ok(Self { config }) } - fn load_config() -> Config { - let config = match fs::read_to_string("./config.toml") { - Ok(toml_string) => match toml::from_str::(&toml_string) { - Ok(config_table) => config_table.config, - Err(_) => panic!("{}", SystemEntry::InvalidConfig) - } - Err(_) => panic!("{}", SystemEntry::ConfigNotFound) - }; - config - } - - pub async fn fetch() -> Config { - // Initialization has been ensured - let lock = ASYNC_CONFIG.get().unwrap(); - lock.read().await.clone() - } - - pub fn fetch_blocking() -> Config { - // Initialization has been ensured - let lock = SYNC_CONFIG.get().unwrap(); - // In extreme cases, a serious error occurs in the system - lock.read().unwrap().clone() - } - - pub async fn update(config: Config) { - // Initialization has been ensured - let lock = SYNC_CONFIG.get().unwrap(); - // There is no lock acquired multiple times, so this is safe - *lock.write().unwrap() = config.clone(); - // Initialization has been ensured - let lock = ASYNC_CONFIG.get().unwrap(); - *lock.write().await = config; + fn load_config_file() -> Result { + let toml_string = + fs::read_to_string("./config.toml").map_err(|err| SystemError::ConfigNotFound(err))?; + let config = toml::from_str::(&toml_string) + .map_err(|err| SystemError::InvalidConfig(err))? + .config; + Ok(config) + } +} + +impl Deref for AppConfig { + type Target = Config; + + fn deref(&self) -> &Self::Target { + &self.config } } diff --git a/src/core/backup_engine.rs b/src/core/backup_engine.rs new file mode 100644 index 0000000..b679bef --- /dev/null +++ b/src/core/backup_engine.rs @@ -0,0 +1,689 @@ +use crate::core::app_config::AppConfig; +use crate::core::event_bus::EventBus; +use crate::core::io_manager::IOManager; +use crate::core::progress_tracker::ProgressTracker; +use crate::interface::file_system::FileSystemTrait; +use crate::interface::service_unit::ServiceUnit; +use crate::model::backup::backup_execution::*; +use crate::model::error::Error; +use crate::model::error::system::SystemError; +use crate::model::error::task::TaskError; +use crate::model::event::execution::*; +use async_trait::async_trait; +use crossbeam_queue::SegQueue; +use dashmap::DashMap; +use futures::future::join_all; +use macros::log; +use std::collections::{HashSet, VecDeque}; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::sync::oneshot; +use tokio::task::JoinHandle; +use tracing::error; +use uuid::Uuid; + +pub struct BackupEngine { + config: Arc, + event_bus: Arc, + io_manager: Arc, + progress_tracker: Arc, + executions: Arc>, + running_executions: Arc, JoinHandle<()>)>>, +} + +impl BackupEngine { + pub async fn new( + config: Arc, + event_bus: Arc, + io_manager: Arc, + progress_tracker: Arc, + ) -> Self { + Self { + config, + event_bus, + io_manager, + progress_tracker, + executions: Arc::new(DashMap::new()), + running_executions: Arc::new(DashMap::new()), + } + } + + pub async fn stop_all_executions(&self) { + let keys: Vec = self + .running_executions + .iter() + .map(|pair| pair.key().clone()) + .collect(); + for uuid in keys { + if let Some((_, (shutdown, handle))) = self.running_executions.remove(&uuid) { + if shutdown.send(()).is_err() { + log!(SystemError::ShutdownSignalFailed); + continue; + } + if let Err(err) = handle.await { + log!(SystemError::ThreadPanic(err)); + } + } + } + } + + pub async fn add_execution(&self, execution: BackupExecution) { + self.executions.insert(execution.uuid, execution); + } + + pub async fn remove_execution(&self, uuid: &Uuid) { + self.executions.remove(uuid); + } + + pub async fn start_execution(&self, uuid: Uuid) -> Result<(), Error> { + if self.running_executions.contains_key(&uuid) { + Err(TaskError::IllegalRunState)? + } + + let mut ref_mut = self + .executions + .get_mut(&uuid) + .ok_or(TaskError::ExecutionNotFound)?; + let execution = ref_mut.value_mut(); + if execution.state != BackupState::Pending { + Err(TaskError::IllegalRunState)? + } + execution.state = BackupState::Running; + + let execution_runner = self.to_execution_runner(); + let execution = execution.clone(); + let (tx, rx) = oneshot::channel(); + let handle = tokio::spawn(async move { execution_runner.run(execution, rx, false).await }); + self.running_executions.insert(uuid, (tx, handle)); + Ok(()) + } + + pub async fn suspend_execution(&self, uuid: Uuid) -> Result<(), Error> { + let mut ref_mut = self + .executions + .get_mut(&uuid) + .ok_or(TaskError::ExecutionNotFound)?; + let execution = ref_mut.value_mut(); + if execution.state != BackupState::Running { + Err(TaskError::IllegalRunState)? + } + execution.state = BackupState::Suspended; + drop(ref_mut); + + let (_, (shutdown, handle)) = self + .running_executions + .remove(&uuid) + .ok_or(TaskError::ExecutionNotFound)?; + shutdown + .send(()) + .map_err(|_| SystemError::ShutdownSignalFailed)?; + handle.await.map_err(|err| SystemError::ThreadPanic(err))?; + Ok(()) + } + + pub async fn resume_execution(&self, uuid: Uuid) -> Result<(), Error> { + if self.running_executions.contains_key(&uuid) { + Err(TaskError::IllegalRunState)? + } + + let mut ref_mut = self + .executions + .get_mut(&uuid) + .ok_or(TaskError::ExecutionNotFound)?; + let execution = ref_mut.value_mut(); + if execution.state != BackupState::Suspended { + Err(TaskError::IllegalRunState)? + } + execution.state = BackupState::Running; + + let execution_runner = self.to_execution_runner(); + let execution = execution.clone(); + let (tx, rx) = oneshot::channel(); + let handle = tokio::spawn(async move { execution_runner.run(execution, rx, true).await }); + self.running_executions.insert(uuid, (tx, handle)); + Ok(()) + } + + fn to_execution_runner(&self) -> ExecutionRunner { + let config = self.config.clone(); + let io_manager = self.io_manager.clone(); + let progress_tracker = self.progress_tracker.clone(); + let executions = self.executions.clone(); + let running_executions = self.running_executions.clone(); + ExecutionRunner::new( + config, + io_manager, + progress_tracker, + executions, + running_executions, + ) + } +} + +struct ExecutionRunner { + config: Arc, + io_manager: Arc, + progress_tracker: Arc, + executions: Arc>, + running_executions: Arc, JoinHandle<()>)>>, +} + +impl ExecutionRunner { + pub fn new( + config: Arc, + io_manager: Arc, + progress_tracker: Arc, + executions: Arc>, + running_executions: Arc, JoinHandle<()>)>>, + ) -> Self { + Self { + config, + io_manager, + progress_tracker, + executions, + running_executions, + } + } + + async fn run( + &self, + execution: BackupExecution, + mut shutdown: oneshot::Receiver<()>, + resume: bool, + ) { + let config = &self.config; + let progress_tracker = &self.progress_tracker; + + let (mut current_level, mut errors) = if resume { + progress_tracker.resume_execution(execution.uuid).await + } else { + let source_root = execution.source_path.clone(); + (vec![source_root], Vec::new()) + }; + + let mut shutdown_flag = false; + while !current_level.is_empty() { + let global_queue = Arc::new(SegQueue::new()); + + for dir in current_level.clone() { + global_queue.push(dir); + } + + let mut worker_handles = Vec::new(); + let mut worker_shutdowns = Vec::new(); + + for _ in 0..config.max_concurrency { + let worker = self.to_worker(); + let (tx, rx) = oneshot::channel(); + let execution = execution.clone(); + let queue = global_queue.clone(); + let handle = tokio::spawn(async move { worker.run(execution, queue, rx).await }); + worker_shutdowns.push(tx); + worker_handles.push(handle); + } + + let workers_results = tokio::select! { + results = join_all(&mut worker_handles) => results, + _ = &mut shutdown => { + shutdown_flag = true; + for shutdown in worker_shutdowns { + if shutdown.send(()).is_err() { + log!(SystemError::ShutdownSignalFailed); + } + } + join_all(&mut worker_handles).await + } + }; + + let mut next_level = Vec::new(); + for result in workers_results { + match result { + Ok((worker_next_level, worker_errors)) => { + next_level.extend(worker_next_level); + errors.extend(worker_errors); + } + Err(err) => log!(SystemError::ThreadPanic(err)), + } + } + + if shutdown_flag { + current_level.extend(next_level); + if let Err(err) = progress_tracker + .save_execution(execution.uuid, current_level, errors) + .await { + error!("{}", err); + } + break; + } else { + current_level = next_level; + } + } + + self.running_executions.remove(&execution.uuid); + + match self.executions.get_mut(&execution.uuid) { + Some(mut ref_mut) => { + let execution = ref_mut.value_mut(); + if shutdown_flag { + execution.state = BackupState::Suspended; + } else { + execution.state = BackupState::Completed; + } + } + None => log!(TaskError::ExecutionNotFound), + } + } + + fn to_worker(&self) -> Worker { + let config = self.config.clone(); + let io_manager = self.io_manager.clone(); + let progress_tracker = self.progress_tracker.clone(); + Worker::new(config, io_manager, progress_tracker) + } +} + +struct Worker { + config: Arc, + io_manager: Arc, + progress_tracker: Arc, +} + +impl Worker { + pub fn new( + config: Arc, + io_manager: Arc, + progress_tracker: Arc, + ) -> Self { + Self { + config, + io_manager, + progress_tracker, + } + } + + async fn run( + &self, + execution: BackupExecution, + global_queue: Arc>, + mut shutdown: oneshot::Receiver<()>, + ) -> (Vec, Vec) { + let io_manager = &self.io_manager; + + let mirror = execution.options.mirror; + + let mut next_level = Vec::new(); + let mut errors = Vec::new(); + + while let Some(current_dir) = global_queue.pop() { + if shutdown.try_recv().is_ok() { + break; + } + + let entries = match io_manager.list_directory(¤t_dir).await { + Ok(entries) => entries, + Err(e) => { + errors.push(e); + continue; + } + }; + + for entry in entries.iter() { + if shutdown.try_recv().is_ok() { + break; + } + match self.process_entry(&execution, entry).await { + Ok(Some(path)) => next_level.push(path), + Ok(None) => {} + Err(e) => errors.push(e), + } + } + + if mirror { + let source_entries = entries; + let destination_dir = match self.calculate_destination_path( + ¤t_dir, + &execution.source_path, + &execution.destination_path, + ) { + Ok(dir) => dir, + Err(e) => { + errors.push(e); + continue; + } + }; + match io_manager.list_directory(&destination_dir).await { + Ok(destination_entries) => { + let (_, mirror_errors) = self + .mirror_cleanup(source_entries, destination_entries) + .await; + errors.extend(mirror_errors); + } + Err(e) => errors.push(e), + } + } + } + + (next_level, errors) + } + + async fn process_entry( + &self, + execution: &BackupExecution, + current_path: &PathBuf, + ) -> Result, Error> { + let io_manager = &self.io_manager; + + let source_root = &execution.source_path; + let destination_root = &execution.destination_path; + + let source_path = current_path.clone(); + let destination_path = + self.calculate_destination_path(&source_path, &source_root, &destination_root)?; + + let is_symlink = io_manager.is_symlink(&source_path).await.unwrap_or(false); + + if is_symlink { + self.process_symlink(execution, &source_path, &destination_path) + .await?; + return Ok(None); + } + + if source_path.is_dir() { + self.backup_directory(execution, &source_path, &destination_path) + .await + } else { + self.backup_file(execution, &source_path, &destination_path) + .await + } + } + + async fn backup_directory( + &self, + execution: &BackupExecution, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result, Error> { + let io_manager = &self.io_manager; + + if !destination_path.exists() { + io_manager.create_directory(&destination_path).await?; + } + + io_manager + .copy_attributes(source_path, destination_path) + .await?; + + if execution.options.backup_permission { + io_manager + .copy_permission(source_path, destination_path) + .await?; + } + + Ok(Some(source_path.clone())) + } + + async fn backup_file( + &self, + execution: &BackupExecution, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result, Error> { + let io_manager = &self.io_manager; + + #[allow(unused_variables)] + let mut file_lock = None; + #[allow(unused_assignments)] + if execution.options.lock_source { + file_lock = Some(io_manager.acquire_file_lock(source_path).await?); + } + + match execution.backup_type { + BackupType::Full => self.full_backup(source_path, destination_path).await?, + BackupType::Incremental => { + let comparison_mode = execution.comparison_mode.ok_or(SystemError::UnknownError)?; + self.incremental_backup(source_path, destination_path, comparison_mode) + .await? + } + } + + io_manager + .copy_attributes(source_path, destination_path) + .await?; + + if execution.options.backup_permission { + io_manager + .copy_permission(source_path, destination_path) + .await?; + } + + drop(file_lock); + + Ok(None) + } + + #[inline(always)] + async fn process_symlink( + &self, + execution: &BackupExecution, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result<(), Error> { + if execution.options.follow_symlinks { + self.follow_symlink(execution, source_path, destination_path) + .await + } else { + self.copy_symlink(execution, source_path, destination_path) + .await + } + } + + async fn follow_symlink( + &self, + execution: &BackupExecution, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result<(), Error> { + let io_manager = &self.io_manager; + + let mut queue = VecDeque::new(); + let mut visited = HashSet::new(); + + queue.push_back((source_path.clone(), destination_path.clone())); + + while let Some((current_source, current_dest)) = queue.pop_front() { + let is_symlink = io_manager + .is_symlink(¤t_source) + .await + .unwrap_or(false); + let canonical_path = if is_symlink { + match current_source.canonicalize() { + Ok(path) => path, + Err(_) => continue, + } + } else { + current_source.clone() + }; + + if visited.contains(&canonical_path) { + continue; + } + visited.insert(canonical_path.clone()); + + if canonical_path.is_dir() { + self.backup_directory(execution, &canonical_path, ¤t_dest) + .await?; + + let entries = io_manager.list_directory(&canonical_path).await?; + for entry in entries { + let relative_path = match entry.strip_prefix(&canonical_path) { + Ok(rel_path) => rel_path.to_path_buf(), + Err(_) => match entry.file_name() { + Some(name) => PathBuf::from(name), + None => continue, + }, + }; + let new_destination = current_dest.join(relative_path); + queue.push_back((entry, new_destination)); + } + } else { + self.backup_file(execution, &canonical_path, ¤t_dest) + .await?; + } + } + + Ok(()) + } + + async fn copy_symlink( + &self, + execution: &BackupExecution, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result<(), Error> { + let io_manager = &self.io_manager; + + io_manager + .copy_symlink(source_path, destination_path) + .await?; + + io_manager + .copy_attributes(source_path, destination_path) + .await?; + + if execution.options.backup_permission { + io_manager + .copy_permission(source_path, destination_path) + .await?; + } + + Ok(()) + } + + #[inline(always)] + async fn full_backup( + &self, + source_path: &PathBuf, + destination_path: &PathBuf, + ) -> Result<(), Error> { + let io_manager = &self.io_manager; + io_manager.copy_file(source_path, destination_path).await + } + + async fn incremental_backup( + &self, + source_path: &PathBuf, + destination_path: &PathBuf, + comparison_mode: ComparisonMode, + ) -> Result<(), Error> { + let io_manager = &self.io_manager; + + let need_copy = !match comparison_mode { + ComparisonMode::Standard => { + io_manager + .standard_compare(source_path, destination_path) + .await + } + ComparisonMode::Advanced => { + io_manager + .advance_compare(source_path, destination_path) + .await + } + ComparisonMode::Thorough(hash_type) => { + io_manager + .thorough_compare(source_path, destination_path, hash_type) + .await + } + }?; + + if need_copy { + io_manager.copy_file(source_path, destination_path).await + } else { + Ok(()) + } + } + + async fn mirror_cleanup( + &self, + source_entries: Vec, + destination_entries: Vec, + ) -> ((), Vec) { + let io_manager = &self.io_manager; + + let mut errors = Vec::new(); + + let source_names: HashSet<_> = source_entries + .into_iter() + .filter_map(|path| path.file_name().map(|name| name.to_owned())) + .collect(); + + for dest_entry in destination_entries { + if let Some(file_name) = dest_entry.file_name() { + if !source_names.contains(file_name) { + if dest_entry.is_dir() { + if let Err(e) = io_manager.delete_directory(&dest_entry).await { + errors.push(e); + } + } else { + if let Err(e) = io_manager.delete_file(&dest_entry).await { + errors.push(e); + } + } + } + } + } + + ((), errors) + } + + fn calculate_destination_path( + &self, + source_path: &PathBuf, + source_root: &PathBuf, + destination_root: &PathBuf, + ) -> Result { + let relative_path = source_path + .strip_prefix(source_root) + .map_err(|err| SystemError::UnexpectError(err))?; + Ok(destination_root.join(relative_path)) + } +} + +#[async_trait] +impl ServiceUnit for BackupEngine { + async fn run_impl(self: Arc, mut shutdown_rx: oneshot::Receiver<()>) { + let backup_engine = self.clone(); + let event_bus = self.event_bus.clone(); + let add_execution = event_bus.subscribe::(); + let remove_execution = event_bus.subscribe::(); + let start_execution = event_bus.subscribe::(); + let resume_execution = event_bus.subscribe::(); + let suspend_execution = event_bus.subscribe::(); + loop { + if shutdown_rx.try_recv().is_ok() { + break; + } + + while let Ok(event) = add_execution.try_recv() { + backup_engine.add_execution(event.execution).await; + } + while let Ok(event) = remove_execution.try_recv() { + backup_engine.remove_execution(&event.execution_id).await; + } + while let Ok(event) = start_execution.try_recv() { + if let Err(err) = backup_engine.start_execution(event.execution_id).await { + error!("{}", err); + } + } + while let Ok(event) = resume_execution.try_recv() { + if let Err(err) = backup_engine.resume_execution(event.execution_id).await { + error!("{}", err); + } + } + while let Ok(event) = suspend_execution.try_recv() { + if let Err(err) = backup_engine.suspend_execution(event.execution_id).await { + error!("{}", err); + } + } + } + } +} diff --git a/src/core/database_manager.rs b/src/core/database_manager.rs index 1a90eac..aa26659 100644 --- a/src/core/database_manager.rs +++ b/src/core/database_manager.rs @@ -1,62 +1,65 @@ -use crate::interface::database_ops::DatabaseOpsTrait; +use crate::interface::repository::schedule::ScheduleRepository; +use crate::model::error::Error; +use crate::model::error::database::DatabaseError; +use crate::model::log::database::DatabaseLog; +use crate::model::log::system::SystemLog; use crate::platform::constants::*; -use crate::platform::database_ops::DatabaseOps; -use crate::utils::log_entry::database::DatabaseEntry; -use crate::utils::log_entry::system::SystemEntry; +use macros::log; use sqlx::SqlitePool; -use std::ops::Deref; -use std::sync::OnceLock; -use tracing::{info, trace}; - -static DATABASE_MANAGER: OnceLock = OnceLock::new(); +use tokio::fs; +use tokio::fs::File; #[derive(Debug)] pub struct DatabaseManager { - ops: DatabaseOps, + pool: SqlitePool, } impl DatabaseManager { - pub async fn initialization() { - info!("{}", SystemEntry::Initializing); - DatabaseOps::lock_database().await.unwrap(); - if !DatabaseOps::exist_database().await { - DatabaseOps::create_database().await.unwrap(); + pub async fn new() -> Result { + log!(SystemLog::Initializing); + if !Self::exist_database().await { + Self::create_database().await?; } - let instance = match SqlitePool::connect(DATABASE_URL).await { - Ok(pool) => { - info!("{}", DatabaseEntry::DatabaseConnectSuccess); - DatabaseManager { - ops: DatabaseOps::new(pool), - } - } - Err(err) => { - trace!(?err); - panic!("{}", DatabaseEntry::DatabaseConnectFailed); - } - }; - if !instance.exist_table("BackupTasks").await { - instance.create_backup_task_table().await.unwrap(); + let pool = SqlitePool::connect(DATABASE_URL) + .await + .map_err(|err| DatabaseError::DatabaseConnectFailed(err))?; + log!(DatabaseLog::DatabaseConnectSuccess); + let database_manager = Self { pool }; + if !database_manager.exist_table("BackupSchedules").await { + database_manager.create_backup_schedule_table().await?; } - DATABASE_MANAGER.set(instance).unwrap(); - info!("{}", SystemEntry::InitializeComplete); + log!(SystemLog::InitializeComplete); + Ok(database_manager) } - pub fn instance() -> &'static DatabaseManager { - // Initialization has been ensured - DATABASE_MANAGER.get().unwrap() + pub fn get_pool(&self) -> SqlitePool { + self.pool.clone() } - pub async fn terminate() { - let instance = DatabaseManager::instance(); - instance.close_connection().await; - let _ = DatabaseOps::unlock_database().await; - } -} - -impl Deref for DatabaseManager { - type Target = DatabaseOps; - - fn deref(&self) -> &Self::Target { - &self.ops + pub async fn close_connection(&self) { + let pool = self.get_pool(); + pool.close().await + } + + pub async fn exist_database() -> bool { + fs::metadata(DATABASE_PATH).await.is_ok() + } + + pub async fn create_database() -> Result<(), Error> { + let _ = File::create(DATABASE_PATH) + .await + .map_err(|err| DatabaseError::CreateDatabaseFailed(err))?; + Ok(()) + } + + pub async fn exist_table(&self, table_name: &str) -> bool { + let pool = self.get_pool(); + sqlx::query_scalar::<_, bool>( + "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?)", + ) + .bind(table_name) + .fetch_one(&pool) + .await + .unwrap_or(false) } } diff --git a/src/core/engine.rs b/src/core/engine.rs deleted file mode 100644 index 4c91b6c..0000000 --- a/src/core/engine.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::model::task::BackupTask; -use crate::utils::log_entry::task::TaskEntry; -use dashmap::DashMap; -use std::sync::OnceLock; -use tokio::sync::oneshot; -use tokio::sync::oneshot::{Receiver as OneShotReceiver, Sender as OneShotSender}; -use uuid::Uuid; - -pub static ENGINE: OnceLock = OnceLock::new(); - -#[derive(Debug)] -pub struct Engine { - tasks: DashMap, - shutdown: DashMap>, -} - -impl Engine { - pub async fn initialize() { - let instance = Engine { - tasks: DashMap::new(), - shutdown: DashMap::new(), - }; - ENGINE.set(instance).unwrap(); - } - - pub async fn instance() -> &'static Engine { - ENGINE.get().unwrap() - } - - pub async fn terminate() { - let instance = Self::instance().await; - let keys: Vec = instance.shutdown.iter().map(|pair| pair.key().clone()).collect(); - for uuid in keys { - if let Some((_, sender)) = instance.shutdown.remove(&uuid) { - let _ = sender.send(()); - } - } - } - - pub async fn add_task(task: BackupTask) { - let instance = Self::instance().await; - instance.tasks.insert(task.uuid, task); - } - - pub async fn remove_task(task: &Uuid) { - let instance = Self::instance().await; - instance.tasks.remove(task); - } - - pub async fn start_task(uuid: Uuid) -> anyhow::Result<()> { - let instance = Self::instance().await; - let task = instance - .tasks - .get(&uuid) - .ok_or(TaskEntry::TaskNotFound)? - .value() - .clone(); - let (tx, rx) = oneshot::channel(); - instance.shutdown.insert(uuid, tx); - tokio::spawn(async move { Engine::run_without_resume(task, rx).await }); - Ok(()) - } - - async fn run_without_resume(task: BackupTask, shutdown: OneShotReceiver<()>) { - - } - - pub async fn suspend_task(uuid: Uuid) -> anyhow::Result<()> { - let instance = Self::instance().await; - let (_, channel) = instance - .shutdown - .remove(&uuid) - .ok_or(TaskEntry::TaskNotFound)?; - let _ = channel.send(()); - Ok(()) - } - - pub async fn resume_task(uuid: Uuid, shutdown: OneShotSender<()>) { - - } - - async fn run_with_resume(task: BackupTask) {} -} diff --git a/src/core/event_bus.rs b/src/core/event_bus.rs new file mode 100644 index 0000000..7621e1a --- /dev/null +++ b/src/core/event_bus.rs @@ -0,0 +1,43 @@ +use crate::interface::event::Event; +use dashmap::DashMap; +use std::any::{Any, TypeId}; +use std::sync::mpsc::{channel, Receiver}; + +pub struct EventBus { + channels: DashMap>>, +} + +impl EventBus { + pub fn new() -> Self { + Self { + channels: DashMap::new(), + } + } + + pub fn subscribe(&self) -> Receiver { + let (tx, rx) = channel(); + let type_id = TypeId::of::(); + + let handler = Box::new(move |event: &dyn Any| { + if let Some(typed_event) = event.downcast_ref::() { + let _ = tx.send(typed_event.clone()); + } + }); + + self.channels + .entry(type_id) + .or_insert_with(Vec::new) + .push(handler); + + rx + } + + pub fn publish(&self, event: E) { + let type_id = TypeId::of::(); + if let Some(handlers) = self.channels.get(&type_id) { + for handler in handlers.value() { + handler(&event); + } + } + } +} diff --git a/src/core/event_system/actor_dispatcher.rs b/src/core/event_system/actor_dispatcher.rs deleted file mode 100644 index a41b037..0000000 --- a/src/core/event_system/actor_dispatcher.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::core::event_system::actor_ref::ActorRef; -use crate::interface::event_system::actor::Actor; -use crate::interface::event_system::dispatcher::Dispatcher; -use crate::interface::event_system::event::Event; -use crate::interface::event_system::event_handler::EventHandler; -use async_trait::async_trait; - -pub struct ActorDispatcher { - actor: ActorRef, - handler: Box + Send + Sync + 'static>, -} - -impl ActorDispatcher { - pub fn new(actor: ActorRef, handler: Box + Send + Sync + 'static>) -> Self { - Self { actor, handler } - } -} - -#[async_trait] -impl Dispatcher for ActorDispatcher { - async fn dispatch(&self, event: E) { - let mut actor = self.actor.lock().await; - self.handler.handle(&mut actor, event).await; - } -} diff --git a/src/core/event_system/actor_ref.rs b/src/core/event_system/actor_ref.rs deleted file mode 100644 index 7819f1c..0000000 --- a/src/core/event_system/actor_ref.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::interface::event_system::actor::Actor; -use std::sync::Arc; -use tokio::sync::{Mutex, MutexGuard}; - -pub struct ActorRef(Arc>); - -impl ActorRef { - pub fn new(actor: A) -> Self { - Self(Arc::new(Mutex::new(actor))) - } - - pub async fn lock(&self) -> MutexGuard<'_, A> { - self.0.lock().await - } -} - -impl Clone for ActorRef { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} diff --git a/src/core/event_system/event_bus.rs b/src/core/event_system/event_bus.rs deleted file mode 100644 index f2cd0de..0000000 --- a/src/core/event_system/event_bus.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::core::event_system::actor_ref::ActorRef; -use crate::core::event_system::listener_group::ListenerGroup; -use crate::interface::event_system::actor::Actor; -use crate::interface::event_system::event::Event; -use crate::interface::event_system::event_handler::EventHandler; -use crate::utils::log_entry::system::SystemEntry; -use dashmap::DashMap; -use std::any::{Any, TypeId}; -use std::sync::OnceLock; - -static EVENT_BUS: OnceLock = OnceLock::new(); - -pub struct EventBus { - listeners: DashMap>, -} - -impl EventBus { - fn new() -> Self { - Self { - listeners: DashMap::new(), - } - } - - fn instance() -> &'static EventBus { - &EVENT_BUS.get_or_init(|| EventBus::new()) - } - - pub async fn subscribe( - actor: &ActorRef, - handler: impl EventHandler + Send + Sync + 'static, - ) -> anyhow::Result<()> { - let instance = Self::instance(); - let type_id = TypeId::of::>(); - let mut entry = instance - .listeners - .entry(type_id) - .or_insert_with(|| Box::new(ListenerGroup::::new())); - let listeners = entry - .value_mut() - .downcast_mut::>() - .ok_or(SystemEntry::InternalError)?; - listeners.subscribe(actor.clone(), handler); - Ok(()) - } - - pub async fn publish(event: E) -> anyhow::Result<()> { - let instance = Self::instance(); - let type_id = TypeId::of::>(); - if let Some(listeners) = instance.listeners.get_mut(&type_id) { - let listeners = listeners - .value() - .downcast_ref::>() - .ok_or(SystemEntry::InternalError)?; - listeners.broadcast(event).await; - } - Ok(()) - } -} diff --git a/src/core/event_system/listener_group.rs b/src/core/event_system/listener_group.rs deleted file mode 100644 index 245a344..0000000 --- a/src/core/event_system/listener_group.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::core::event_system::actor_dispatcher::ActorDispatcher; -use crate::core::event_system::actor_ref::ActorRef; -use crate::interface::event_system::actor::Actor; -use crate::interface::event_system::dispatcher::Dispatcher; -use crate::interface::event_system::event::Event; -use crate::interface::event_system::event_handler::EventHandler; -use futures::future; - -pub struct ListenerGroup { - dispatchers: Vec + Send + Sync + 'static>>, -} - -impl ListenerGroup { - pub fn new() -> Self { - ListenerGroup { - dispatchers: Vec::new(), - } - } - - pub fn subscribe( - &mut self, - actor: ActorRef, - handler: impl EventHandler + Send + Sync + 'static, - ) { - let handler = Box::new(handler); - let actor_dispatcher = ActorDispatcher::new(actor, handler); - self.dispatchers.push(Box::new(actor_dispatcher)); - } - - pub async fn broadcast(&self, event: E) { - let futures = self - .dispatchers - .iter() - .map(|dispatcher| dispatcher.dispatch(event.clone())); - future::join_all(futures).await; - } -} diff --git a/src/core/event_system/mod.rs b/src/core/event_system/mod.rs deleted file mode 100644 index d6e1165..0000000 --- a/src/core/event_system/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod actor_dispatcher; -pub mod actor_ref; -pub mod event_bus; -pub mod listener_group; diff --git a/src/core/gui_manager.rs b/src/core/gui_manager.rs new file mode 100644 index 0000000..9125525 --- /dev/null +++ b/src/core/gui_manager.rs @@ -0,0 +1,62 @@ +use crate::core::backup_engine::BackupEngine; +use crate::core::event_bus::EventBus; +use crate::core::schedule_manager::ScheduleManager; +use crate::model::error::Error; +use crate::model::error::misc::MiscError; +use crate::ui::main_page::MainPage; +use eframe::egui; +use std::sync::Arc; +use crate::core::app_config::AppConfig; + +pub struct GuiManager { + config: Arc, + event_bus: Arc, + backup_engine: Arc, + schedule_manager: Arc, +} + +impl GuiManager { + pub fn new( + config: Arc, + event_bus: Arc, + backup_engine: Arc, + schedule_manager: Arc, + ) -> Self { + Self { + config, + event_bus, + backup_engine, + schedule_manager, + } + } + + pub fn start(&self) -> Result<(), Error> { + let config = self.config.clone(); + let event_bus = self.event_bus.clone(); + let backup_engine = self.backup_engine.clone(); + let schedule_manager = self.schedule_manager.clone(); + + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_inner_size([1200.0, 800.0]) + .with_title("MirrorSphere"), + ..Default::default() + }; + + eframe::run_native( + "MirrorSphere", + options, + Box::new(|_| { + Ok(Box::new(MainPage::new( + config, + event_bus, + backup_engine, + schedule_manager, + ))) + }), + ) + .map_err(|err| MiscError::UIPlatformError(err))?; + + Ok(()) + } +} diff --git a/src/core/io_manager.rs b/src/core/io_manager.rs index b6c9745..e5d00bd 100644 --- a/src/core/io_manager.rs +++ b/src/core/io_manager.rs @@ -2,34 +2,24 @@ use crate::core::app_config::AppConfig; use crate::interface::file_system::FileSystemTrait; use crate::platform::file_system::FileSystem; use std::ops::Deref; -use std::sync::{Arc, OnceLock}; +use std::sync::Arc; use tokio::sync::Semaphore; -pub static IO_MANAGER: OnceLock = OnceLock::new(); - pub struct IOManager { file_system: FileSystem, } impl IOManager { - pub async fn initialize() { - let config = AppConfig::fetch().await; + pub fn new(config: Arc) -> Self { let max_file_operations = config.max_file_operations; let semaphore = Arc::new(Semaphore::new(max_file_operations)); - let instance = IOManager { + Self { file_system: FileSystem::new(semaphore), - }; - IO_MANAGER.get_or_init(|| instance); + } } - pub fn instance() -> &'static IOManager { - // Initialization has been ensured - IO_MANAGER.get().unwrap() - } - - pub fn terminate() { - let instance = Self::instance(); - instance.file_system.semaphore().close(); + pub fn terminate(&self) { + self.file_system.semaphore().close(); } } diff --git a/src/core/mod.rs b/src/core/mod.rs index 8951859..170a8d6 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,7 +1,9 @@ pub mod app_config; +pub mod backup_engine; pub mod database_manager; -pub mod engine; -pub mod event_system; +pub mod event_bus; +pub mod gui_manager; pub mod io_manager; pub mod progress_tracker; +pub mod schedule_manager; pub mod system; diff --git a/src/core/progress_tracker.rs b/src/core/progress_tracker.rs index 4c7885d..34f9082 100644 --- a/src/core/progress_tracker.rs +++ b/src/core/progress_tracker.rs @@ -1,7 +1,106 @@ -use crate::interface::event_system::actor::Actor; +use crate::core::io_manager::IOManager; +use crate::interface::file_system::FileSystemTrait; +use crate::model::backup::progress_data::ProgressData; +use crate::model::error::Error; +use crate::model::error::io::IOError; +use crate::model::error::misc::MiscError; +use crate::platform::constants::PROGRESS_SAVE_PATH; +use memmap2::MmapMut; +use std::path::PathBuf; +use std::sync::Arc; +use tokio::fs::OpenOptions; +use uuid::Uuid; -pub struct ProgressTracker {} +pub struct ProgressTracker { + io_manager: Arc, +} -impl ProgressTracker {} +impl ProgressTracker { + pub fn new(io_manager: Arc) -> Self { + Self { io_manager } + } -impl Actor for ProgressTracker {} + pub async fn save_execution( + &self, + execution_uuid: Uuid, + current_level: Vec, + errors: Vec, + ) -> Result<(), Error> { + let progress_data = ProgressData::new(current_level, errors); + + self.write_progress_file(execution_uuid, &progress_data) + .await + } + + pub async fn resume_execution(&self, execution_uuid: Uuid) -> (Vec, Vec) { + match self.read_progress_file(execution_uuid).await { + Ok(progress_data) => (progress_data.current_level, progress_data.errors), + Err(_) => (Vec::new(), Vec::new()), + } + } + + async fn write_progress_file( + &self, + execution_uuid: Uuid, + data: &ProgressData, + ) -> Result<(), Error> { + let saved_path = PathBuf::from(PROGRESS_SAVE_PATH).join(execution_uuid.to_string()); + + if let Some(parent) = saved_path.parent() { + let instance = &self.io_manager; + let parent = parent.to_path_buf(); + instance.create_directory(&parent).await?; + } + + let config = bincode::config::standard(); + let serialized = bincode::serde::encode_to_vec(data, config) + .map_err(|err| MiscError::DeserializeError(err))?; + let data_len = serialized.len(); + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&saved_path) + .await + .map_err(|err| IOError::CreateFileFailed(saved_path.clone(), err))?; + + file.set_len(data_len as u64) + .await + .map_err(|err| IOError::WriteFileFailed(saved_path.clone(), err))?; + + let mut mmap = unsafe { + MmapMut::map_mut(&file) + .map_err(|err| IOError::WriteFileFailed(saved_path.clone(), err))? + }; + mmap[..data_len].copy_from_slice(&serialized); + mmap.flush() + .map_err(|err| IOError::WriteFileFailed(saved_path, err))?; + + Ok(()) + } + + async fn read_progress_file(&self, execution_uuid: Uuid) -> Result { + let saved_path = PathBuf::from(PROGRESS_SAVE_PATH).join(execution_uuid.to_string()); + + if !saved_path.exists() { + Err(IOError::FileDoesNotExist { + path: saved_path.clone(), + })? + } + + let file = tokio::fs::File::open(&saved_path) + .await + .map_err(|err| IOError::ReadFileFailed(saved_path.clone(), err))?; + + let mmap = unsafe { + MmapMut::map_mut(&file).map_err(|err| IOError::ReadFileFailed(saved_path, err))? + }; + + let config = bincode::config::standard(); + let (progress_data, _) = bincode::serde::decode_from_slice(&mmap, config) + .map_err(|err| MiscError::DeserializeError(err))?; + + Ok(progress_data) + } +} diff --git a/src/core/schedule_manager.rs b/src/core/schedule_manager.rs new file mode 100644 index 0000000..d1c53d2 --- /dev/null +++ b/src/core/schedule_manager.rs @@ -0,0 +1,308 @@ +use crate::core::app_config::AppConfig; +use crate::core::database_manager::DatabaseManager; +use crate::core::event_bus::EventBus; +use crate::interface::repository::schedule::ScheduleRepository; +use crate::interface::service_unit::ServiceUnit; +use crate::model::backup::backup_schedule::*; +use crate::model::error::Error; +use crate::model::error::system::SystemError; +use crate::model::error::task::TaskError; +use crate::model::event::execution::*; +use crate::model::event::schedule::*; +use crate::model::log::system::SystemLog; +use async_trait::async_trait; +use chrono::{Duration, Months, Utc}; +use macros::log; +use std::sync::Arc; +use tokio::select; +use tokio::sync::{mpsc, oneshot}; +use tokio::time::sleep; +use tracing::error; +use uuid::Uuid; + +pub struct ScheduleManager { + config: Arc, + event_bus: Arc, + database_manager: Arc, +} + +impl ScheduleManager { + pub fn new( + config: Arc, + event_bus: Arc, + database_manager: Arc, + ) -> Self { + ScheduleManager { + config, + event_bus, + database_manager, + } + } + + pub async fn get_schedule(&self, uuid: Uuid) -> Result, Error> { + self.database_manager.get_backup_schedule(uuid).await + } + + pub async fn get_all_schedules(&self) -> Result, Error> { + self.database_manager.get_all_backup_schedules().await + } + + pub async fn create_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> { + self.database_manager + .create_backup_schedule(&schedule) + .await + } + + pub async fn modify_schedule(&self, schedule: BackupSchedule) -> Result<(), Error> { + self.database_manager + .modify_backup_schedule(&schedule) + .await + } + + pub async fn remove_schedule(&self, uuid: Uuid) -> Result<(), Error> { + self.database_manager.remove_backup_schedule(uuid).await + } + + pub async fn active_schedule(&self, uuid: Uuid) -> Result<(), Error> { + if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? { + schedule.state = ScheduleState::Active; + self.database_manager + .modify_backup_schedule(&schedule) + .await?; + } + Ok(()) + } + + pub async fn pause_schedule(&self, uuid: Uuid) -> Result<(), Error> { + if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? { + schedule.state = ScheduleState::Paused; + self.database_manager + .modify_backup_schedule(&schedule) + .await?; + } + Ok(()) + } + + pub async fn disable_schedule(&self, uuid: Uuid) -> Result<(), Error> { + if let Some(mut schedule) = self.database_manager.get_backup_schedule(uuid).await? { + schedule.state = ScheduleState::Disabled; + self.database_manager + .modify_backup_schedule(&schedule) + .await?; + } + Ok(()) + } + + pub async fn execute_ready_schedule(&self) -> Result<(), Error> { + let event_bus = self.event_bus.clone(); + let database_manager = self.database_manager.clone(); + + let now = Utc::now().naive_utc(); + let mut schedules = self.get_all_schedules().await?; + + for schedule in schedules.iter_mut() { + if schedule.state != ScheduleState::Active { + continue; + } + if let Some(next_run_time) = schedule.next_run_time { + if next_run_time < now { + let execution = schedule.to_execution(); + event_bus.publish(ExecutionAddRequest { execution }); + self.update_next_run_time(schedule); + database_manager.modify_backup_schedule(&schedule).await?; + } + } + } + + Ok(()) + } + + fn update_next_run_time(&self, schedule: &mut BackupSchedule) { + if schedule.next_run_time.is_none() { + return; + } + let now = Utc::now().naive_utc(); + let old_next_run_time = schedule.next_run_time.unwrap(); + let new_next_run_time = match schedule.interval { + ScheduleInterval::Once => None, + ScheduleInterval::Daily => Some(old_next_run_time + Duration::days(1)), + ScheduleInterval::Weekly => Some(old_next_run_time + Duration::days(7)), + ScheduleInterval::Monthly => Some( + old_next_run_time + .checked_add_months(Months::new(1)) + .unwrap_or(old_next_run_time + Duration::days(30)), + ), + }; + schedule.last_run_time = Some(now); + schedule.next_run_time = new_next_run_time; + } +} + +struct ScheduleTimer { + config: Arc, + event_bus: Arc, + schedule_manager: Arc, + shutdown_rx: Option>, + refresh_rx: mpsc::UnboundedReceiver<()>, +} + +impl ScheduleTimer { + pub fn new( + config: Arc, + event_bus: Arc, + schedule_manager: Arc, + shutdown_rx: oneshot::Receiver<()>, + refresh_rx: mpsc::UnboundedReceiver<()>, + ) -> Self { + ScheduleTimer { + config, + event_bus, + schedule_manager, + shutdown_rx: Some(shutdown_rx), + refresh_rx, + } + } + + pub async fn run(mut self) { + match self.shutdown_rx.take() { + Some(mut shutdown_rx) => loop { + let mut sleep_time = match self.calculate_sleep_duration().await { + Ok(Some(duration)) => duration, + Ok(None) => Duration::seconds(self.config.default_wakeup_time), + Err(err) => { + error!("{}", err); + Duration::seconds(self.config.default_wakeup_time) + } + }; + if sleep_time < Duration::seconds(0) { + sleep_time = Duration::seconds(0); + } + select! { + biased; + _ = &mut shutdown_rx => { break; } + _ = self.refresh_rx.recv() => {} + _ = sleep(sleep_time.to_std().unwrap()) => {} + } + }, + None => log!(TaskError::IllegalRunState), + } + } + + async fn calculate_sleep_duration(&self) -> Result, Error> { + let mut next_time = None; + + let schedules = self.schedule_manager.get_all_schedules().await?; + + for schedule in schedules { + if schedule.state != ScheduleState::Active { + continue; + } + if let Some(schedule_next_time) = schedule.next_run_time { + match next_time { + Some(current_time) => { + if schedule_next_time < current_time { + next_time = Some(schedule_next_time); + } + } + None => next_time = Some(schedule_next_time), + } + } + } + if let Some(schedule_next_time) = next_time { + let now = Utc::now().naive_utc(); + let duration = schedule_next_time.signed_duration_since(now); + Ok(Some(Duration::seconds(duration.num_seconds().max(0)))) + } else { + Ok(None) + } + } +} + +#[async_trait] +impl ServiceUnit for ScheduleManager { + async fn run_impl(self: Arc, mut shutdown_rx: oneshot::Receiver<()>) { + let (timer_shutdown_tx, timer_shutdown_rx) = oneshot::channel(); + let (timer_refresh_tx, timer_refresh_rx) = mpsc::unbounded_channel(); + + let schedule_timer = ScheduleTimer::new( + self.config.clone(), + self.event_bus.clone(), + self.clone(), + timer_shutdown_rx, + timer_refresh_rx, + ); + + tokio::spawn(schedule_timer.run()); + + let event_bus = self.event_bus.clone(); + let schedule_manager = self.clone(); + let create_schedule = event_bus.subscribe::(); + let modify_schedule = event_bus.subscribe::(); + let remove_schedule = event_bus.subscribe::(); + let active_schedule = event_bus.subscribe::(); + let pause_schedule = event_bus.subscribe::(); + let disable_schedule = event_bus.subscribe::(); + let sleep_duration = Duration::milliseconds(self.config.internal_timestamp) + .to_std() + .unwrap(); + + loop { + if shutdown_rx.try_recv().is_ok() { + log!(SystemLog::Terminating); + if timer_shutdown_tx.send(()).is_err() { + log!(SystemError::TerminateError( + "Fail send shutdown signal to timer" + )) + } else { + log!(SystemLog::TerminateComplete); + } + break; + } + + let mut need_refresh = false; + + while let Ok(event) = create_schedule.try_recv() { + match schedule_manager.create_schedule(event.schedule).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + while let Ok(event) = modify_schedule.try_recv() { + match schedule_manager.modify_schedule(event.schedule).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + while let Ok(event) = remove_schedule.try_recv() { + match schedule_manager.remove_schedule(event.schedule_id).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + while let Ok(event) = active_schedule.try_recv() { + match schedule_manager.active_schedule(event.schedule_id).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + while let Ok(event) = pause_schedule.try_recv() { + match schedule_manager.pause_schedule(event.schedule_id).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + while let Ok(event) = disable_schedule.try_recv() { + match schedule_manager.disable_schedule(event.schedule_id).await { + Ok(_) => need_refresh = true, + Err(err) => error!("{}", err), + } + } + + if need_refresh { + let _ = timer_refresh_tx.send(()); + } + + sleep(sleep_duration).await; + } + } +} diff --git a/src/core/system.rs b/src/core/system.rs index f77719f..a976997 100644 --- a/src/core/system.rs +++ b/src/core/system.rs @@ -1,40 +1,113 @@ use crate::core::app_config::AppConfig; +use crate::core::backup_engine::BackupEngine; use crate::core::database_manager::DatabaseManager; -use crate::core::engine::Engine; +use crate::core::event_bus::EventBus; +use crate::core::gui_manager::GuiManager; use crate::core::io_manager::IOManager; +use crate::core::progress_tracker::ProgressTracker; +use crate::core::schedule_manager::ScheduleManager; +use crate::interface::service_unit::ServiceUnit; +use crate::model::error::Error; +use crate::model::error::system::SystemError; +use crate::model::log::system::SystemLog; +#[cfg(not(debug_assertions))] use crate::platform::elevate::elevate; -use crate::utils::log_entry::system::SystemEntry; +use crate::utils::database_lock::DatabaseLock; use crate::utils::logging::Logging; +use macros::log; +#[cfg(not(debug_assertions))] use privilege::user::privileged; -use tracing::info; +use std::mem; +#[cfg(not(debug_assertions))] +use std::process; +use std::sync::Arc; +use tokio::sync::oneshot; -pub struct System; +pub struct System { + pub event_bus: Arc, + pub app_config: Arc, + pub io_manager: Arc, + pub progress_tracker: Arc, + pub database_lock: DatabaseLock, + pub database_manager: Arc, + pub gui_manager: Arc, + pub schedule_manager: Arc, + pub backup_engine: Arc, + pub shutdowns: Vec>, +} impl System { - pub async fn initialize() { + pub async fn new() -> Result { Logging::initialize().await; - info!("{}", SystemEntry::Initializing); + log!(SystemLog::Initializing); + #[cfg(not(debug_assertions))] if !privileged() { - info!("{}", SystemEntry::ReRunAsAdmin); - elevate() - .map_err(|_| SystemEntry::RunAsAdminFailed) - .unwrap(); + log!(SystemLog::ReRunAsAdmin); + elevate()?; + process::exit(0); } - AppConfig::initialization().await; - Engine::initialize().await; - IOManager::initialize().await; - DatabaseManager::initialization().await; - info!("{}", SystemEntry::InitializeComplete); + let app_config = Arc::new(AppConfig::new()?); + let event_bus = Arc::new(EventBus::new()); + let io_manager = Arc::new(IOManager::new(app_config.clone())); + let progress_tracker = Arc::new(ProgressTracker::new(io_manager.clone())); + let database_lock = DatabaseLock::acquire().await?; + let database_manager = Arc::new(DatabaseManager::new().await?); + + let schedule_manager = Arc::new(ScheduleManager::new( + app_config.clone(), + event_bus.clone(), + database_manager.clone(), + )); + let backup_engine = Arc::new( + BackupEngine::new( + app_config.clone(), + event_bus.clone(), + io_manager.clone(), + progress_tracker.clone(), + ) + .await, + ); + let gui_manager = Arc::new(GuiManager::new( + app_config.clone(), + event_bus.clone(), + backup_engine.clone(), + schedule_manager.clone(), + )); + log!(SystemLog::InitializeComplete); + Ok(Self { + event_bus, + app_config, + io_manager, + progress_tracker, + database_lock, + database_manager, + gui_manager, + schedule_manager, + backup_engine, + shutdowns: Vec::new(), + }) } - pub async fn run() { - info!("{}", SystemEntry::Online); + pub async fn run(&mut self) -> Result<(), Error> { + let gui_manager = self.gui_manager.clone(); + let schedule_manager_shutdown = self.schedule_manager.clone().run().await; + let backup_engine_shutdown = self.backup_engine.clone().run().await; + self.shutdowns.push(schedule_manager_shutdown); + self.shutdowns.push(backup_engine_shutdown); + gui_manager.start() } - pub async fn terminate() { - info!("{}", SystemEntry::Terminating); - Engine::terminate().await; - DatabaseManager::terminate().await; - info!("{}", SystemEntry::TerminateComplete); + pub async fn terminate(&mut self) { + log!(SystemLog::Terminating); + let shutdowns = mem::take(&mut self.shutdowns); + for shutdown in shutdowns { + if shutdown.send(()).is_err() { + log!(SystemError::ShutdownSignalFailed); + } + } + self.backup_engine.stop_all_executions().await; + self.database_manager.close_connection().await; + self.io_manager.terminate(); + log!(SystemLog::TerminateComplete); } } diff --git a/src/interface/database_ops.rs b/src/interface/database_ops.rs deleted file mode 100644 index e004cc8..0000000 --- a/src/interface/database_ops.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::model::task::BackupTask; -use crate::platform::constants::{DATABASE_LOCK_PATH, DATABASE_PATH}; -use crate::utils::log_entry::database::DatabaseEntry; -use async_trait::async_trait; -use sqlx::SqlitePool; -use tokio::fs; -use tokio::fs::File; -use uuid::Uuid; - -#[async_trait] -pub trait DatabaseOpsTrait { - fn new(pool: SqlitePool) -> Self; - - fn get_pool(&self) -> SqlitePool; - - async fn exist_database() -> bool { - fs::metadata(DATABASE_PATH).await.is_ok() - } - - async fn create_database() -> anyhow::Result<()> { - let _ = File::create(DATABASE_PATH) - .await - .map_err(|_| DatabaseEntry::CreateDatabaseFailed)?; - Ok(()) - } - - async fn lock_database() -> anyhow::Result<()> { - if fs::metadata(DATABASE_LOCK_PATH).await.is_err() { - File::create(&DATABASE_LOCK_PATH) - .await - .map_err(|_| DatabaseEntry::LockDatabaseFailed)?; - Ok(()) - } else { - Err(DatabaseEntry::LockDatabaseFailed)? - } - } - - async fn unlock_database() -> anyhow::Result<()> { - fs::remove_file(&DATABASE_LOCK_PATH) - .await - .map_err(|_| DatabaseEntry::UnlockDatabaseFailed)?; - Ok(()) - } - - async fn close_connection(&self) { - let pool = self.get_pool(); - pool.close().await - } - - async fn exist_table(&self, table_name: &str) -> bool { - let pool = self.get_pool(); - sqlx::query_scalar::<_, bool>( - "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?)", - ) - .bind(table_name) - .fetch_one(&pool) - .await - .unwrap_or(false) - } - - async fn create_backup_task_table(&self) -> anyhow::Result<()> { - let pool = self.get_pool(); - sqlx::query( - r#" - CREATE TABLE BackupTasks ( - uuid BLOB PRIMARY KEY, - source_path TEXT NOT NULL, - destination_path TEXT NOT NULL, - backup_type TEXT NOT NULL, - comparison_mode TEXT NOT NULL, - options TEXT NOT NULL, - schedule INTEGER NOT NULL, - last_run_time INTEGER, - next_run_time INTEGER - ) - "#, - ) - .execute(&pool) - .await?; - Ok(()) - } - - async fn add_backup_task(&self, backup_task: BackupTask) -> anyhow::Result<()> { - let pool = self.get_pool(); - sqlx::query( - r#" - INSERT INTO BackupTasks ( - uuid, - source_path, - destination_path, - backup_type, - comparison_mode, - options, - schedule, - last_run_time, - next_run_time - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - "#, - ) - .bind(backup_task.uuid) - .bind(backup_task.source_path.to_string_lossy().to_string()) - .bind(backup_task.destination_path.to_string_lossy().to_string()) - .bind(serde_json::to_string(&backup_task.backup_type)?) - .bind(serde_json::to_string(&backup_task.comparison_mode)?) - .bind(serde_json::to_string(&backup_task.options)?) - .bind(backup_task.schedule) - .bind( - backup_task - .last_run_time - .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64), - ) - .bind( - backup_task - .next_run_time - .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64), - ) - .execute(&pool) - .await?; - Ok(()) - } - - async fn modify_backup_task(&self, backup_task: BackupTask) -> anyhow::Result<()> { - let pool = self.get_pool(); - sqlx::query( - r#" - UPDATE BackupTasks - SET - source_path = ?, - destination_path = ?, - backup_type = ?, - comparison_mode = ?, - options = ?, - schedule = ?, - last_run_time = ?, - next_run_time = ? - WHERE uuid = ? - "#, - ) - .bind(backup_task.source_path.to_string_lossy().to_string()) - .bind(backup_task.destination_path.to_string_lossy().to_string()) - .bind(serde_json::to_string(&backup_task.backup_type)?) - .bind(serde_json::to_string(&backup_task.comparison_mode)?) - .bind(serde_json::to_string(&backup_task.options)?) - .bind(backup_task.schedule) - .bind( - backup_task - .last_run_time - .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64), - ) - .bind( - backup_task - .next_run_time - .map(|t| t.duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as i64), - ) - .bind(backup_task.uuid) - .execute(&pool) - .await?; - - Ok(()) - } - - async fn remove_backup_task(&self, uuid: Uuid) -> anyhow::Result<()> { - let pool = self.get_pool(); - sqlx::query("DELETE FROM BackupTasks WHERE uuid = ?") - .bind(uuid) - .execute(&pool) - .await?; - Ok(()) - } -} diff --git a/src/interface/event_system/event.rs b/src/interface/event.rs similarity index 100% rename from src/interface/event_system/event.rs rename to src/interface/event.rs diff --git a/src/interface/event_system/actor.rs b/src/interface/event_system/actor.rs deleted file mode 100644 index 10d80f7..0000000 --- a/src/interface/event_system/actor.rs +++ /dev/null @@ -1 +0,0 @@ -pub trait Actor: Send + Sync + 'static {} diff --git a/src/interface/event_system/dispatcher.rs b/src/interface/event_system/dispatcher.rs deleted file mode 100644 index a360d3c..0000000 --- a/src/interface/event_system/dispatcher.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::interface::event_system::event::Event; -use async_trait::async_trait; - -#[async_trait] -pub trait Dispatcher { - async fn dispatch(&self, event: E); -} diff --git a/src/interface/event_system/event_handler.rs b/src/interface/event_system/event_handler.rs deleted file mode 100644 index 384b96e..0000000 --- a/src/interface/event_system/event_handler.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::interface::event_system::actor::Actor; -use crate::interface::event_system::event::Event; -use async_trait::async_trait; -use futures::future::BoxFuture; - -#[async_trait] -pub trait EventHandler { - async fn handle(&self, actor: &mut A, event: E); -} - -#[async_trait] -impl EventHandler for F -where - F: for<'a> Fn(&'a mut A, E) -> BoxFuture<'a, ()> + Send + Sync + 'static, -{ - async fn handle(&self, actor: &mut A, event: E) { - self(actor, event).await - } -} diff --git a/src/interface/event_system/mod.rs b/src/interface/event_system/mod.rs deleted file mode 100644 index 67a4365..0000000 --- a/src/interface/event_system/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod actor; -pub mod dispatcher; -pub mod event; -pub mod event_handler; diff --git a/src/interface/file_system.rs b/src/interface/file_system.rs index 67b8021..737017f 100644 --- a/src/interface/file_system.rs +++ b/src/interface/file_system.rs @@ -1,12 +1,10 @@ -use crate::core::event_system::event_bus::EventBus; -use crate::model::event::io::directory::*; -use crate::model::event::io::file::*; -use crate::model::event::io::hash::*; -use crate::model::task::HashType; +use crate::model::error::io::IOError; +use crate::model::error::system::SystemError; +use crate::model::error::Error; +use crate::model::backup::backup_execution::HashType; use crate::platform::attributes::*; use crate::utils::file_hash::*; -use crate::utils::log_entry::io::IOEntry; -use crate::utils::log_entry::system::SystemEntry; +use crate::utils::file_lock::FileLock; use async_trait::async_trait; use std::path::PathBuf; use std::sync::Arc; @@ -15,7 +13,6 @@ use tokio::sync::Semaphore; use tokio::task::spawn_blocking; use tokio_stream::wrappers::ReadDirStream; use tokio_stream::StreamExt; -use uuid::Uuid; #[async_trait] pub trait FileSystemTrait { @@ -23,207 +20,232 @@ pub trait FileSystemTrait { fn semaphore(&self) -> Arc; - async fn list_directory(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result> { + async fn is_symlink(&self, path: &PathBuf) -> Result { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - let mut result = Vec::new(); - let reader = fs::read_dir(&path) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + let symlink_metadata = tokio::fs::symlink_metadata(path) .await - .map_err(|_| IOEntry::ReadDirectoryFailed)?; + .map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?; + + Ok(symlink_metadata.file_type().is_symlink()) + } + + async fn create_symlink(&self, target: &PathBuf, link_path: &PathBuf) -> Result<(), Error>; + + async fn copy_symlink( + &self, + source_link: &PathBuf, + destination_link: &PathBuf, + ) -> Result<(), Error>; + + async fn list_directory(&self, path: &PathBuf) -> Result, Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|err| IOError::SemaphoreClosed(err))?; + + let mut result = Vec::new(); + let reader = fs::read_dir(path) + .await + .map_err(|err| IOError::ReadDirectoryFailed(path.clone(), err))?; let mut entries = ReadDirStream::new(reader); while let Some(entry) = entries.next().await { - let path = entry.map_err(|_| IOEntry::ReadFileFailed)?.path(); + let path = entry + .map_err(|err| IOError::ReadDirectoryFailed(path.clone(), err))? + .path(); result.push(path); } - let event = ListDirectoryEvent { task_id, path }; - EventBus::publish(event).await?; Ok(result) } - async fn create_directory(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result<()> { + async fn create_directory(&self, path: &PathBuf) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - fs::create_dir_all(&path) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + fs::create_dir_all(path) .await - .map_err(|_| IOEntry::CreateDirectoryFailed)?; - let event = CreateDirectoryEvent { task_id, path }; - EventBus::publish(event).await?; + .map_err(|err| IOError::CreateDirectoryFailed(path.clone(), err))?; Ok(()) } - async fn delete_directory(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result<()> { + async fn delete_directory(&self, path: &PathBuf) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - fs::remove_dir_all(&path) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + fs::remove_dir_all(path) .await - .map_err(|_| IOEntry::DeleteDirectoryFailed)?; - let event = DeleteDirectoryEvent { task_id, path }; - EventBus::publish(event).await?; + .map_err(|err| IOError::DeleteDirectoryFailed(path.clone(), err))?; Ok(()) } - async fn copy_file( - &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, - ) -> anyhow::Result<()> { + async fn copy_file(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - fs::copy(&source, &destination) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + fs::copy(source, destination) .await - .map_err(|_| IOEntry::CopyFileFailed)?; - let event = CopyFileEvent { - task_id, - source, - destination, - }; - EventBus::publish(event).await?; + .map_err(|err| IOError::CopyFileFailed(source.clone(), destination.clone(), err))?; Ok(()) } - async fn delete_file(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result<()> { + async fn delete_file(&self, path: &PathBuf) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - fs::remove_file(&path) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + fs::remove_file(path) .await - .map_err(|_| IOEntry::DeleteFileFailed)?; - let event = DeleteFileEvent { task_id, path }; - EventBus::publish(event).await?; + .map_err(|err| IOError::DeleteFileFailed(path.clone(), err))?; Ok(()) } - async fn get_attributes(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result; + async fn get_attributes(&self, path: &PathBuf) -> Result; - async fn set_attributes( - &self, - task_id: Uuid, - path: PathBuf, - attributes: Attributes, - ) -> anyhow::Result<()>; + async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error>; + + async fn copy_attributes(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> { + let source_attributes = self.get_attributes(source).await?; + self.set_attributes(destination, source_attributes).await?; + Ok(()) + } async fn compare_attributes( &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, - ) -> anyhow::Result; + source: &PathBuf, + destination: &PathBuf, + ) -> Result { + let source_attributes = self.get_attributes(source).await?; + let destination_attributes = self.get_attributes(destination).await?; + Ok(source_attributes == destination_attributes) + } - async fn get_permission(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result; + async fn get_permission(&self, path: &PathBuf) -> Result; - async fn set_permission( - &self, - task_id: Uuid, - path: PathBuf, - permission: Permissions, - ) -> anyhow::Result<()>; + async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error>; - async fn standard_compare( - &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, - ) -> anyhow::Result { - if !self - .compare_attributes(task_id, source.clone(), destination.clone()) - .await? - { - return Ok(false); - } + async fn copy_permission(&self, source: &PathBuf, destination: &PathBuf) -> Result<(), Error> { + let source_permissions = self.get_permission(source).await?; + self.set_permission(destination, source_permissions).await?; + Ok(()) + } + async fn acquire_file_lock(&self, path: &PathBuf) -> Result { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; + .map_err(|err| IOError::SemaphoreClosed(err))?; - let source_metadata = fs::metadata(&source) + let file_lock = FileLock::new(path).await?; + + Ok(file_lock) + } + + async fn calculate_hash(&self, path: &PathBuf, hash_type: HashType) -> Result, Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() .await - .map_err(|_| IOEntry::GetMetadataFailed)?; - let destination_metadata = fs::metadata(&destination) + .map_err(|err| IOError::SemaphoreClosed(err))?; + + let path_clone = path.clone(); + let hash = spawn_blocking(move || { + let path = path_clone; + match hash_type { + HashType::MD5 => md5(path), + HashType::SHA3 => sha3(path), + HashType::SHA256 => sha256(path), + HashType::BLAKE2B => blake2b(path), + HashType::BLAKE2S => blake2s(path), + HashType::BLAKE3 => blake3(path), + } + }) + .await + .map_err(|err| SystemError::ThreadPanic(err))??; + Ok(hash) + } + + async fn standard_compare( + &self, + source: &PathBuf, + destination: &PathBuf, + ) -> Result { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() .await - .map_err(|_| IOEntry::GetMetadataFailed)?; + .map_err(|err| IOError::SemaphoreClosed(err))?; + + let source_metadata = + fs::metadata(source) + .await + .map_err(|err| IOError::GetMetadataFailed(source.clone(), err))?; + let destination_metadata = + fs::metadata(destination) + .await + .map_err(|err| IOError::GetMetadataFailed(destination.clone(), err))?; if source_metadata.len() != destination_metadata.len() { return Ok(false); } - let source_modified = source_metadata - .modified() - .map_err(|_| IOEntry::GetMetadataFailed)?; - let destination_modified = destination_metadata - .modified() - .map_err(|_| IOEntry::GetMetadataFailed)?; + let source_modified = + source_metadata + .modified() + .map_err(|err| IOError::GetMetadataFailed(source.clone(), err))?; + let destination_modified = + destination_metadata + .modified() + .map_err(|err| IOError::GetMetadataFailed(destination.clone(), err))?; if source_modified != destination_modified { return Ok(false); } + Ok(true) + } + + async fn advance_compare( + &self, + source: &PathBuf, + destination: &PathBuf, + ) -> Result { + if !self.standard_compare(source, destination).await? { + return Ok(false); + } + + if !self.compare_attributes(source, destination).await? { + return Ok(false); + } Ok(true) } async fn thorough_compare( &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, + source: &PathBuf, + destination: &PathBuf, hash_type: HashType, - ) -> anyhow::Result { - if !self - .standard_compare(task_id, source.clone(), destination.clone()) - .await? - { + ) -> Result { + if !self.advance_compare(source, destination).await? { return Ok(false); } - let source_file_hash = self.calculate_hash(task_id, source, hash_type).await?; - let destination_file_hash = self.calculate_hash(task_id, destination, hash_type).await?; + let source_file_hash = self.calculate_hash(source, hash_type).await?; + let destination_file_hash = self.calculate_hash(destination, hash_type).await?; Ok(source_file_hash == destination_file_hash) } - - async fn calculate_hash( - &self, - task_id: Uuid, - path: PathBuf, - hash_type: HashType, - ) -> anyhow::Result> { - let semaphore = self.semaphore(); - let _permit = semaphore - .acquire_owned() - .await - .map_err(|_| IOEntry::SemaphoreClosed)?; - - let path_clone = path.clone(); - let hash = spawn_blocking(move || { - let path = path_clone; - let file = std::fs::File::open(path)?; - match hash_type { - HashType::MD5 => md5(file), - HashType::SHA3 => sha3(file), - HashType::SHA256 => sha256(file), - HashType::BLAKE2B => blake2b(file), - HashType::BLAKE2S => blake2s(file), - HashType::BLAKE3 => blake3(file), - } - }) - .await - .map_err(|_| SystemEntry::ThreadPanic)??; - - let event = CalculateHashEvent { task_id, path }; - EventBus::publish(event).await?; - Ok(hash) - } } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 3855a01..39dc947 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -1,3 +1,4 @@ -pub mod database_ops; -pub mod event_system; +pub mod event; pub mod file_system; +pub mod repository; +pub mod service_unit; diff --git a/src/interface/repository/mod.rs b/src/interface/repository/mod.rs new file mode 100644 index 0000000..f70a363 --- /dev/null +++ b/src/interface/repository/mod.rs @@ -0,0 +1 @@ +pub mod schedule; \ No newline at end of file diff --git a/src/interface/repository/schedule.rs b/src/interface/repository/schedule.rs new file mode 100644 index 0000000..6e491e4 --- /dev/null +++ b/src/interface/repository/schedule.rs @@ -0,0 +1,295 @@ +use crate::core::database_manager::DatabaseManager; +use crate::model::backup::backup_schedule::BackupSchedule; +use crate::model::error::Error; +use crate::model::error::database::DatabaseError; +use crate::model::error::misc::MiscError; +use sqlx::Row; +use uuid::Uuid; + +pub trait ScheduleRepository { + async fn create_backup_schedule_table(&self) -> Result<(), Error>; + async fn create_backup_schedule(&self, backup_schedule: &BackupSchedule) -> Result<(), Error>; + async fn modify_backup_schedule(&self, backup_schedule: &BackupSchedule) -> Result<(), Error>; + async fn remove_backup_schedule(&self, uuid: Uuid) -> Result<(), Error>; + async fn get_backup_schedule(&self, uuid: Uuid) -> Result, Error>; + async fn get_all_backup_schedules(&self) -> Result, Error>; +} + +impl ScheduleRepository for DatabaseManager { + async fn create_backup_schedule_table(&self) -> Result<(), Error> { + let pool = self.get_pool(); + sqlx::query( + r#" + CREATE TABLE BackupSchedules ( + uuid BLOB PRIMARY KEY, + name TEXT NOT NULL, + state TEXT NOT NULL, + source_path TEXT NOT NULL, + destination_path TEXT NOT NULL, + backup_type TEXT NOT NULL, + comparison_mode TEXT, + options TEXT NOT NULL, + interval TEXT NOT NULL, + last_run_time TEXT, + next_run_time TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + "#, + ) + .execute(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + Ok(()) + } + + async fn create_backup_schedule(&self, backup_schedule: &BackupSchedule) -> Result<(), Error> { + let pool = self.get_pool(); + sqlx::query( + r#" + INSERT INTO BackupSchedules ( + uuid, + name, + state, + source_path, + destination_path, + backup_type, + comparison_mode, + options, + interval, + last_run_time, + next_run_time, + created_at, + updated_at + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "#, + ) + .bind(backup_schedule.uuid.as_bytes().as_slice()) + .bind(&backup_schedule.name) + .bind( + serde_json::to_string(&backup_schedule.state) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind(backup_schedule.source_path.to_string_lossy().to_string()) + .bind(backup_schedule.destination_path.to_string_lossy().to_string()) + .bind( + serde_json::to_string(&backup_schedule.backup_type) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.comparison_mode) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.options) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.interval) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind(backup_schedule.last_run_time) + .bind(backup_schedule.next_run_time) + .bind(backup_schedule.created_at) + .bind(backup_schedule.updated_at) + .execute(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + Ok(()) + } + + async fn modify_backup_schedule(&self, backup_schedule: &BackupSchedule) -> Result<(), Error> { + let pool = self.get_pool(); + sqlx::query( + r#" + UPDATE BackupSchedules + SET + name = ?, + state = ?, + source_path = ?, + destination_path = ?, + backup_type = ?, + comparison_mode = ?, + options = ?, + interval = ?, + last_run_time = ?, + next_run_time = ?, + created_at = ?, + updated_at = ? + WHERE uuid = ? + "#, + ) + .bind(backup_schedule.uuid.as_bytes().as_slice()) + .bind(&backup_schedule.name) + .bind( + serde_json::to_string(&backup_schedule.state) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind(backup_schedule.source_path.to_string_lossy().to_string()) + .bind(backup_schedule.destination_path.to_string_lossy().to_string()) + .bind( + serde_json::to_string(&backup_schedule.backup_type) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.comparison_mode) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.options) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind( + serde_json::to_string(&backup_schedule.interval) + .map_err(|err| MiscError::SerializeError(err))?, + ) + .bind(backup_schedule.last_run_time) + .bind(backup_schedule.next_run_time) + .bind(backup_schedule.created_at) + .bind(backup_schedule.updated_at) + .execute(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + Ok(()) + } + + async fn remove_backup_schedule(&self, uuid: Uuid) -> Result<(), Error> { + let pool = self.get_pool(); + sqlx::query("DELETE FROM BackupSchedules WHERE uuid = ?") + .bind(uuid.as_bytes().as_slice()) + .execute(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + Ok(()) + } + + async fn get_backup_schedule(&self, uuid: Uuid) -> Result, Error> { + let pool = self.get_pool(); + let row = sqlx::query( + r#" + SELECT + uuid, + name, + state, + source_path, + destination_path, + backup_type, + comparison_mode, + options, + "interval", + last_run_time, + next_run_time, + created_at, + updated_at + FROM BackupSchedules + WHERE uuid = ? + "#, + ) + .bind(uuid.as_bytes().as_slice()) + .fetch_optional(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + + if let Some(row) = row { + let uuid_bytes: Vec = row.get("uuid"); + let uuid = Uuid::from_slice(&uuid_bytes).map_err(|_| DatabaseError::DataCorrupted)?; + + let state_str: String = row.get("state"); + let state = serde_json::from_str(&state_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let backup_type_str: String = row.get("backup_type"); + let backup_type = serde_json::from_str(&backup_type_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let comparison_mode_str: String = row.get("comparison_mode"); + let comparison_mode = serde_json::from_str(&comparison_mode_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let options_str: String = row.get("options"); + let options = serde_json::from_str(&options_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let interval_str: String = row.get("interval"); + let interval = serde_json::from_str(&interval_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + Ok(Some(BackupSchedule { + uuid, + name: row.get("name"), + state, + source_path: row.get::("source_path").into(), + destination_path: row.get::("destination_path").into(), + backup_type, + comparison_mode, + options, + interval, + last_run_time: row.get("last_run_time"), + next_run_time: row.get("next_run_time"), + created_at: row.get("created_at"), + updated_at: row.get("updated_at"), + })) + } else { + Ok(None) + } + } + + async fn get_all_backup_schedules(&self) -> Result, Error> { + let pool = self.get_pool(); + let rows = sqlx::query( + r#" + SELECT uuid, name, state, source_path, destination_path, schedule_type, + last_run_time, next_run_time, created_at, updated_at + FROM BackupSchedules + "#, + ) + .fetch_all(&pool) + .await + .map_err(|err| DatabaseError::StatementExecutionFailed(err))?; + + let mut schedules = Vec::new(); + for row in rows { + let uuid_bytes: Vec = row.get("uuid"); + let uuid = Uuid::from_slice(&uuid_bytes).map_err(|_| DatabaseError::DataCorrupted)?; + + let state_str: String = row.get("state"); + let state = serde_json::from_str(&state_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let backup_type_str: String = row.get("backup_type"); + let backup_type = serde_json::from_str(&backup_type_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let comparison_mode_str: String = row.get("comparison_mode"); + let comparison_mode = serde_json::from_str(&comparison_mode_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let options_str: String = row.get("options"); + let options = serde_json::from_str(&options_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + let interval_str: String = row.get("interval"); + let interval = serde_json::from_str(&interval_str) + .map_err(|err| MiscError::DeserializeError(err))?; + + schedules.push(BackupSchedule { + uuid, + name: row.get("name"), + state, + source_path: row.get::("source_path").into(), + destination_path: row.get::("destination_path").into(), + backup_type, + comparison_mode, + options, + interval, + last_run_time: row.get("last_run_time"), + next_run_time: row.get("next_run_time"), + created_at: row.get("created_at"), + updated_at: row.get("updated_at"), + }); + } + + Ok(schedules) + } +} diff --git a/src/interface/service_unit.rs b/src/interface/service_unit.rs new file mode 100644 index 0000000..5e3ccd7 --- /dev/null +++ b/src/interface/service_unit.rs @@ -0,0 +1,16 @@ +use std::sync::Arc; +use async_trait::async_trait; +use tokio::sync::oneshot; + +#[async_trait] +pub trait ServiceUnit: 'static { + async fn run(self: Arc) -> oneshot::Sender<()> { + let (shutdown_tx, shutdown_rx) = oneshot::channel(); + + tokio::spawn(self.run_impl(shutdown_rx)); + + shutdown_tx + } + + async fn run_impl(self: Arc, shutdown_rx: oneshot::Receiver<()>); +} diff --git a/src/main.rs b/src/main.rs index 5fd765f..2fbcd03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,8 +8,9 @@ mod ui; mod utils; #[tokio::main] -async fn main() { - System::initialize().await; - System::run().await; - System::terminate().await; +async fn main() -> Result<(), Box> { + let mut system = System::new().await?; + system.run().await?; + system.terminate().await; + Ok(()) } diff --git a/src/model/task.rs b/src/model/backup/backup_execution.rs similarity index 70% rename from src/model/task.rs rename to src/model/backup/backup_execution.rs index 5ebb8e9..44d2024 100644 --- a/src/model/task.rs +++ b/src/model/backup/backup_execution.rs @@ -1,13 +1,15 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use std::time::SystemTime; use uuid::Uuid; #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum BackupState { Running, + Pending, Suspended, - Stopped, + Completed, + Failed, + Canceled, } #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] @@ -30,20 +32,22 @@ pub enum HashType { pub enum ComparisonMode { // Compare size and modify time Standard, - // Standard + regular file attr + compare file checksum + // Standard + regular file attr + Advanced, + // Advanced + compare file checksum Thorough(HashType), } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct BackupOptions { - lock_source: bool, - backup_acl: bool, - backup_other_file: bool, - advanced_file_attr: bool, + pub mirror: bool, + pub lock_source: bool, + pub backup_permission: bool, + pub follow_symlinks: bool, } #[derive(Debug, Clone)] -pub struct BackupTask { +pub struct BackupExecution { pub uuid: Uuid, pub state: BackupState, pub source_path: PathBuf, @@ -51,7 +55,4 @@ pub struct BackupTask { pub backup_type: BackupType, pub comparison_mode: Option, pub options: BackupOptions, - pub schedule: bool, - pub last_run_time: Option, - pub next_run_time: Option, } diff --git a/src/model/backup/backup_schedule.rs b/src/model/backup/backup_schedule.rs new file mode 100644 index 0000000..8cce7b2 --- /dev/null +++ b/src/model/backup/backup_schedule.rs @@ -0,0 +1,55 @@ +use crate::model::backup::backup_execution::*; +use chrono::NaiveDateTime; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use uuid::Uuid; + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScheduleState { + Active, + Paused, + Disabled, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScheduleInterval { + Once, + Daily, + Weekly, + Monthly, +} + +#[derive(Debug, Clone)] +pub struct BackupSchedule { + pub uuid: Uuid, + pub name: String, + pub state: ScheduleState, + pub source_path: PathBuf, + pub destination_path: PathBuf, + pub backup_type: BackupType, + pub comparison_mode: Option, + pub options: BackupOptions, + pub interval: ScheduleInterval, + pub last_run_time: Option, + pub next_run_time: Option, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +impl BackupSchedule { + pub fn to_execution(&self) -> BackupExecution { + BackupExecution { + uuid: self.uuid, + state: BackupState::Pending, + source_path: self.source_path.clone(), + destination_path: self.destination_path.clone(), + backup_type: if self.last_run_time.is_some() { + self.backup_type + } else { + BackupType::Full + }, + comparison_mode: self.comparison_mode, + options: self.options, + } + } +} diff --git a/src/model/backup/mod.rs b/src/model/backup/mod.rs new file mode 100644 index 0000000..300bc30 --- /dev/null +++ b/src/model/backup/mod.rs @@ -0,0 +1,3 @@ +pub mod backup_execution; +pub mod backup_schedule; +pub mod progress_data; \ No newline at end of file diff --git a/src/model/backup/progress_data.rs b/src/model/backup/progress_data.rs new file mode 100644 index 0000000..b17c08e --- /dev/null +++ b/src/model/backup/progress_data.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use crate::model::error::Error; + +#[derive(Serialize, Deserialize, Debug)] +pub struct ProgressData { + pub current_level: Vec, + pub errors: Vec, +} + +impl ProgressData { + pub fn new(current_level: Vec, errors: Vec) -> ProgressData { + ProgressData { + current_level, + errors + } + } +} diff --git a/src/model/config.rs b/src/model/config.rs index f41b303..d6c8837 100644 --- a/src/model/config.rs +++ b/src/model/config.rs @@ -8,6 +8,8 @@ pub struct ConfigTable { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Config { + pub internal_timestamp: i64, // mini second + pub default_wakeup_time: i64, // second pub max_concurrency: u8, // number pub max_file_operations: usize, // number } diff --git a/src/model/error/database.rs b/src/model/error/database.rs new file mode 100644 index 0000000..ead3aa1 --- /dev/null +++ b/src/model/error/database.rs @@ -0,0 +1,24 @@ +use macros::traceable; + +traceable! { + DatabaseError { + #[error("Failed to create database")] + CreateDatabaseFailed => tracing::Level::ERROR, + + #[error("Failed to connect to database")] + DatabaseConnectFailed => tracing::Level::ERROR, + + #[error("Failed to lock database")] + LockDatabaseFailed => tracing::Level::ERROR, + + #[error("Failed to unlock database")] + UnlockDatabaseFailed => tracing::Level::ERROR, + + #[error("Failed to execute SQL statement")] + StatementExecutionFailed => tracing::Level::ERROR, + + #[no_source] + #[error("Data is corrupted or in invalid format")] + DataCorrupted => tracing::Level::ERROR, + } +} diff --git a/src/model/error/event.rs b/src/model/error/event.rs new file mode 100644 index 0000000..11535ce --- /dev/null +++ b/src/model/error/event.rs @@ -0,0 +1,8 @@ +use macros::traceable; + +traceable! { + EventError { + #[error("Placeholder")] + Placeholder => tracing::Level::INFO, + } +} diff --git a/src/model/error/io.rs b/src/model/error/io.rs new file mode 100644 index 0000000..0b03790 --- /dev/null +++ b/src/model/error/io.rs @@ -0,0 +1,55 @@ +use macros::traceable; +use std::path::PathBuf; + +traceable! { + IOError { + #[error("Semaphore has been closed")] + SemaphoreClosed => tracing::Level::ERROR, + + #[error("Failed to read symbol link: {path}")] + ReadSymbolLinkFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to create symbol link: From {src} To {dst}")] + CreateSymbolLinkFailed { src: PathBuf, dst: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to create directory: {path}")] + CreateDirectoryFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to read directory: {path}")] + ReadDirectoryFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to create file: {path}")] + CreateFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to read file: {path}")] + ReadFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to copy file: From {src} To {dst}")] + CopyFileFailed { src: PathBuf, dst: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to write file: {path}")] + WriteFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to delete directory: {path}")] + DeleteDirectoryFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to delete file: {path}")] + DeleteFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to get file metadata: {path}")] + GetMetadataFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to set file metadata: {path}")] + SetMetadataFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to lock file: {path}")] + LockFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[error("Failed to unlock file: {path}")] + UnlockFileFailed { path: PathBuf } => tracing::Level::ERROR, + + #[no_source] + #[error("File does not exist: {path}")] + FileDoesNotExist { path: PathBuf } => tracing::Level::ERROR, + } +} diff --git a/src/model/error/misc.rs b/src/model/error/misc.rs new file mode 100644 index 0000000..14ac5b6 --- /dev/null +++ b/src/model/error/misc.rs @@ -0,0 +1,17 @@ +use macros::traceable; + +traceable! { + MiscError { + #[error("Failed to free object")] + ObjectFreeFailed => tracing::Level::ERROR, + + #[error("Failed to serialize object")] + SerializeError => tracing::Level::ERROR, + + #[error("Failed to deserialize object")] + DeserializeError => tracing::Level::ERROR, + + #[error("Failed to initialize UI platform")] + UIPlatformError => tracing::Level::ERROR, + } +} diff --git a/src/model/error/mod.rs b/src/model/error/mod.rs new file mode 100644 index 0000000..bcc5088 --- /dev/null +++ b/src/model/error/mod.rs @@ -0,0 +1,66 @@ +pub mod database; +pub mod event; +pub mod io; +pub mod misc; +pub mod system; +pub mod task; + +use crate::model::error::database::DatabaseError; +use crate::model::error::event::EventError; +use crate::model::error::io::IOError; +use crate::model::error::misc::MiscError; +use crate::model::error::system::SystemError; +use crate::model::error::task::TaskError; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, thiserror::Error, Serialize, Deserialize, Clone)] +pub enum Error { + #[error("{0}")] + Database(DatabaseError), + #[error("{0}")] + Event(EventError), + #[error("{0}")] + IO(IOError), + #[error("{0}")] + Misc(MiscError), + #[error("{0}")] + System(SystemError), + #[error("{0}")] + Task(TaskError), +} + +impl From for Error { + fn from(error: DatabaseError) -> Self { + Self::Database(error) + } +} + +impl From for Error { + fn from(error: EventError) -> Self { + Self::Event(error) + } +} + +impl From for Error { + fn from(error: IOError) -> Self { + Self::IO(error) + } +} + +impl From for Error { + fn from(error: MiscError) -> Self { + Self::Misc(error) + } +} + +impl From for Error { + fn from(error: SystemError) -> Self { + Self::System(error) + } +} + +impl From for Error { + fn from(error: TaskError) -> Self { + Self::Task(error) + } +} diff --git a/src/model/error/system.rs b/src/model/error/system.rs new file mode 100644 index 0000000..b728a11 --- /dev/null +++ b/src/model/error/system.rs @@ -0,0 +1,35 @@ +use macros::traceable; + +traceable! { + SystemError { + #[no_source] + #[error("Unable to run as administrator")] + RunAsAdminFailed => tracing::Level::ERROR, + + #[error("Failed to adjust token privileges")] + AdjustTokenPrivilegesFailed => tracing::Level::ERROR, + + #[error("Invalid configuration")] + InvalidConfig => tracing::Level::ERROR, + + #[error("Configuration not found")] + ConfigNotFound => tracing::Level::ERROR, + + #[error("Failed to terminate instance")] + TerminateError => tracing::Level::ERROR, + + #[no_source] + #[error("Failed to send shutdown signal")] + ShutdownSignalFailed => tracing::Level::ERROR, + + #[error("Unexcepted thread panic")] + ThreadPanic => tracing::Level::ERROR, + + #[error("Unexcepted error")] + UnexpectError => tracing::Level::ERROR, + + #[no_source] + #[error("Unknown error")] + UnknownError => tracing::Level::ERROR, + } +} diff --git a/src/model/error/task.rs b/src/model/error/task.rs new file mode 100644 index 0000000..7d06ef6 --- /dev/null +++ b/src/model/error/task.rs @@ -0,0 +1,16 @@ +use macros::traceable; + +traceable! { + TaskError { + #[no_source] + #[error("Illegal run state")] + IllegalRunState => tracing::Level::ERROR, + + #[no_source] + #[error("Task not found")] + ExecutionNotFound => tracing::Level::ERROR, + + #[error("Failed to stop task")] + StopExecutionFailed => tracing::Level::ERROR, + } +} diff --git a/src/model/event/error.rs b/src/model/event/error.rs new file mode 100644 index 0000000..31981e7 --- /dev/null +++ b/src/model/event/error.rs @@ -0,0 +1,12 @@ +use crate::interface::event::Event; +use crate::model::error::Error; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct BackupError { + pub task_id: Uuid, + pub error: Error, +} +impl Event for BackupError {} + +//todo Need add global error event diff --git a/src/model/event/execution.rs b/src/model/event/execution.rs new file mode 100644 index 0000000..75ee1d8 --- /dev/null +++ b/src/model/event/execution.rs @@ -0,0 +1,48 @@ +use crate::interface::event::Event; +use crate::model::backup::backup_execution::{BackupExecution, BackupState}; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct ExecutionAddRequest { + pub execution: BackupExecution, +} +impl Event for ExecutionAddRequest {} + +#[derive(Clone, Debug)] +pub struct ExecutionRemoveRequest { + pub execution_id: Uuid, +} +impl Event for ExecutionRemoveRequest {} + +#[derive(Clone, Debug)] +pub struct ExecutionStartRequest { + pub execution_id: Uuid, +} +impl Event for ExecutionStartRequest {} + +#[derive(Clone, Debug)] +pub struct ExecutionSuspendRequest { + pub execution_id: Uuid, +} +impl Event for ExecutionSuspendRequest {} + +#[derive(Clone, Debug)] +pub struct ExecutionResumeRequested { + pub execution_id: Uuid, +} +impl Event for ExecutionResumeRequested {} + +#[derive(Clone, Debug)] +pub struct ExecutionStateChanged { + pub execution_id: Uuid, + pub new_state: BackupState, +} +impl Event for ExecutionStateChanged {} + +#[derive(Clone, Debug)] +pub struct ExecutionProgress { + pub task_id: Uuid, + pub processed_files: usize, + pub error_count: usize, +} +impl Event for ExecutionProgress {} diff --git a/src/model/event/filesystem.rs b/src/model/event/filesystem.rs new file mode 100644 index 0000000..06bab99 --- /dev/null +++ b/src/model/event/filesystem.rs @@ -0,0 +1,10 @@ +use crate::interface::event::Event; +use std::path::PathBuf; +use uuid::Uuid; + +#[derive(Clone, Debug)] +pub struct FolderProcessing { + pub execution_id: Uuid, + pub current_folder: PathBuf, +} +impl Event for FolderProcessing {} diff --git a/src/model/event/io/attributes.rs b/src/model/event/io/attributes.rs deleted file mode 100644 index c91899e..0000000 --- a/src/model/event/io/attributes.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::interface::event_system::event::Event; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Clone)] -pub struct GetAttributesEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for GetAttributesEvent {} - -#[derive(Clone)] -pub struct SetAttributesEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for SetAttributesEvent {} diff --git a/src/model/event/io/directory.rs b/src/model/event/io/directory.rs deleted file mode 100644 index 21a4a12..0000000 --- a/src/model/event/io/directory.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::interface::event_system::event::Event; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Clone)] -pub struct ListDirectoryEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for ListDirectoryEvent {} - -#[derive(Clone)] -pub struct CreateDirectoryEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for CreateDirectoryEvent {} - -#[derive(Clone)] -pub struct DeleteDirectoryEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for DeleteDirectoryEvent {} diff --git a/src/model/event/io/file.rs b/src/model/event/io/file.rs deleted file mode 100644 index 4e971cb..0000000 --- a/src/model/event/io/file.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::interface::event_system::event::Event; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Clone)] -pub struct CopyFileEvent { - pub task_id: Uuid, - pub source: PathBuf, - pub destination: PathBuf, -} - -impl Event for CopyFileEvent {} - -#[derive(Clone)] -pub struct DeleteFileEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for DeleteFileEvent {} diff --git a/src/model/event/io/hash.rs b/src/model/event/io/hash.rs deleted file mode 100644 index 2aee068..0000000 --- a/src/model/event/io/hash.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::interface::event_system::event::Event; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Clone)] -pub struct CalculateHashEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for CalculateHashEvent {} diff --git a/src/model/event/io/mod.rs b/src/model/event/io/mod.rs deleted file mode 100644 index aacd0cc..0000000 --- a/src/model/event/io/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod attributes; -pub mod directory; -pub mod file; -pub mod hash; -pub mod permission; diff --git a/src/model/event/io/permission.rs b/src/model/event/io/permission.rs deleted file mode 100644 index 58b1254..0000000 --- a/src/model/event/io/permission.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::interface::event_system::event::Event; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Clone)] -pub struct GetPermissionEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for GetPermissionEvent {} - -#[derive(Clone)] -pub struct SetPermissionEvent { - pub task_id: Uuid, - pub path: PathBuf, -} - -impl Event for SetPermissionEvent {} diff --git a/src/model/event/mod.rs b/src/model/event/mod.rs index e96e9de..c5df988 100644 --- a/src/model/event/mod.rs +++ b/src/model/event/mod.rs @@ -1,3 +1,4 @@ -pub mod io; -pub mod progress; -pub mod task; +pub mod error; +pub mod execution; +pub mod filesystem; +pub mod schedule; diff --git a/src/model/event/progress/checkpoint.rs b/src/model/event/progress/checkpoint.rs deleted file mode 100644 index 827137a..0000000 --- a/src/model/event/progress/checkpoint.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::interface::event_system::event::Event; - -#[derive(Clone)] -pub struct SaveCheckPointEvent {} - -impl Event for SaveCheckPointEvent {} diff --git a/src/model/event/progress/mod.rs b/src/model/event/progress/mod.rs deleted file mode 100644 index 2f51277..0000000 --- a/src/model/event/progress/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod checkpoint; -pub mod task; \ No newline at end of file diff --git a/src/model/event/progress/task.rs b/src/model/event/progress/task.rs deleted file mode 100644 index 6175e9a..0000000 --- a/src/model/event/progress/task.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::interface::event_system::event::Event; - -#[derive(Clone)] -pub struct UpdateProgressEvent {} - -impl Event for UpdateProgressEvent {} diff --git a/src/model/event/schedule.rs b/src/model/event/schedule.rs new file mode 100644 index 0000000..2b1cec6 --- /dev/null +++ b/src/model/event/schedule.rs @@ -0,0 +1,39 @@ +use uuid::Uuid; +use crate::interface::event::Event; +use crate::model::backup::backup_schedule::BackupSchedule; + +#[derive(Clone, Debug)] +pub struct ScheduleCreateRequest { + pub schedule: BackupSchedule, +} +impl Event for ScheduleCreateRequest {} + +#[derive(Clone, Debug)] +pub struct ScheduleModifyRequest { + pub schedule: BackupSchedule, +} +impl Event for ScheduleModifyRequest {} + +#[derive(Clone, Debug)] +pub struct ScheduleRemoveRequest { + pub schedule_id: Uuid, +} +impl Event for ScheduleRemoveRequest {} + +#[derive(Clone, Debug)] +pub struct ScheduleActiveRequest { + pub schedule_id: Uuid, +} +impl Event for ScheduleActiveRequest {} + +#[derive(Clone, Debug)] +pub struct SchedulePauseRequest { + pub schedule_id: Uuid, +} +impl Event for SchedulePauseRequest {} + +#[derive(Clone, Debug)] +pub struct ScheduleDisableRequest { + pub schedule_id: Uuid, +} +impl Event for ScheduleDisableRequest {} diff --git a/src/model/event/task.rs b/src/model/event/task.rs deleted file mode 100644 index b69501a..0000000 --- a/src/model/event/task.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::interface::event_system::event::Event; - -#[derive(Clone)] -pub struct TaskCreateEvent {} - -impl Event for TaskCreateEvent {} - -#[derive(Clone)] -pub struct TaskStartEvent {} - -impl Event for TaskStartEvent {} - -#[derive(Clone)] -pub struct TaskPauseEvent {} - -impl Event for TaskPauseEvent {} - -#[derive(Clone)] -pub struct TaskResumeEvent {} - -impl Event for TaskResumeEvent {} - -#[derive(Clone)] -pub struct TaskCompleteEvent {} -impl Event for TaskCompleteEvent {} - -#[derive(Clone)] -pub struct TaskCancelEvent {} - -impl Event for TaskCancelEvent {} - -#[derive(Clone)] -pub struct TaskFailEvent {} - -impl Event for TaskFailEvent {} diff --git a/src/model/log/database.rs b/src/model/log/database.rs new file mode 100644 index 0000000..ebe1d28 --- /dev/null +++ b/src/model/log/database.rs @@ -0,0 +1,8 @@ +use macros::loggable; + +loggable! { + DatabaseLog { + #[error("Connected to database successfully")] + DatabaseConnectSuccess => tracing::Level::INFO, + } +} diff --git a/src/model/log/event.rs b/src/model/log/event.rs new file mode 100644 index 0000000..ca39f66 --- /dev/null +++ b/src/model/log/event.rs @@ -0,0 +1,8 @@ +use macros::loggable; + +loggable! { + EventLog { + #[error("Placeholder")] + Placeholder => tracing::Level::INFO, + } +} diff --git a/src/model/log/io.rs b/src/model/log/io.rs new file mode 100644 index 0000000..328afca --- /dev/null +++ b/src/model/log/io.rs @@ -0,0 +1,8 @@ +use macros::loggable; + +loggable! { + IOLog { + #[error("Placeholder")] + Placeholder => tracing::Level::INFO, + } +} diff --git a/src/model/log/misc.rs b/src/model/log/misc.rs new file mode 100644 index 0000000..f19d1cd --- /dev/null +++ b/src/model/log/misc.rs @@ -0,0 +1,8 @@ +use macros::loggable; + +loggable! { + MiscLog { + #[error("Placeholder")] + Placeholder => tracing::Level::INFO, + } +} diff --git a/src/model/log/mod.rs b/src/model/log/mod.rs new file mode 100644 index 0000000..0bc2529 --- /dev/null +++ b/src/model/log/mod.rs @@ -0,0 +1,6 @@ +pub mod database; +pub mod event; +pub mod io; +pub mod misc; +pub mod system; +pub mod task; diff --git a/src/model/log/system.rs b/src/model/log/system.rs new file mode 100644 index 0000000..4d19cec --- /dev/null +++ b/src/model/log/system.rs @@ -0,0 +1,23 @@ +use macros::loggable; + +loggable! { + SystemLog { + #[error("Rerun the program as administrator")] + ReRunAsAdmin => tracing::Level::WARN, + + #[error("Initializing")] + Initializing => tracing::Level::INFO, + + #[error("Initialization completed")] + InitializeComplete => tracing::Level::INFO, + + #[error("Termination in process")] + Terminating => tracing::Level::INFO, + + #[error("Termination completed")] + TerminateComplete => tracing::Level::INFO, + + #[error("Gui Exited")] + GuiExited => tracing::Level::INFO, + } +} diff --git a/src/model/log/task.rs b/src/model/log/task.rs new file mode 100644 index 0000000..3515a11 --- /dev/null +++ b/src/model/log/task.rs @@ -0,0 +1,8 @@ +use macros::loggable; + +loggable! { + TaskLog { + #[error("Placeholder")] + Placeholder => tracing::Level::INFO, + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index b074d8b..f585d7c 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,3 +1,5 @@ -pub mod task; +pub mod backup; pub mod config; +pub mod error; pub mod event; +pub mod log; diff --git a/src/platform/linux/attributes.rs b/src/platform/linux/attributes.rs index 1aee95c..6453f63 100644 --- a/src/platform/linux/attributes.rs +++ b/src/platform/linux/attributes.rs @@ -1,14 +1,45 @@ -#[derive(Clone, PartialEq, Eq)] -pub struct Attributes { +use libc::{gid_t, mode_t, uid_t}; +use std::time::SystemTime; +#[derive(Debug, Clone, PartialEq)] +pub struct Attributes { + pub attributes: u32, + pub creation_time: SystemTime, + pub last_access_time: SystemTime, + pub change_time: SystemTime, } -impl PartialEq for Attributes { - fn eq(&self, _: &Attributes) -> bool { - true +#[derive(Debug, Clone, PartialEq)] +pub struct Permissions { + pub uid: uid_t, + pub gid: gid_t, + pub mode: u32, + pub is_sticky: bool, + pub is_setuid: bool, + pub is_setgid: bool, +} + +impl Permissions { + pub fn new(uid: uid_t, gid: gid_t, mode: mode_t) -> Self { + Self { + uid, + gid, + mode: mode as u32, + is_sticky: (mode & libc::S_ISVTX as mode_t) != 0, + is_setuid: (mode & libc::S_ISUID as mode_t) != 0, + is_setgid: (mode & libc::S_ISGID as mode_t) != 0, + } } } -pub struct Permissions { - +#[derive(Debug, Clone, PartialEq)] +pub struct ExtendedAttribute { + pub name: String, + pub value: Vec, +} + +impl ExtendedAttribute { + pub fn new(name: String, value: Vec) -> Self { + Self { name, value } + } } diff --git a/src/platform/linux/constants.rs b/src/platform/linux/constants.rs index ff8e9b5..ad8a1db 100644 --- a/src/platform/linux/constants.rs +++ b/src/platform/linux/constants.rs @@ -1,3 +1,4 @@ pub const DATABASE_PATH: &'static str = "./mirrorSphere.db"; pub const DATABASE_URL: &'static str = "sqlite://./mirrorSphere.db"; pub const DATABASE_LOCK_PATH: &'static str = "./db.lock"; +pub const PROGRESS_SAVE_PATH: &'static str = "./progress"; diff --git a/src/platform/linux/database_ops.rs b/src/platform/linux/database_ops.rs deleted file mode 100644 index 5ecf30c..0000000 --- a/src/platform/linux/database_ops.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::interface::database_ops::DatabaseOpsTrait; -use async_trait::async_trait; -use sqlx::SqlitePool; - -#[derive(Clone, Debug)] -pub struct DatabaseOps { - pool: SqlitePool, -} - -#[async_trait] -impl DatabaseOpsTrait for DatabaseOps { - fn new(pool: SqlitePool) -> Self { - DatabaseOps { pool } - } - - fn get_pool(&self) -> SqlitePool { - self.pool.clone() - } -} diff --git a/src/platform/linux/file_system.rs b/src/platform/linux/file_system.rs index 5e480f2..b1d3005 100644 --- a/src/platform/linux/file_system.rs +++ b/src/platform/linux/file_system.rs @@ -1,9 +1,17 @@ +use crate::core::event_bus::event_bus::EventBus; use crate::interface::file_system::FileSystemTrait; +use crate::model::error::io::IOError; +use crate::model::error::system::SystemError; use crate::platform::attributes::{Attributes, Permissions}; use async_trait::async_trait; +use libc::mode_t; +use std::ffi::CString; +use std::os::unix::fs::MetadataExt; use std::path::PathBuf; use std::sync::Arc; +use std::time::SystemTime; use tokio::sync::Semaphore; +use tokio::task::spawn_blocking; use uuid::Uuid; pub struct FileSystem { @@ -11,48 +19,231 @@ pub struct FileSystem { } #[async_trait] -#[cfg(target_os = "linux")] impl FileSystemTrait for FileSystem { fn new(semaphore: Arc) -> Self { - FileSystem { semaphore } + Self { semaphore } } fn semaphore(&self) -> Arc { self.semaphore.clone() } - async fn get_attributes(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result { - todo!() + async fn create_symlink(&self, target: &PathBuf, link_path: &PathBuf) -> Result<(), Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + tokio::fs::symlink(target, link_path).await.map_err(|_| { + IOError::CreateSymbolLinkFailed { + src: target.clone(), + dst: link_path.clone(), + } + })?; + + Ok(()) } - async fn set_attributes( + async fn copy_symlink( &self, - task_id: Uuid, - path: PathBuf, - attributes: Attributes, - ) -> anyhow::Result<()> { - todo!() + source_link: &PathBuf, + destination_link: &PathBuf, + ) -> Result<(), Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + tokio::fs::symlink(&link_target, destination_link) + .await + .map_err(|_| IOError::CreateSymbolLinkFailed { + src: source_link.clone(), + dst: destination_link.clone(), + })?; } - async fn compare_attributes( - &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, - ) -> anyhow::Result { - todo!() + async fn get_attributes(&self, path: &PathBuf) -> Result { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + let metadata = tokio::fs::metadata(path) + .await + .map_err(|_| IOError::GetMetadataFailed)?; + + let mode = metadata.mode(); + let file_type = metadata.file_type(); + + let mut attributes = 0_u32; + + if file_type.is_dir() { + attributes |= libc::S_IFDIR; + } else if file_type.is_file() { + attributes |= libc::S_IFREG; + } else if file_type.is_symlink() { + attributes |= libc::S_IFLNK; + } + + attributes |= mode & 0o777; + + let creation_time = metadata + .created() + .unwrap_or_else(|_| metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH)); + let last_access_time = metadata + .accessed() + .map_err(|_| IOError::GetMetadataFailed)?; + let change_time = metadata + .modified() + .map_err(|_| IOError::GetMetadataFailed)?; + + let attributes = Attributes { + attributes, + creation_time, + last_access_time, + change_time, + }; + + Ok(attributes) } - async fn get_permission(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result { - todo!() + async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + let path_clone = path.clone(); + let mode = attributes.attributes & 0o7777; + + spawn_blocking(move || { + let path = path_clone; + let c_path = CString::new(path.to_string_lossy().as_bytes()) + .map_err(|_| IOError::SetMetadataFailed)?; + + unsafe { + if libc::chmod(c_path.as_ptr(), mode as mode_t) != 0 { + Err(IOError::SetMetadataFailed)?; + } + } + + Self::set_file_times(&path, &attributes)?; + + Ok::<(), Error>(()) + }) + .await + .map_err(|_| SystemError::ThreadPanic)??; + + Ok(()) } - async fn set_permission( - &self, - task_id: Uuid, - path: PathBuf, - permission: Permissions, - ) -> anyhow::Result<()> { - todo!() + async fn get_permission(&self, path: &PathBuf) -> Result { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + let path_clone = path.clone(); + + let permission = spawn_blocking(move || { + let path = path_clone; + let metadata = std::fs::metadata(&path).map_err(|_| IOError::GetMetadataFailed)?; + + let uid = metadata.uid(); + let gid = metadata.gid(); + let mode = metadata.mode(); + + Ok::(Permissions { + uid, + gid, + mode, + is_sticky: (mode & libc::S_ISVTX as u32) != 0, + is_setuid: (mode & libc::S_ISUID as u32) != 0, + is_setgid: (mode & libc::S_ISGID as u32) != 0, + }) + }) + .await + .map_err(|_| SystemError::ThreadPanic)??; + + Ok(permission) + } + + async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|_| IOError::SemaphoreClosed)?; + + let path_clone = path.clone(); + + spawn_blocking(move || { + let path = path_clone; + let c_path = CString::new(path.to_string_lossy().as_bytes()) + .map_err(|_| IOError::SetMetadataFailed)?; + + unsafe { + if libc::chown(c_path.as_ptr(), permissions.uid, permissions.gid) != 0 { + return Err(IOError::SetMetadataFailed.into()); + } + + let mut mode = permissions.mode & 0o7777; + if permissions.is_sticky { + mode |= libc::S_ISVTX as u32; + } + if permissions.is_setuid { + mode |= libc::S_ISUID as u32; + } + if permissions.is_setgid { + mode |= libc::S_ISGID as u32; + } + + if libc::chmod(c_path.as_ptr(), mode as mode_t) != 0 { + return Err(IOError::SetMetadataFailed.into()); + } + } + + Ok::<(), Error>(()) + }) + .await + .map_err(|_| SystemError::ThreadPanic)??; + + Ok(()) + } +} + +impl FileSystem { + fn set_file_times(path: &PathBuf, attributes: &Attributes) -> Result<(), Error> { + let c_path = CString::new(path.to_string_lossy().as_bytes()) + .map_err(|_| IOError::SetMetadataFailed)?; + + let access_time = Self::system_time_to_timespec(attributes.last_access_time)?; + let modify_time = Self::system_time_to_timespec(attributes.change_time)?; + + let times = [access_time, modify_time]; + + unsafe { + if libc::utimensat(libc::AT_FDCWD, c_path.as_ptr(), times.as_ptr(), 0) != 0 { + return Err(IOError::SetMetadataFailed.into()); + } + } + + Ok(()) + } + + fn system_time_to_timespec(system_time: SystemTime) -> Result { + let duration = system_time + .duration_since(SystemTime::UNIX_EPOCH) + .map_err(|_| SystemError::UnexpectError)?; + + Ok(libc::timespec { + tv_sec: duration.as_secs() as libc::time_t, + tv_nsec: duration.subsec_nanos() as libc::c_long, + }) } } diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 30d5e94..b3d6698 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -1,5 +1,4 @@ pub mod attributes; pub mod constants; -pub mod database_ops; pub mod elevate; pub mod file_system; diff --git a/src/platform/windows/attributes.rs b/src/platform/windows/attributes.rs index 03cc427..65055fc 100644 --- a/src/platform/windows/attributes.rs +++ b/src/platform/windows/attributes.rs @@ -1,14 +1,10 @@ +use crate::platform::raii_guard::SecurityDescriptorGuard; use std::time::SystemTime; use windows::Win32::Security::{ACL, PSID}; -use crate::platform::raii_guard::SecurityDescriptorGuard; #[derive(Clone, Eq)] pub struct Attributes { - pub read_only: bool, - pub hidden: bool, - pub archive: bool, - pub normal: bool, - pub index: bool, + pub attributes: u32, pub creation_time: SystemTime, pub last_access_time: SystemTime, pub change_time: SystemTime, @@ -16,13 +12,7 @@ pub struct Attributes { impl PartialEq for Attributes { fn eq(&self, other: &Self) -> bool { - self.read_only == other.read_only - && self.hidden == other.hidden - && self.archive == other.archive - && self.normal == other.normal - && self.index == other.index - && self.creation_time == other.creation_time - && self.last_access_time == other.last_access_time + self.attributes == other.attributes } } diff --git a/src/platform/windows/constants.rs b/src/platform/windows/constants.rs index e42fe93..2e4b9ec 100644 --- a/src/platform/windows/constants.rs +++ b/src/platform/windows/constants.rs @@ -1,3 +1,4 @@ pub const DATABASE_PATH: &'static str = ".\\mirrorSphere.db"; pub const DATABASE_URL: &'static str = "sqlite://.\\mirrorSphere.db"; pub const DATABASE_LOCK_PATH: &'static str = ".\\db.lock"; +pub const PROGRESS_SAVE_PATH: &'static str = ".\\progress"; diff --git a/src/platform/windows/database_ops.rs b/src/platform/windows/database_ops.rs deleted file mode 100644 index 88b42ee..0000000 --- a/src/platform/windows/database_ops.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::interface::database_ops::DatabaseOpsTrait; -use sqlx::SqlitePool; - -#[derive(Clone, Debug)] -pub struct DatabaseOps { - pool: SqlitePool, -} - -impl DatabaseOpsTrait for DatabaseOps { - fn new(pool: SqlitePool) -> Self { - DatabaseOps { pool } - } - - fn get_pool(&self) -> SqlitePool { - self.pool.clone() - } -} diff --git a/src/platform/windows/elevate.rs b/src/platform/windows/elevate.rs index 428257b..6f34ae1 100644 --- a/src/platform/windows/elevate.rs +++ b/src/platform/windows/elevate.rs @@ -1,9 +1,11 @@ -use crate::utils::log_entry::system::SystemEntry; +use crate::model::error::misc::MiscError; +use crate::model::error::system::SystemError; +use crate::model::error::Error; use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; use std::{env, mem}; use windows::core::PCWSTR; -use windows::Win32::Foundation::{CloseHandle, GetLastError, HANDLE, LUID}; +use windows::Win32::Foundation::{CloseHandle, HANDLE, LUID}; use windows::Win32::Security::{ AdjustTokenPrivileges, LookupPrivilegeValueW, LUID_AND_ATTRIBUTES, SE_PRIVILEGE_ENABLED, SE_SECURITY_NAME, TOKEN_ADJUST_PRIVILEGES, TOKEN_PRIVILEGES, TOKEN_QUERY, @@ -15,8 +17,9 @@ use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken}; use windows::Win32::UI::Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW}; use windows::Win32::UI::WindowsAndMessaging::SW_NORMAL; -pub fn elevate() -> anyhow::Result<()> { - let exe = env::current_exe()?; +pub fn elevate() -> Result<(), Error> { + let exe = env::current_exe() + .map_err(|err| SystemError::UnexpectError(err))?; let args: Vec = env::args().skip(1).collect(); let params = args.join(" "); @@ -36,69 +39,73 @@ pub fn elevate() -> anyhow::Result<()> { } } -unsafe fn win_runas(cmd: Vec, args: Vec) -> anyhow::Result<()> { - let mut sei: SHELLEXECUTEINFOW = mem::zeroed(); - let verb = "runas\0".encode_utf16().collect::>(); +unsafe fn win_runas(cmd: Vec, args: Vec) -> Result<(), Error> { + unsafe { + let mut sei: SHELLEXECUTEINFOW = mem::zeroed(); + let verb = "runas\0".encode_utf16().collect::>(); - if CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE).is_err() { - Err(SystemEntry::RunAsAdminFailed)? + if CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE).is_err() { + Err(SystemError::RunAsAdminFailed)? + } + + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.cbSize = size_of::() as u32; + sei.lpVerb = PCWSTR(verb.as_ptr()); + sei.lpFile = PCWSTR(cmd.as_ptr()); + sei.lpParameters = PCWSTR(args.as_ptr()); + sei.nShow = SW_NORMAL.0; + + if ShellExecuteExW(&mut sei).is_err() || sei.hProcess.is_invalid() { + Err(SystemError::RunAsAdminFailed)? + } + + Ok(()) } - - sei.fMask = SEE_MASK_NOCLOSEPROCESS; - sei.cbSize = size_of::() as u32; - sei.lpVerb = PCWSTR(verb.as_ptr()); - sei.lpFile = PCWSTR(cmd.as_ptr()); - sei.lpParameters = PCWSTR(args.as_ptr()); - sei.nShow = SW_NORMAL.0; - - if ShellExecuteExW(&mut sei).is_err() || sei.hProcess.is_invalid() { - Err(SystemEntry::RunAsAdminFailed)? - } - - Ok(()) } -unsafe fn adjust_token_privileges() -> anyhow::Result<()> { - let mut token_handle: HANDLE = HANDLE::default(); +unsafe fn adjust_token_privileges() -> Result<(), Error> { + unsafe { + let mut token_handle: HANDLE = HANDLE::default(); - OpenProcessToken( - GetCurrentProcess(), - TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, - &mut token_handle, - ) - .map_err(|_| SystemEntry::AdjustTokenPrivilegesFailed)?; + OpenProcessToken( + GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + &mut token_handle, + ) + .map_err(|err| SystemError::AdjustTokenPrivilegesFailed(err))?; - let mut luid = LUID { - LowPart: 0, - HighPart: 0, - }; + let mut luid = LUID { + LowPart: 0, + HighPart: 0, + }; - LookupPrivilegeValueW(PCWSTR::null(), SE_SECURITY_NAME, &mut luid) - .map_err(|_| SystemEntry::AdjustTokenPrivilegesFailed)?; + LookupPrivilegeValueW(PCWSTR::null(), SE_SECURITY_NAME, &mut luid) + .map_err(|err| SystemError::AdjustTokenPrivilegesFailed(err))?; - let mut token_privilege = TOKEN_PRIVILEGES { - PrivilegeCount: 1, - Privileges: [LUID_AND_ATTRIBUTES { - Luid: luid, - Attributes: SE_PRIVILEGE_ENABLED, - }], - }; + let mut token_privilege = TOKEN_PRIVILEGES { + PrivilegeCount: 1, + Privileges: [LUID_AND_ATTRIBUTES { + Luid: luid, + Attributes: SE_PRIVILEGE_ENABLED, + }], + }; - AdjustTokenPrivileges( - token_handle, - false, - Some(&mut token_privilege), - 0, - None, - None, - ) - .map_err(|_| SystemEntry::AdjustTokenPrivilegesFailed)?; + AdjustTokenPrivileges( + token_handle, + false, + Some(&mut token_privilege), + 0, + None, + None, + ) + .map_err(|err| SystemError::AdjustTokenPrivilegesFailed(err))?; - CloseHandle(token_handle).map_err(|_| SystemEntry::AdjustTokenPrivilegesFailed)?; + CloseHandle(token_handle).map_err(|err| MiscError::ObjectFreeFailed(err))?; - if GetLastError().is_err() { - Err(SystemEntry::AdjustTokenPrivilegesFailed)? + // if GetLastError().is_err() { + // Err(SystemError::AdjustTokenPrivilegesFailed)? + // } + + Ok(()) } - - Ok(()) } diff --git a/src/platform/windows/file_system.rs b/src/platform/windows/file_system.rs index 7b98481..c67d83b 100644 --- a/src/platform/windows/file_system.rs +++ b/src/platform/windows/file_system.rs @@ -1,11 +1,10 @@ -use crate::core::event_system::event_bus::EventBus; use crate::interface::file_system::FileSystemTrait; -use crate::model::event::io::attributes::{GetAttributesEvent, SetAttributesEvent}; -use crate::model::event::io::permission::GetPermissionEvent; +use crate::model::error::io::IOError; +use crate::model::error::misc::MiscError; +use crate::model::error::system::SystemError; +use crate::model::error::Error; use crate::platform::attributes::{Attributes, Permissions}; use crate::platform::raii_guard::SecurityDescriptorGuard; -use crate::utils::log_entry::io::IOEntry; -use crate::utils::log_entry::system::SystemEntry; use async_trait::async_trait; use chrono::{DateTime, Datelike, Timelike}; use std::os::windows::ffi::OsStrExt; @@ -16,7 +15,6 @@ use std::sync::Arc; use std::time::SystemTime; use tokio::sync::Semaphore; use tokio::task::spawn_blocking; -use uuid::Uuid; use windows::core::PCWSTR; use windows::Win32::Foundation::{CloseHandle, FILETIME, GENERIC_ALL, SYSTEMTIME}; use windows::Win32::Security::Authorization::{ @@ -24,104 +22,119 @@ use windows::Win32::Security::Authorization::{ }; use windows::Win32::Security::{ACL, BACKUP_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSID}; use windows::Win32::Storage::FileSystem::{ - CreateFileW, SetFileAttributesW, SetFileTime, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_HIDDEN, - FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_READONLY, - FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + CreateFileW, SetFileAttributesW, SetFileTime, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_DELETE, + FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }; use windows::Win32::System::Time::SystemTimeToFileTime; -#[cfg(target_os = "windows")] pub struct FileSystem { semaphore: Arc, } #[async_trait] -#[cfg(target_os = "windows")] impl FileSystemTrait for FileSystem { fn new(semaphore: Arc) -> Self { - FileSystem { semaphore } + Self { semaphore } } fn semaphore(&self) -> Arc { self.semaphore.clone() } - async fn get_attributes(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result { + async fn create_symlink(&self, target: &PathBuf, link_path: &PathBuf) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; + .map_err(|err| IOError::SemaphoreClosed(err))?; - let metadata = tokio::fs::metadata(&path) + if link_path.is_dir() { + tokio::fs::symlink_dir(target, link_path).await + } else { + tokio::fs::symlink_file(target, link_path).await + } + .map_err(|err| IOError::CreateSymbolLinkFailed(target.clone(), link_path.clone(), err))?; + + Ok(()) + } + + async fn copy_symlink( + &self, + source_link: &PathBuf, + destination_link: &PathBuf, + ) -> Result<(), Error> { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() .await - .map_err(|_| IOEntry::GetMetadataFailed)?; + .map_err(|err| IOError::SemaphoreClosed(err))?; - let (read_only, hidden, archive, normal, index) = { - let attributes = metadata.file_attributes(); - ( - (attributes & FILE_ATTRIBUTE_READONLY.0) != 0, - (attributes & FILE_ATTRIBUTE_HIDDEN.0) != 0, - (attributes & FILE_ATTRIBUTE_ARCHIVE.0) != 0, - (attributes & FILE_ATTRIBUTE_NORMAL.0) != 0, - (attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED.0) != 0, - ) - }; + let link_target = + tokio::fs::read_link(source_link) + .await + .map_err(|err| IOError::ReadSymbolLinkFailed(source_link.clone(), err))?; + + if link_target.is_dir() { + tokio::fs::symlink_dir(&link_target, destination_link).await + } else { + tokio::fs::symlink_file(&link_target, destination_link).await + } + .map_err(|err| IOError::CreateSymbolLinkFailed(link_target, destination_link, err))?; + + Ok(()) + } + + async fn get_attributes(&self, path: &PathBuf) -> Result { + let semaphore = self.semaphore(); + let _permit = semaphore + .acquire_owned() + .await + .map_err(|err| IOError::SemaphoreClosed(err))?; + + let metadata = tokio::fs::metadata(path) + .await + .map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?; + + let attributes = metadata.file_attributes(); let creation_time = metadata .created() - .map_err(|_| IOEntry::GetMetadataFailed)?; + .map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?; let last_access_time = metadata .accessed() - .map_err(|_| IOEntry::GetMetadataFailed)?; + .map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?; let change_time = metadata .modified() - .map_err(|_| IOEntry::GetMetadataFailed)?; + .map_err(|err| IOError::GetMetadataFailed(path.clone(), err))?; let attributes = Attributes { - read_only, - hidden, - archive, - normal, - index, + attributes, creation_time, last_access_time, change_time, }; - let event = GetAttributesEvent { task_id, path }; - EventBus::publish(event).await?; - Ok(attributes) } - async fn set_attributes( - &self, - task_id: Uuid, - path: PathBuf, - attributes: Attributes, - ) -> anyhow::Result<()> { + async fn set_attributes(&self, path: &PathBuf, attributes: Attributes) -> Result<(), Error> { let semaphore = self.semaphore(); let _permit = semaphore .acquire_owned() .await - .map_err(|_| IOEntry::SemaphoreClosed)?; + .map_err(|err| IOError::SemaphoreClosed(err))?; let file_path_wild: Vec = path.as_os_str().encode_wide().chain(Some(0)).collect(); - let mut file_attributes: u32 = 0; - file_attributes |= FILE_ATTRIBUTE_READONLY.0; - file_attributes |= FILE_ATTRIBUTE_HIDDEN.0; - file_attributes |= FILE_ATTRIBUTE_ARCHIVE.0; - file_attributes |= FILE_ATTRIBUTE_NORMAL.0; - file_attributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED.0; + let file_attributes = attributes.attributes; + let path = path.clone(); spawn_blocking(move || unsafe { SetFileAttributesW( PCWSTR(file_path_wild.as_ptr()), FILE_FLAGS_AND_ATTRIBUTES(file_attributes), ) - .map_err(|_| IOEntry::SetMetadataFailed)?; + .map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?; let handle = CreateFileW( PCWSTR(file_path_wild.as_ptr()), @@ -132,7 +145,7 @@ impl FileSystemTrait for FileSystem { FILE_FLAGS_AND_ATTRIBUTES(file_attributes), None, ) - .map_err(|_| IOEntry::SetMetadataFailed)?; + .map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?; let creation_filetime = Self::system_time_to_file_time(attributes.creation_time)?; let last_access_filetime = Self::system_time_to_file_time(attributes.last_access_time)?; @@ -145,36 +158,22 @@ impl FileSystemTrait for FileSystem { Some(&change_filetime), ); - CloseHandle(handle) - .map_err(|_| SystemEntry::ObjectFreeFailed)?; + CloseHandle(handle).map_err(|err| MiscError::ObjectFreeFailed(err))?; - result - .map_err(|_| IOEntry::SetMetadataFailed)?; - - Ok::<(), anyhow::Error>(()) + result.map_err(|err| IOError::SetMetadataFailed(path.clone(), err))?; + + Ok::<(), Error>(()) }) .await - .map_err(|_| SystemEntry::ThreadPanic)??; + .map_err(|err| SystemError::ThreadPanic(err))??; - let event = SetAttributesEvent { task_id, path }; - EventBus::publish(event).await?; Ok(()) } - async fn compare_attributes( - &self, - task_id: Uuid, - source: PathBuf, - destination: PathBuf, - ) -> anyhow::Result { - let source_attributes = self.get_attributes(task_id, source).await?; - let destination_attributes = self.get_attributes(task_id, destination).await?; - Ok(source_attributes == destination_attributes) - } - - async fn get_permission(&self, task_id: Uuid, path: PathBuf) -> anyhow::Result { + async fn get_permission(&self, path: &PathBuf) -> Result { let file_path_wild: Vec = path.as_os_str().encode_wide().chain(Some(0)).collect(); + let path = path.clone(); let permission = spawn_blocking(move || unsafe { let security_info = BACKUP_SECURITY_INFORMATION; let mut owner = PSID::default(); @@ -183,7 +182,7 @@ impl FileSystemTrait for FileSystem { let mut sacl: *mut ACL = ptr::null_mut(); let mut security_descriptor = PSECURITY_DESCRIPTOR::default(); - if GetNamedSecurityInfoW( + let result = GetNamedSecurityInfoW( PCWSTR(file_path_wild.as_ptr()), SE_FILE_OBJECT, security_info, @@ -192,13 +191,13 @@ impl FileSystemTrait for FileSystem { Some(&mut dacl), Some(&mut sacl), &mut security_descriptor, - ) - .is_err() - { - Err(IOEntry::GetMetadataFailed)?; + ); + + if result.is_err() { + Err(IOError::GetMetadataFailed(path.clone(), format!("{:?}", result)))?; } - Ok::(Permissions { + Ok::(Permissions { owner, primary_group, dacl, @@ -207,19 +206,12 @@ impl FileSystemTrait for FileSystem { }) }) .await - .map_err(|_| SystemEntry::ThreadPanic)??; + .map_err(|err| SystemError::ThreadPanic(err))??; - let event = GetPermissionEvent { task_id, path }; - EventBus::publish(event).await?; Ok(permission) } - async fn set_permission( - &self, - task_id: Uuid, - path: PathBuf, - permissions: Permissions, - ) -> anyhow::Result<()> { + async fn set_permission(&self, path: &PathBuf, permissions: Permissions) -> Result<(), Error> { let file_path_wild: Vec = path.as_os_str().encode_wide().chain(Some(0)).collect(); let security_info = BACKUP_SECURITY_INFORMATION; @@ -230,7 +222,7 @@ impl FileSystemTrait for FileSystem { let security_descriptor = permissions.security_descriptor; unsafe { - if SetNamedSecurityInfoW( + let result = SetNamedSecurityInfoW( PCWSTR(file_path_wild.as_ptr()), SE_FILE_OBJECT, security_info, @@ -238,29 +230,27 @@ impl FileSystemTrait for FileSystem { Some(primary_group), Some(dacl), Some(sacl), - ) - .is_err() - { - Err(IOEntry::SetMetadataFailed)?; + ); + + if result.is_err() { + Err(IOError::SetMetadataFailed(path.clone(), format!("{:?}", result)))?; } } drop(security_descriptor); - let event = SetAttributesEvent { task_id, path }; - EventBus::publish(event).await?; Ok(()) } } impl FileSystem { - fn system_time_to_file_time(system_time: SystemTime) -> anyhow::Result { + fn system_time_to_file_time(system_time: SystemTime) -> Result { let duration = system_time .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|_| SystemEntry::InternalError)?; + .map_err(|err| SystemError::UnexpectError(err))?; let epoch = DateTime::from_timestamp(duration.as_secs() as i64, duration.subsec_nanos()) - .ok_or_else(|| SystemEntry::InternalError)?; + .ok_or(SystemError::UnknownError)?; let sys_time = SYSTEMTIME { wYear: epoch.year() as u16, @@ -277,7 +267,7 @@ impl FileSystem { unsafe { SystemTimeToFileTime(&sys_time, &mut file_time) - .map_err(|_| SystemEntry::InternalError)?; + .map_err(|err| SystemError::UnexpectError(err))?; Ok(file_time) } } diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index c03489d..ea83136 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1,6 +1,5 @@ pub mod attributes; pub mod constants; -pub mod database_ops; pub mod elevate; pub mod file_system; pub mod raii_guard; diff --git a/src/ui/execution_page.rs b/src/ui/execution_page.rs new file mode 100644 index 0000000..20b2788 --- /dev/null +++ b/src/ui/execution_page.rs @@ -0,0 +1,454 @@ +use crate::core::backup_engine::BackupEngine; +use crate::core::event_bus::EventBus; +use crate::model::backup::backup_execution::*; +use crate::model::error::Error; +use crate::model::event::error::BackupError; +use crate::model::event::execution::*; +use crate::model::event::filesystem::FolderProcessing; +use dashmap::DashMap; +use eframe::egui; +use egui_file_dialog::FileDialog; +use futures::executor::block_on; +use std::path::PathBuf; +use std::sync::Arc; +use std::sync::mpsc::Receiver; +use uuid::Uuid; +use crate::core::app_config::AppConfig; + +#[derive(Debug, Clone)] +struct ExecutionDisplay { + execution: BackupExecution, + current_folder: String, + processed_files: usize, + error_count: usize, +} + +impl From for ExecutionDisplay { + fn from(execution: BackupExecution) -> Self { + Self { + execution, + current_folder: String::new(), + processed_files: 0, + error_count: 0, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +enum FolderSelectionMode { + Source, + Destination, +} + +pub struct ExecutionPage { + config: Arc, + backup_engine: Arc, + + folder_processing_events: Receiver, + progress_events: Receiver, + backup_error_events: Receiver, + + executions: DashMap, + error_messages: DashMap>, + + new_task_source: String, + new_task_destination: String, + new_task_mirror: bool, + new_task_lock_source: bool, + new_task_backup_permission: bool, + new_task_follow_symlinks: bool, + show_add_task_dialog: bool, + + file_dialog: FileDialog, + folder_selection_mode: Option, + + pub auto_scroll_errors: bool, + pub show_completed_tasks: bool, + viewing_errors_for_task: Option, +} + +impl ExecutionPage { + pub fn new(config: Arc, event_bus: Arc, backup_engine: Arc) -> Self { + let folder_processing_events = event_bus.subscribe::(); + let progress_events = event_bus.subscribe::(); + let backup_error_events = event_bus.subscribe::(); + + Self { + config, + backup_engine, + folder_processing_events, + progress_events, + backup_error_events, + executions: DashMap::new(), + error_messages: DashMap::new(), + new_task_source: String::new(), + new_task_destination: String::new(), + new_task_mirror: false, + new_task_lock_source: false, + new_task_backup_permission: false, + new_task_follow_symlinks: false, + show_add_task_dialog: false, + file_dialog: FileDialog::new(), + folder_selection_mode: None, + auto_scroll_errors: true, + show_completed_tasks: true, + viewing_errors_for_task: None, + } + } + + fn process_events(&mut self) { + while let Ok(event) = self.folder_processing_events.try_recv() { + if let Some(mut task_display) = self.executions.get_mut(&event.execution_id) { + task_display.current_folder = event.current_folder.to_string_lossy().to_string(); + } + } + + while let Ok(event) = self.progress_events.try_recv() { + if let Some(mut task_display) = self.executions.get_mut(&event.task_id) { + task_display.processed_files = event.processed_files; + task_display.error_count = event.error_count; + } + } + + while let Ok(event) = self.backup_error_events.try_recv() { + match self.error_messages.get_mut(&event.task_id) { + Some(mut errors) => errors.push(event.error), + None => { + self.error_messages.insert(event.task_id, vec![event.error]); + } + } + } + } + + pub fn update(&mut self, ctx: &egui::Context) { + self.process_events(); + + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Backup Executions"); + + ui.horizontal(|ui| { + if ui.button("➕ Add Execution").clicked() { + self.show_add_task_dialog = true; + } + + ui.separator(); + + let running_count = self + .executions + .iter() + .filter(|entry| entry.value().execution.state == BackupState::Running) + .count(); + ui.label(format!("Running: {}", running_count)); + + let completed_count = self + .executions + .iter() + .filter(|entry| entry.value().execution.state == BackupState::Completed) + .count(); + ui.label(format!("Completed: {}", completed_count)); + + let error_count: usize = self + .error_messages + .iter() + .map(|entry| entry.value().len()) + .sum(); + if error_count > 0 { + ui.separator(); + ui.colored_label(egui::Color32::RED, format!("Total Errors: {}", error_count)); + } + }); + + ui.separator(); + + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + let tasks_to_show: Vec<(Uuid, ExecutionDisplay)> = self + .executions + .iter() + .filter_map(|entry| { + let (task_id, task_display) = (entry.key(), entry.value()); + + if !self.show_completed_tasks + && task_display.execution.state == BackupState::Completed + { + return None; + } + + Some((*task_id, task_display.clone())) + }) + .collect(); + + for (task_id, task_display) in tasks_to_show { + self.draw_execution_item(ui, task_id, &task_display); + ui.separator(); + } + + if self.executions.is_empty() { + ui.vertical_centered(|ui| { + ui.label("🚀 No backup executions"); + ui.label("Click the button above to add an execution"); + }); + } + }); + }); + + self.draw_add_execution_dialog(ctx); + self.draw_execution_errors_window(ctx); + } + + fn draw_execution_item(&mut self, ui: &mut egui::Ui, task_id: Uuid, task_display: &ExecutionDisplay) { + egui::Frame::new() + .fill(ui.visuals().faint_bg_color) + .inner_margin(8.0) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.label(format!("🗂️ {}", task_display.execution.source_path.display())); + ui.label(format!("📁 {}", task_display.execution.destination_path.display())); + + ui.horizontal(|ui| { + let (color, symbol) = match task_display.execution.state { + BackupState::Running => (egui::Color32::GREEN, "▶️"), + BackupState::Suspended => (egui::Color32::YELLOW, "⏸️"), + BackupState::Completed => (egui::Color32::BLUE, "✅"), + BackupState::Failed => (egui::Color32::RED, "❌"), + BackupState::Canceled => (egui::Color32::GRAY, "⏹️"), + BackupState::Pending => (egui::Color32::GRAY, "⏸️"), + }; + + ui.colored_label(color, format!("{} {:?}", symbol, task_display.execution.state)); + + if !task_display.current_folder.is_empty() { + ui.separator(); + ui.label(format!("📄 {}", + task_display.current_folder.chars().take(50).collect::())); + } + }); + + ui.horizontal(|ui| { + if task_display.processed_files > 0 || task_display.error_count > 0 { + ui.label(format!("📊 Processed: {} | Errors: {}", + task_display.processed_files, task_display.error_count)); + } + }); + }); + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if let Some(errors) = self.error_messages.get(&task_id) { + if !errors.is_empty() { + if ui.small_button("👁 View Errors").clicked() { + self.viewing_errors_for_task = Some(task_id); + } + ui.separator(); + } + } + + match task_display.execution.state { + BackupState::Pending | BackupState::Suspended => { + if ui.button("▶️ Start").clicked() { + if let Err(e) = block_on(self.backup_engine.start_execution(task_id)) { + eprintln!("Failed to start execution: {:?}", e); + } + } + } + BackupState::Running => { + if ui.button("⏸️ Pause").clicked() { + if let Err(e) = block_on(self.backup_engine.suspend_execution(task_id)) { + eprintln!("Failed to suspend execution: {:?}", e); + } + } + } + _ => {} + } + + if task_display.execution.state == BackupState::Suspended { + if ui.button("▶️ Resume").clicked() { + let rt = tokio::runtime::Handle::current(); + if let Err(e) = rt.block_on(self.backup_engine.resume_execution(task_id)) { + eprintln!("Failed to resume execution: {:?}", e); + } + } + } + + if ui.button("🗑️").clicked() { + let rt = tokio::runtime::Handle::current(); + rt.block_on(self.backup_engine.remove_execution(&task_id)); + self.executions.remove(&task_id); + self.error_messages.remove(&task_id); + if self.viewing_errors_for_task == Some(task_id) { + self.viewing_errors_for_task = None; + } + } + }); + }); + }); + } + + fn draw_add_execution_dialog(&mut self, ctx: &egui::Context) { + if self.show_add_task_dialog { + egui::Window::new("Add Backup Execution") + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + egui::Grid::new("add_execution_grid") + .num_columns(3) + .spacing([10.0, 4.0]) + .show(ui, |ui| { + ui.label("Source Path:"); + ui.text_edit_singleline(&mut self.new_task_source); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Source); + self.file_dialog.pick_directory(); + } + ui.end_row(); + + ui.label("Destination Path:"); + ui.text_edit_singleline(&mut self.new_task_destination); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Destination); + self.file_dialog.pick_directory(); + } + ui.end_row(); + }); + + ui.separator(); + + ui.label("Options:"); + ui.checkbox(&mut self.new_task_follow_symlinks, "Follow Symlinks"); + ui.checkbox(&mut self.new_task_mirror, "Mirror Mode (Delete extra files in destination)"); + ui.checkbox(&mut self.new_task_lock_source, "Lock Source Files"); + ui.checkbox(&mut self.new_task_backup_permission, "Backup File Permissions"); + + ui.separator(); + + ui.horizontal(|ui| { + if ui.button("Create Execution").clicked() { + if !self.new_task_source.is_empty() && !self.new_task_destination.is_empty() { + let execution = BackupExecution { + uuid: Uuid::new_v4(), + state: BackupState::Pending, + source_path: PathBuf::from(&self.new_task_source), + destination_path: PathBuf::from(&self.new_task_destination), + backup_type: BackupType::Full, + comparison_mode: None, + options: BackupOptions { + mirror: self.new_task_mirror, + lock_source: self.new_task_lock_source, + backup_permission: self.new_task_backup_permission, + follow_symlinks: self.new_task_follow_symlinks, + }, + }; + + let execution_display = ExecutionDisplay::from(execution.clone()); + self.executions.insert(execution.uuid, execution_display); + + let rt = tokio::runtime::Handle::current(); + rt.block_on(self.backup_engine.add_execution(execution)); + self.reset_form(); + } + } + + if ui.button("Cancel").clicked() { + self.show_add_task_dialog = false; + } + }); + }); + } + + self.file_dialog.update(ctx); + + if let Some(path) = self.file_dialog.take_picked() { + if let Some(mode) = &self.folder_selection_mode { + match mode { + FolderSelectionMode::Source => { + self.new_task_source = path.to_string_lossy().to_string(); + } + FolderSelectionMode::Destination => { + self.new_task_destination = path.to_string_lossy().to_string(); + } + } + } + self.folder_selection_mode = None; + } + } + + fn draw_execution_errors_window(&mut self, ctx: &egui::Context) { + if let Some(task_id) = self.viewing_errors_for_task { + let mut show_window = true; + + let window_title = if let Some(task) = self.executions.get(&task_id) { + format!("Execution Errors - {}", + task.execution.source_path.file_name().unwrap_or_default().to_string_lossy()) + } else { + "Execution Errors".to_string() + }; + + egui::Window::new(window_title) + .open(&mut show_window) + .resizable(true) + .default_width(600.0) + .default_height(400.0) + .show(ctx, |ui| { + if let Some(errors) = self.error_messages.get(&task_id) { + ui.horizontal(|ui| { + ui.heading(format!("Error List ({} items)", errors.len())); + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button("🗑️ Clear All Errors").clicked() { + self.error_messages.remove(&task_id); + self.viewing_errors_for_task = None; + } + }); + }); + + ui.separator(); + + egui::ScrollArea::vertical() + .stick_to_bottom(self.auto_scroll_errors) + .show(ui, |ui| { + for (i, error) in errors.iter().enumerate() { + egui::Frame::new() + .fill(if i % 2 == 0 { + ui.visuals().faint_bg_color + } else { + egui::Color32::TRANSPARENT + }) + .inner_margin(4.0) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.label(format!("{}.", i + 1)); + ui.colored_label(egui::Color32::LIGHT_RED, format!("{}", error)); + }); + }); + } + + if errors.is_empty() { + ui.vertical_centered(|ui| { + ui.label("✅ No errors for this execution"); + }); + } + }); + } else { + ui.vertical_centered(|ui| { + ui.label("⚠️ Cannot find error information for this execution"); + }); + } + }); + + if !show_window { + self.viewing_errors_for_task = None; + } + } + } + + fn reset_form(&mut self) { + self.new_task_source.clear(); + self.new_task_destination.clear(); + self.new_task_mirror = false; + self.new_task_lock_source = false; + self.new_task_backup_permission = false; + self.new_task_follow_symlinks = false; + self.show_add_task_dialog = false; + } +} diff --git a/src/ui/main_page.rs b/src/ui/main_page.rs new file mode 100644 index 0000000..556ae4f --- /dev/null +++ b/src/ui/main_page.rs @@ -0,0 +1,89 @@ +use crate::core::backup_engine::BackupEngine; +use crate::core::event_bus::EventBus; +use crate::core::schedule_manager::ScheduleManager; +use crate::ui::execution_page::ExecutionPage; +use crate::ui::schedule_page::SchedulePage; +use crate::model::log::system::SystemLog; +use eframe::egui; +use eframe::{App, Frame}; +use macros::log; +use std::sync::Arc; +use crate::core::app_config::AppConfig; + +#[derive(Debug, Clone, PartialEq)] +enum PageType { + Executions, + Schedules, +} + +pub struct MainPage { + current_page: PageType, + execution_page: ExecutionPage, + schedule_page: SchedulePage, +} + +impl MainPage { + pub fn new( + config: Arc, + event_bus: Arc, + backup_engine: Arc, + schedule_manager: Arc, + ) -> Self { + Self { + current_page: PageType::Executions, + execution_page: ExecutionPage::new(config.clone(), event_bus, backup_engine), + schedule_page: SchedulePage::new(config, schedule_manager), + } + } + + fn draw_top_panel(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { + egui::menu::bar(ui, |ui| { + ui.menu_button("File", |ui| { + if ui.button("Exit").clicked() { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + }); + + ui.menu_button("View", |ui| { + match self.current_page { + PageType::Executions => { + ui.checkbox(&mut self.execution_page.show_completed_tasks, "Show Completed Tasks"); + ui.checkbox(&mut self.execution_page.auto_scroll_errors, "Auto-scroll Error Messages"); + } + PageType::Schedules => { + ui.checkbox(&mut self.schedule_page.show_disabled_schedules, "Show Disabled Schedules"); + } + } + }); + }); + }); + } + + fn draw_tabs(&mut self, ctx: &egui::Context) { + egui::TopBottomPanel::top("tabs_panel").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.selectable_value(&mut self.current_page, PageType::Executions, "📋 Executions"); + ui.selectable_value(&mut self.current_page, PageType::Schedules, "⏰ Schedules"); + }); + }); + } +} + +impl App for MainPage { + fn update(&mut self, ctx: &egui::Context, _frame: &mut Frame) { + ctx.request_repaint_after(std::time::Duration::from_millis(100)); + + self.draw_top_panel(ctx); + self.draw_tabs(ctx); + + match self.current_page { + PageType::Executions => self.execution_page.update(ctx), + PageType::Schedules => self.schedule_page.update(ctx), + } + } + + fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { + log!(SystemLog::GuiExited) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e69de29..cdf53d1 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -0,0 +1,3 @@ +pub mod execution_page; +pub mod main_page; +pub mod schedule_page; diff --git a/src/ui/schedule_page.rs b/src/ui/schedule_page.rs new file mode 100644 index 0000000..f817e74 --- /dev/null +++ b/src/ui/schedule_page.rs @@ -0,0 +1,449 @@ +use crate::core::schedule_manager::ScheduleManager; +use crate::model::backup::backup_execution::*; +use crate::model::backup::backup_schedule::*; +use eframe::egui; +use egui_file_dialog::FileDialog; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use futures::executor::block_on; +use uuid::Uuid; +use crate::core::app_config::AppConfig; + +#[derive(Debug, Clone, PartialEq)] +enum FolderSelectionMode { + Source, + Destination, +} + +pub struct SchedulePage { + config: Arc, + schedule_manager: Arc, + + schedules: Vec, + + new_schedule_name: String, + new_schedule_source: String, + new_schedule_destination: String, + new_schedule_interval: ScheduleInterval, + new_schedule_mirror: bool, + new_schedule_lock_source: bool, + new_schedule_backup_permission: bool, + new_schedule_follow_symlinks: bool, + show_add_schedule_dialog: bool, + + file_dialog: FileDialog, + folder_selection_mode: Option, + + pub show_disabled_schedules: bool, + viewing_schedule_details: Option, + last_refresh: Option, +} + +impl SchedulePage { + pub fn new(config: Arc, schedule_manager: Arc) -> Self { + Self { + config, + schedule_manager, + schedules: Vec::new(), + new_schedule_name: String::new(), + new_schedule_source: String::new(), + new_schedule_destination: String::new(), + new_schedule_interval: ScheduleInterval::Daily, + new_schedule_mirror: false, + new_schedule_lock_source: false, + new_schedule_backup_permission: false, + new_schedule_follow_symlinks: false, + show_add_schedule_dialog: false, + file_dialog: FileDialog::new(), + folder_selection_mode: None, + show_disabled_schedules: true, + viewing_schedule_details: None, + last_refresh: None, + } + } + + fn load_schedules(&mut self) { + match block_on(self.schedule_manager.get_all_schedules()) { + Ok(schedules) => self.schedules = schedules, + Err(e) => eprintln!("Failed to load schedules: {:?}", e), + } + } + + pub fn update(&mut self, ctx: &egui::Context) { + // 檢查是否需要刷新 + let should_refresh = match self.last_refresh { + None => true, // 第一次載入 + Some(last) => last.elapsed() > Duration::from_millis(self.config.internal_timestamp as u64), + }; + + if should_refresh { + self.load_schedules(); + self.last_refresh = Some(Instant::now()); + } + + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("Backup Schedules"); + + ui.horizontal(|ui| { + if ui.button("🔄 Refresh").clicked() { + self.load_schedules(); + self.last_refresh = Some(Instant::now()); + } + + if ui.button("➕ Add Schedule").clicked() { + self.show_add_schedule_dialog = true; + } + + ui.separator(); + + let active_count = self.schedules.iter().filter(|s| s.state == ScheduleState::Active).count(); + ui.label(format!("Active: {}", active_count)); + + let paused_count = self.schedules.iter().filter(|s| s.state == ScheduleState::Paused).count(); + ui.label(format!("Paused: {}", paused_count)); + + let disabled_count = self.schedules.iter().filter(|s| s.state == ScheduleState::Disabled).count(); + ui.label(format!("Disabled: {}", disabled_count)); + }); + + ui.separator(); + + egui::ScrollArea::vertical() + .auto_shrink([false; 2]) + .show(ui, |ui| { + let schedules_to_show: Vec = self + .schedules + .iter() + .filter(|schedule| { + self.show_disabled_schedules || schedule.state != ScheduleState::Disabled + }) + .cloned() + .collect(); + + for schedule in schedules_to_show { + self.draw_schedule_item(ui, &schedule); + ui.separator(); + } + + if self.schedules.is_empty() { + ui.vertical_centered(|ui| { + ui.label("⏰ No backup schedules"); + ui.label("Click the button above to add a schedule"); + }); + } + }); + }); + + self.draw_add_schedule_dialog(ctx); + self.draw_schedule_details_window(ctx); + } + + fn draw_schedule_item(&mut self, ui: &mut egui::Ui, schedule: &BackupSchedule) { + egui::Frame::new() + .fill(ui.visuals().faint_bg_color) + .inner_margin(8.0) + .show(ui, |ui| { + ui.horizontal(|ui| { + ui.vertical(|ui| { + ui.label(format!("📅 {}", schedule.name)); + ui.label(format!("🗂️ {}", schedule.source_path.display())); + ui.label(format!("📁 {}", schedule.destination_path.display())); + ui.label(format!("⏱️ {:?}", schedule.interval)); + + ui.horizontal(|ui| { + let (color, symbol, status_text) = match schedule.state { + ScheduleState::Active => (egui::Color32::GREEN, "✅", "Active"), + ScheduleState::Paused => (egui::Color32::YELLOW, "⏸️", "Paused"), + ScheduleState::Disabled => (egui::Color32::GRAY, "❌", "Disabled"), + }; + + ui.colored_label(color, format!("{} {}", symbol, status_text)); + + if let Some(last_run) = schedule.last_run_time { + ui.separator(); + ui.label(format!("Last run: {}", last_run.format("%Y-%m-%d %H:%M"))); + } + + if let Some(next_run) = schedule.next_run_time { + ui.separator(); + ui.label(format!("Next run: {}", next_run.format("%Y-%m-%d %H:%M"))); + } + }); + }); + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.small_button("👁 Details").clicked() { + self.viewing_schedule_details = Some(schedule.uuid); + } + + ui.separator(); + + match schedule.state { + ScheduleState::Active => { + if ui.button("⏸️ Pause").clicked() { + if let Err(e) = block_on(self.schedule_manager.pause_schedule(schedule.uuid)) { + eprintln!("Failed to pause schedule: {:?}", e); + } + } + } + ScheduleState::Paused => { + if ui.button("▶️ Resume").clicked() { + if let Err(e) = block_on(self.schedule_manager.active_schedule(schedule.uuid)) { + eprintln!("Failed to resume schedule: {:?}", e); + } + } + } + ScheduleState::Disabled => { + if ui.button("▶️ Enable").clicked() { + if let Err(e) = block_on(self.schedule_manager.active_schedule(schedule.uuid)) { + eprintln!("Failed to enable schedule: {:?}", e); + } + } + } + } + + if schedule.state != ScheduleState::Disabled { + if ui.button("❌ Disable").clicked() { + if let Err(e) = block_on(self.schedule_manager.disable_schedule(schedule.uuid)) { + eprintln!("Failed to disable schedule: {:?}", e); + } + } + } + + if ui.button("🗑️").clicked() { + if let Err(e) = block_on(self.schedule_manager.remove_schedule(schedule.uuid)) { + eprintln!("Failed to remove schedule: {:?}", e); + } + } + }); + }); + }); + } + + fn draw_add_schedule_dialog(&mut self, ctx: &egui::Context) { + if self.show_add_schedule_dialog { + egui::Window::new("Add Backup Schedule") + .collapsible(false) + .resizable(false) + .show(ctx, |ui| { + egui::Grid::new("add_schedule_grid") + .num_columns(3) + .spacing([10.0, 4.0]) + .show(ui, |ui| { + ui.label("Schedule Name:"); + ui.text_edit_singleline(&mut self.new_schedule_name); + ui.label(""); + ui.end_row(); + + ui.label("Interval:"); + egui::ComboBox::from_label("") + .selected_text(format!("{:?}", self.new_schedule_interval)) + .show_ui(ui, |ui| { + ui.selectable_value(&mut self.new_schedule_interval, ScheduleInterval::Once, "Once"); + ui.selectable_value(&mut self.new_schedule_interval, ScheduleInterval::Daily, "Daily"); + ui.selectable_value(&mut self.new_schedule_interval, ScheduleInterval::Weekly, "Weekly"); + ui.selectable_value(&mut self.new_schedule_interval, ScheduleInterval::Monthly, "Monthly"); + }); + ui.label(""); + ui.end_row(); + + ui.label("Source Path:"); + ui.text_edit_singleline(&mut self.new_schedule_source); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Source); + self.file_dialog.pick_directory(); + } + ui.end_row(); + + ui.label("Destination Path:"); + ui.text_edit_singleline(&mut self.new_schedule_destination); + if ui.button("📁 Browse").clicked() { + self.folder_selection_mode = Some(FolderSelectionMode::Destination); + self.file_dialog.pick_directory(); + } + ui.end_row(); + }); + + ui.separator(); + + ui.label("Options:"); + ui.checkbox(&mut self.new_schedule_follow_symlinks, "Follow Symlinks"); + ui.checkbox(&mut self.new_schedule_mirror, "Mirror Mode (Delete extra files in destination)"); + ui.checkbox(&mut self.new_schedule_lock_source, "Lock Source Files"); + ui.checkbox(&mut self.new_schedule_backup_permission, "Backup File Permissions"); + + ui.separator(); + + ui.horizontal(|ui| { + if ui.button("Create Schedule").clicked() { + if !self.new_schedule_name.is_empty() + && !self.new_schedule_source.is_empty() + && !self.new_schedule_destination.is_empty() { + + let schedule = BackupSchedule { + uuid: Uuid::new_v4(), + name: self.new_schedule_name.clone(), + state: ScheduleState::Active, + source_path: PathBuf::from(&self.new_schedule_source), + destination_path: PathBuf::from(&self.new_schedule_destination), + backup_type: BackupType::Full, + comparison_mode: None, + options: BackupOptions { + mirror: self.new_schedule_mirror, + lock_source: self.new_schedule_lock_source, + backup_permission: self.new_schedule_backup_permission, + follow_symlinks: self.new_schedule_follow_symlinks, + }, + interval: self.new_schedule_interval, + last_run_time: None, + next_run_time: None, + created_at: chrono::Utc::now().naive_utc(), + updated_at: chrono::Utc::now().naive_utc(), + }; + + if let Err(e) = block_on(self.schedule_manager.create_schedule(schedule)) { + eprintln!("Failed to create schedule: {:?}", e); + } + + self.reset_schedule_form(); + } + } + + if ui.button("Cancel").clicked() { + self.show_add_schedule_dialog = false; + } + }); + }); + } + + self.file_dialog.update(ctx); + + if let Some(path) = self.file_dialog.take_picked() { + if let Some(mode) = &self.folder_selection_mode { + match mode { + FolderSelectionMode::Source => { + self.new_schedule_source = path.to_string_lossy().to_string(); + } + FolderSelectionMode::Destination => { + self.new_schedule_destination = path.to_string_lossy().to_string(); + } + } + } + self.folder_selection_mode = None; + } + } + + fn draw_schedule_details_window(&mut self, ctx: &egui::Context) { + if let Some(schedule_id) = self.viewing_schedule_details { + let mut show_window = true; + + if let Some(schedule) = self.schedules.iter().find(|s| s.uuid == schedule_id) { + egui::Window::new(format!("Schedule Details - {}", schedule.name)) + .open(&mut show_window) + .resizable(true) + .default_width(500.0) + .default_height(350.0) + .show(ctx, |ui| { + egui::Grid::new("schedule_details_grid") + .num_columns(2) + .spacing([10.0, 4.0]) + .show(ui, |ui| { + ui.label("Name:"); + ui.label(&schedule.name); + ui.end_row(); + + ui.label("State:"); + ui.label(format!("{:?}", schedule.state)); + ui.end_row(); + + ui.label("Source:"); + ui.label(schedule.source_path.display().to_string()); + ui.end_row(); + + ui.label("Destination:"); + ui.label(schedule.destination_path.display().to_string()); + ui.end_row(); + + ui.label("Backup Type:"); + ui.label(format!("{:?}", schedule.backup_type)); + ui.end_row(); + + ui.label("Interval:"); + ui.label(format!("{:?}", schedule.interval)); + ui.end_row(); + + if let Some(last_run) = schedule.last_run_time { + ui.label("Last Run:"); + ui.label(last_run.format("%Y-%m-%d %H:%M:%S").to_string()); + ui.end_row(); + } + + if let Some(next_run) = schedule.next_run_time { + ui.label("Next Run:"); + ui.label(next_run.format("%Y-%m-%d %H:%M:%S").to_string()); + ui.end_row(); + } + + ui.label("Created:"); + ui.label(schedule.created_at.format("%Y-%m-%d %H:%M:%S").to_string()); + ui.end_row(); + + ui.label("Updated:"); + ui.label(schedule.updated_at.format("%Y-%m-%d %H:%M:%S").to_string()); + ui.end_row(); + }); + + ui.separator(); + + ui.label("Options:"); + ui.horizontal_wrapped(|ui| { + if schedule.options.mirror { + ui.label("✅ Mirror Mode"); + } + if schedule.options.lock_source { + ui.label("✅ Lock Source"); + } + if schedule.options.backup_permission { + ui.label("✅ Backup Permissions"); + } + if schedule.options.follow_symlinks { + ui.label("✅ Follow Symlinks"); + } + }); + + ui.separator(); + + ui.horizontal(|ui| { + if ui.button("Run Now").clicked() { + // 這裡可以觸發立即執行排程 + // 可能需要新增一個 API 方法來立即執行排程 + println!("Would run schedule {} now", schedule.name); + } + + if ui.button("Edit").clicked() { + // 這裡可以開啟編輯對話框 + println!("Would edit schedule {}", schedule.name); + } + }); + }); + } + + if !show_window { + self.viewing_schedule_details = None; + } + } + } + + fn reset_schedule_form(&mut self) { + self.new_schedule_name.clear(); + self.new_schedule_source.clear(); + self.new_schedule_destination.clear(); + self.new_schedule_interval = ScheduleInterval::Daily; + self.new_schedule_mirror = false; + self.new_schedule_lock_source = false; + self.new_schedule_backup_permission = false; + self.new_schedule_follow_symlinks = false; + self.show_add_schedule_dialog = false; + } +} diff --git a/src/utils/database_lock.rs b/src/utils/database_lock.rs new file mode 100644 index 0000000..41fd267 --- /dev/null +++ b/src/utils/database_lock.rs @@ -0,0 +1,29 @@ +use crate::model::error::database::DatabaseError; +use crate::model::error::Error; +use crate::platform::constants::DATABASE_LOCK_PATH; +use std::fs; +use tokio::fs::File; + +pub struct DatabaseLock { + _private: (), +} + +impl DatabaseLock { + pub async fn acquire() -> Result { + let lock = Self { _private: () }; + if tokio::fs::metadata(DATABASE_LOCK_PATH).await.is_err() { + File::create(&DATABASE_LOCK_PATH) + .await + .map_err(|err| DatabaseError::LockDatabaseFailed(err))?; + Ok(lock) + } else { + Err(DatabaseError::LockDatabaseFailed("Lock file already exists."))? + } + } +} + +impl Drop for DatabaseLock { + fn drop(&mut self) { + let _ = fs::remove_file(&DATABASE_LOCK_PATH); + } +} diff --git a/src/utils/file_hash.rs b/src/utils/file_hash.rs index 4aba121..079ba14 100644 --- a/src/utils/file_hash.rs +++ b/src/utils/file_hash.rs @@ -1,46 +1,50 @@ +use crate::model::error::io::IOError; +use crate::model::error::Error; +use blake2::{Blake2b512, Blake2s256}; use digest::{Digest, DynDigest, HashMarker}; use md5::Md5; +use sha2::Sha256; use std::fs::File; use std::io::Read; -use blake2::{Blake2b512, Blake2s256}; -use sha2::Sha256; -use crate::utils::log_entry::io::IOEntry; +use std::path::PathBuf; -pub fn md5(file: File) -> anyhow::Result> { +pub fn md5(path: PathBuf) -> Result, Error> { let hasher = Md5::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -pub fn sha3(file: File) -> anyhow::Result> { +pub fn sha3(path: PathBuf) -> Result, Error> { let hasher = sha3::Sha3_256::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -pub fn sha256(file: File) -> anyhow::Result> { +pub fn sha256(path: PathBuf) -> Result, Error> { let hasher = Sha256::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -pub fn blake2b(file: File) -> anyhow::Result> { +pub fn blake2b(path: PathBuf) -> Result, Error> { let hasher = Blake2b512::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -pub fn blake2s(file: File) -> anyhow::Result> { +pub fn blake2s(path: PathBuf) -> Result, Error> { let hasher = Blake2s256::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -pub fn blake3(file: File) -> anyhow::Result> { +pub fn blake3(path: PathBuf) -> Result, Error> { let hasher = blake3::Hasher::new(); - file_hash(file, hasher) + file_hash(path, hasher) } -fn file_hash(mut file: File, mut hasher: impl HashMarker + DynDigest) -> anyhow::Result> { +fn file_hash(path: PathBuf, mut hasher: impl HashMarker + DynDigest) -> Result, Error> { + let mut file = File::open(&path).map_err(|err| IOError::ReadFileFailed(path.clone(), err))?; let mut buffer = [0; 65536]; loop { - let bytes_read = file.read(&mut buffer) - .map_err(|_| IOEntry::ReadFileFailed)?; + let bytes_read = file + .read(&mut buffer) + .map_err(|err| IOError::ReadFileFailed(path.clone(), err))?; if bytes_read == 0 { break; } diff --git a/src/utils/file_lock.rs b/src/utils/file_lock.rs index 107c102..c4577f1 100644 --- a/src/utils/file_lock.rs +++ b/src/utils/file_lock.rs @@ -1,28 +1,35 @@ -use tokio::fs::File; -use std::io; -use std::path::Path; +use crate::model::error::io::IOError; +use crate::model::error::Error; use fs4::tokio::AsyncFileExt; +use macros::log; +use std::path::PathBuf; +use tokio::fs::File; #[derive(Debug)] pub struct FileLock { file: File, + path: PathBuf, } impl FileLock { - pub async fn new>(path: P) -> io::Result { - let file = File::open(path).await?; - file.try_lock_exclusive()?; - Ok(Self { file }) - } - - pub async fn from_file(file: File) -> io::Result { - file.try_lock_exclusive()?; - Ok(Self { file }) + pub async fn new(path: &PathBuf) -> Result { + let file = File::open(path) + .await + .map_err(|err| IOError::ReadFileFailed(path.clone(), err))?; + file.try_lock_exclusive() + .map_err(|err| IOError::LockFileFailed(path.clone(), err))?; + Ok(Self { + file, + path: path.clone(), + }) } } impl Drop for FileLock { fn drop(&mut self) { - let _ = self.file.unlock(); + let path = self.path.clone(); + if let Err(err) = self.file.unlock() { + log!(IOError::UnlockFileFailed(path, err)); + } } } diff --git a/src/utils/log_entry/database.rs b/src/utils/log_entry/database.rs deleted file mode 100644 index 193b67c..0000000 --- a/src/utils/log_entry/database.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::utils::log_entry::define_log_entries; - -define_log_entries! { - DatabaseEntry { - #[error("Failed to create database")] - CreateDatabaseFailed: tracing::Level::ERROR, - - #[error("Connected to database successfully")] - DatabaseConnectSuccess: tracing::Level::INFO, - - #[error("Failed to connect to database")] - DatabaseConnectFailed: tracing::Level::ERROR, - - #[error("Failed to lock database")] - LockDatabaseFailed: tracing::Level::ERROR, - - #[error("Failed to unlock database")] - UnlockDatabaseFailed: tracing::Level::ERROR, - } -} diff --git a/src/utils/log_entry/event.rs b/src/utils/log_entry/event.rs deleted file mode 100644 index 071468d..0000000 --- a/src/utils/log_entry/event.rs +++ /dev/null @@ -1,5 +0,0 @@ -use thiserror::Error; -use crate::define_log_entries; - -#[derive(Error, Debug)] -pub enum EventEntry {} diff --git a/src/utils/log_entry/io.rs b/src/utils/log_entry/io.rs deleted file mode 100644 index d19e325..0000000 --- a/src/utils/log_entry/io.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::define_log_entries; - -define_log_entries! { - IOEntry { - #[error("Semaphore has been closed")] - SemaphoreClosed: tracing::Level::ERROR, - - #[error("Failed to create directory")] - CreateDirectoryFailed: tracing::Level::ERROR, - - #[error("Failed to read directory")] - ReadDirectoryFailed: tracing::Level::ERROR, - - #[error("Failed to read file")] - ReadFileFailed: tracing::Level::ERROR, - - #[error("Failed to copy file")] - CopyFileFailed: tracing::Level::ERROR, - - #[error("Failed to delete directory")] - DeleteDirectoryFailed: tracing::Level::ERROR, - - #[error("Failed to delete file")] - DeleteFileFailed: tracing::Level::ERROR, - - #[error("Failed to get file metadata")] - GetMetadataFailed: tracing::Level::ERROR, - - #[error("Failed to set file metadata")] - SetMetadataFailed: tracing::Level::ERROR, - } -} diff --git a/src/utils/log_entry/misc.rs b/src/utils/log_entry/misc.rs deleted file mode 100644 index a653695..0000000 --- a/src/utils/log_entry/misc.rs +++ /dev/null @@ -1,6 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum MiscEntry { - -} diff --git a/src/utils/log_entry/mod.rs b/src/utils/log_entry/mod.rs deleted file mode 100644 index 3d546dd..0000000 --- a/src/utils/log_entry/mod.rs +++ /dev/null @@ -1,70 +0,0 @@ -pub mod database; -pub mod event; -pub mod io; -pub mod misc; -pub mod system; -pub mod task; - -pub use crate::define_log_entries; - -#[macro_export] -macro_rules! define_log_entries { - ( - $enum_name:ident { - $( - $(#[doc = $doc:expr])* - #[error($msg:expr)] - $variant:ident: $level:expr - $(,)? - )* - } - ) => { - #[allow(dead_code)] - #[derive(thiserror::Error, Debug)] - pub enum $enum_name { - $( - $(#[doc = $doc])* - #[error($msg)] - $variant, - )* - } - - impl $enum_name { - #[allow(dead_code)] - pub fn level(&self) -> tracing::Level { - match self { - $(Self::$variant => $level,)* - } - } - - #[allow(dead_code)] - pub fn log(&self) { - let level = self.level(); - let message = self.to_string(); - - match level { - tracing::Level::ERROR => tracing::error!("{}", message), - tracing::Level::WARN => tracing::warn!("{}", message), - tracing::Level::INFO => tracing::info!("{}", message), - tracing::Level::DEBUG => tracing::debug!("{}", message), - tracing::Level::TRACE => tracing::trace!("{}", message), - } - } - - #[allow(dead_code)] - pub fn log_with_context(&self, context: T) { - let level = self.level(); - let message = self.to_string(); - let context = context.to_string(); - - match level { - tracing::Level::ERROR => tracing::error!(message = %message, context = %context), - tracing::Level::WARN => tracing::warn!(message = %message, context = %context), - tracing::Level::INFO => tracing::info!(message = %message, context = %context), - tracing::Level::DEBUG => tracing::debug!(message = %message, context = %context), - tracing::Level::TRACE => tracing::trace!(message = %message, context = %context), - } - } - } - }; -} diff --git a/src/utils/log_entry/system.rs b/src/utils/log_entry/system.rs deleted file mode 100644 index 768c035..0000000 --- a/src/utils/log_entry/system.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::define_log_entries; - -define_log_entries! { - SystemEntry { - #[error("Rerun the program as administrator")] - ReRunAsAdmin: tracing::Level::WARN, - - #[error("Unable to run as administrator")] - RunAsAdminFailed: tracing::Level::ERROR, - - #[error("Failed to adjust token privileges")] - AdjustTokenPrivilegesFailed: tracing::Level::ERROR, - - #[error("Failed to free object")] - ObjectFreeFailed: tracing::Level::ERROR, - - #[error("Online now")] - Online: tracing::Level::INFO, - - #[error("Initializing")] - Initializing: tracing::Level::INFO, - - #[error("Initialization completed")] - InitializeComplete: tracing::Level::INFO, - - #[error("Termination in process")] - Terminating: tracing::Level::INFO, - - #[error("Termination completed")] - TerminateComplete: tracing::Level::INFO, - - #[error("Invalid configuration")] - InvalidConfig: tracing::Level::ERROR, - - #[error("Configuration not found")] - ConfigNotFound: tracing::Level::ERROR, - - #[error("Internal error")] - InternalError: tracing::Level::ERROR, - - #[error("Unknown error")] - ThreadPanic: tracing::Level::ERROR, - - #[error("Unknown error")] - UnknownError: tracing::Level::ERROR, - } -} diff --git a/src/utils/log_entry/task.rs b/src/utils/log_entry/task.rs deleted file mode 100644 index d13168c..0000000 --- a/src/utils/log_entry/task.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::define_log_entries; - -define_log_entries! { - TaskEntry { - #[error("Task not found")] - TaskNotFound: tracing::Level::ERROR, - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f180653..caa1438 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,4 @@ +pub mod database_lock; pub mod file_hash; pub mod file_lock; -pub mod log_entry; pub mod logging;