diff --git a/Cargo.lock b/Cargo.lock index d531d66b..053a06df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -69,30 +69,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -198,9 +198,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff" +checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" dependencies = [ "async-lock 3.2.0", "cfg-if", @@ -209,7 +209,7 @@ dependencies = [ "futures-lite 2.1.0", "parking", "polling 3.3.1", - "rustix 0.38.25", + "rustix 0.38.28", "slab", "tracing", "windows-sys 0.52.0", @@ -248,7 +248,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.25", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -260,7 +260,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -269,13 +269,13 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.2.1", + "async-io 2.2.2", "async-lock 2.8.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.25", + "rustix 0.38.28", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -300,7 +300,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -317,7 +317,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -589,7 +589,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -620,7 +620,7 @@ dependencies = [ [[package]] name = "clipcat-base" -version = "0.14.0" +version = "0.15.0" dependencies = [ "bytes", "directories", @@ -639,7 +639,7 @@ dependencies = [ [[package]] name = "clipcat-cli" -version = "0.14.0" +version = "0.15.0" dependencies = [ "serde", "serde_with", @@ -650,7 +650,7 @@ dependencies = [ [[package]] name = "clipcat-client" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "clipcat-base", @@ -668,7 +668,7 @@ dependencies = [ [[package]] name = "clipcat-clipboard" -version = "0.14.0" +version = "0.15.0" dependencies = [ "arboard", "bytes", @@ -687,7 +687,7 @@ dependencies = [ [[package]] name = "clipcat-external-editor" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clipcat-base", "snafu", @@ -696,7 +696,7 @@ dependencies = [ [[package]] name = "clipcat-menu" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clap 4.4.11", "clap_complete", @@ -706,6 +706,7 @@ dependencies = [ "clipcat-external-editor", "http 1.0.0", "http-serde", + "mimalloc", "serde", "skim", "snafu", @@ -718,12 +719,13 @@ dependencies = [ [[package]] name = "clipcat-notify" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clap 4.4.11", "clap_complete", "clipcat-base", "clipcat-server", + "mimalloc", "mime", "serde", "serde_json", @@ -734,7 +736,7 @@ dependencies = [ [[package]] name = "clipcat-proto" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clipcat-base", "mime", @@ -748,7 +750,7 @@ dependencies = [ [[package]] name = "clipcat-server" -version = "0.14.0" +version = "0.15.0" dependencies = [ "async-trait", "bincode", @@ -777,7 +779,7 @@ dependencies = [ [[package]] name = "clipcatctl" -version = "0.14.0" +version = "0.15.0" dependencies = [ "bytes", "clap 4.4.11", @@ -789,6 +791,7 @@ dependencies = [ "directories", "http 1.0.0", "http-serde", + "mimalloc", "mime", "serde", "simdutf8", @@ -802,7 +805,7 @@ dependencies = [ [[package]] name = "clipcatd" -version = "0.14.0" +version = "0.15.0" dependencies = [ "clap 4.4.11", "clap_complete", @@ -814,10 +817,12 @@ dependencies = [ "exitcode", "libc", "linicon", + "mimalloc", "mime", "serde", "simdutf8", "snafu", + "time", "tokio", "toml", "tracing", @@ -848,9 +853,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -858,9 +863,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" @@ -877,9 +882,9 @@ dependencies = [ [[package]] name = "core-graphics-types" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -920,9 +925,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" dependencies = [ "cfg-if", "crossbeam-utils", @@ -930,9 +935,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -941,22 +946,21 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset 0.9.0", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "b9bcf5bdbfdd6030fb4a1c497b5d5fc5921aa2f60d359a17e249c0e6df3de153" dependencies = [ "cfg-if", "crossbeam-utils", @@ -964,9 +968,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" dependencies = [ "cfg-if", ] @@ -1041,7 +1045,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1063,7 +1067,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core 0.20.3", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1078,9 +1082,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", "serde", @@ -1245,7 +1249,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1520,7 +1524,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -1697,11 +1701,11 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1728,9 +1732,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http 0.2.11", @@ -1911,9 +1915,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jpeg-decoder" @@ -1967,6 +1971,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libmimalloc-sys" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libredox" version = "0.0.1" @@ -2009,9 +2023,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -2099,6 +2113,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mimalloc" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" @@ -2299,9 +2322,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "option-ext" @@ -2413,7 +2436,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2483,7 +2506,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.25", + "rustix 0.38.28", "tracing", "windows-sys 0.52.0", ] @@ -2507,7 +2530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2556,7 +2579,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.39", + "syn 2.0.41", "tempfile", "which", ] @@ -2571,7 +2594,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2741,15 +2764,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.4.1", "errno", "libc", - "linux-raw-sys 0.4.11", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.12", + "windows-sys 0.52.0", ] [[package]] @@ -2760,9 +2783,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "scoped-tls" @@ -2799,7 +2822,7 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2821,7 +2844,7 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -2859,7 +2882,7 @@ dependencies = [ "darling 0.20.3", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3060,9 +3083,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" dependencies = [ "proc-macro2", "quote", @@ -3094,7 +3117,7 @@ dependencies = [ "cfg-if", "fastrand 2.0.1", "redox_syscall", - "rustix 0.38.25", + "rustix 0.38.28", "windows-sys 0.48.0", ] @@ -3141,7 +3164,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3241,7 +3264,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3352,7 +3375,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3407,7 +3430,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", ] [[package]] @@ -3472,9 +3495,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tuikit" @@ -3498,10 +3521,11 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ + "memoffset 0.9.0", "tempfile", "winapi", ] @@ -3599,7 +3623,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-shared", ] @@ -3621,7 +3645,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.39", + "syn 2.0.41", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3720,7 +3744,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.25", + "rustix 0.38.28", ] [[package]] @@ -3916,9 +3940,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.19" +version = "0.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" dependencies = [ "memchr", ] @@ -3963,7 +3987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" dependencies = [ "gethostname 0.4.3", - "rustix 0.38.25", + "rustix 0.38.28", "x11rb-protocol 0.13.0", ] diff --git a/Cargo.toml b/Cargo.toml index f8b93cf8..5af4a8ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.14.0" +version = "0.15.0" authors = ["xrelkd <46590321+xrelkd@users.noreply.github.com>"] homepage = "https://github.com/xrelkd/clipcat" repository = "https://github.com/xrelkd/clipcat" diff --git a/README.md b/README.md index 61954cce..cec2ed82 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ cd ~/bin # download and extract clipcat to ~/bin/ # NOTE: replace the version with the version you want to install -export CLIPCAT_VERSION=v0.14.0 +export CLIPCAT_VERSION=v0.15.0 # NOTE: the architecture of your machine, # available values are `x86_64-unknown-linux-musl`, `armv7-unknown-linux-musleabihf`, `aarch64-unknown-linux-musl` diff --git a/clipcat-menu/Cargo.toml b/clipcat-menu/Cargo.toml index f405e0d5..123b324a 100644 --- a/clipcat-menu/Cargo.toml +++ b/clipcat-menu/Cargo.toml @@ -12,6 +12,8 @@ categories.workspace = true keywords.workspace = true [dependencies] +mimalloc = "0.1" + tracing = "0.1" tracing-journald = "0.3" tracing-subscriber = "0.3" diff --git a/clipcat-menu/src/main.rs b/clipcat-menu/src/main.rs index 35eeb5da..c9a04b90 100644 --- a/clipcat-menu/src/main.rs +++ b/clipcat-menu/src/main.rs @@ -3,8 +3,13 @@ mod config; mod error; mod finder; +use mimalloc::MiMalloc; + use self::cli::Cli; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + fn main() { if let Err(err) = Cli::default().run() { eprintln!("Error: {err}"); diff --git a/clipcat-notify/Cargo.toml b/clipcat-notify/Cargo.toml index 0d8d5200..bf869e3a 100644 --- a/clipcat-notify/Cargo.toml +++ b/clipcat-notify/Cargo.toml @@ -12,6 +12,8 @@ categories.workspace = true keywords.workspace = true [dependencies] +mimalloc = "0.1" + serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/clipcat-notify/src/main.rs b/clipcat-notify/src/main.rs index cde1a955..4330b1ef 100644 --- a/clipcat-notify/src/main.rs +++ b/clipcat-notify/src/main.rs @@ -4,6 +4,7 @@ use std::{io::Write, sync::Arc}; use clap::{CommandFactory, Parser, Subcommand}; use clipcat_base::{ClipFilter, ClipboardKind}; +use mimalloc::MiMalloc; use serde::Serialize; use snafu::ResultExt; use time::OffsetDateTime; @@ -11,6 +12,9 @@ use tokio::runtime::Runtime; use self::error::Error; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + #[derive(Parser)] #[clap(name = clipcat_base::NOTIFY_PROGRAM_NAME, author, version, about, long_about = None)] struct Cli { @@ -56,15 +60,33 @@ impl Cli { let enable_primary = !self.no_primary; let enable_secondary = !self.no_secondary; - if !enable_clipboard && !enable_primary && !enable_secondary { - return Err(Error::ListenToNothing); - } + let clipboard_kinds = { + let mut clipboard_kinds = Vec::with_capacity(ClipboardKind::MAX_LENGTH); + if enable_clipboard { + clipboard_kinds.push(ClipboardKind::Clipboard); + } + if enable_primary { + clipboard_kinds.push(ClipboardKind::Primary); + } + if enable_secondary { + clipboard_kinds.push(ClipboardKind::Secondary); + } + + if clipboard_kinds.is_empty() { + return Err(Error::ListenToNothing); + } + + clipboard_kinds + }; Runtime::new().context(error::InitializeTokioRuntimeSnafu)?.block_on( async move { - let backend = - clipcat_server::backend::new(&Arc::new(ClipFilter::default()), &[]) - .context(error::InitializeClipboardBackendSnafu)?; + let backend = clipcat_server::backend::new( + clipboard_kinds, + &Arc::new(ClipFilter::default()), + &[], + ) + .context(error::InitializeClipboardBackendSnafu)?; let mut subscriber = backend.subscribe().context(error::SubscribeClipboardSnafu)?; diff --git a/clipcatctl/Cargo.toml b/clipcatctl/Cargo.toml index 145c9813..92fead3b 100644 --- a/clipcatctl/Cargo.toml +++ b/clipcatctl/Cargo.toml @@ -12,6 +12,8 @@ categories.workspace = true keywords.workspace = true [dependencies] +mimalloc = "0.1" + tracing = "0.1" tracing-journald = "0.3" tracing-subscriber = "0.3" diff --git a/clipcatctl/src/cli.rs b/clipcatctl/src/cli.rs index bb56f408..ae8cd4bc 100644 --- a/clipcatctl/src/cli.rs +++ b/clipcatctl/src/cli.rs @@ -263,7 +263,7 @@ impl Cli { } Some(Commands::Get { id }) => { let data = if let Some(id) = id { - client.get(id).await?.printable_data(None) + client.get(id).await?.preview_information(None) } else { client .list(PREVIEW_LENGTH) diff --git a/clipcatctl/src/main.rs b/clipcatctl/src/main.rs index 98b39b5b..f0dd97e9 100644 --- a/clipcatctl/src/main.rs +++ b/clipcatctl/src/main.rs @@ -2,8 +2,13 @@ mod cli; mod config; mod error; +use mimalloc::MiMalloc; + use self::cli::Cli; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + fn main() { match Cli::default().run() { Ok(exit_code) => { diff --git a/clipcatd/Cargo.toml b/clipcatd/Cargo.toml index 1c7a5fe3..c5941c20 100644 --- a/clipcatd/Cargo.toml +++ b/clipcatd/Cargo.toml @@ -12,6 +12,8 @@ categories.workspace = true keywords.workspace = true [dependencies] +mimalloc = "0.1" + tracing = "0.1" tracing-journald = "0.3" tracing-subscriber = "0.3" @@ -31,6 +33,7 @@ linicon = "2" mime = "0.3" simdutf8 = "0.1" snafu = "0.7" +time = { version = "0.3", features = ["formatting", "macros"] } clipcat-base = { path = "../crates/base" } clipcat-cli = { path = "../crates/cli" } diff --git a/clipcatd/src/config.rs b/clipcatd/src/config.rs index ac5dd128..e8df1ce9 100644 --- a/clipcatd/src/config.rs +++ b/clipcatd/src/config.rs @@ -8,6 +8,7 @@ use std::{ use directories::BaseDirs; use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; +use time::OffsetDateTime; const DEFAULT_ICON_NAME: &str = "accessories-clipboard"; @@ -53,6 +54,9 @@ pub struct WatcherConfig { #[serde(default)] pub enable_primary: bool, + #[serde(default = "WatcherConfig::default_enable_secondary")] + pub enable_secondary: bool, + #[serde(default = "WatcherConfig::default_sensitive_x11_atoms")] pub sensitive_x11_atoms: HashSet, @@ -78,6 +82,7 @@ impl From for clipcat_server::ClipboardWatcherOptions { load_current, enable_clipboard, enable_primary, + enable_secondary, capture_image, filter_text_min_length, filter_text_max_length, @@ -90,6 +95,7 @@ impl From for clipcat_server::ClipboardWatcherOptions { load_current, enable_clipboard, enable_primary, + enable_secondary, capture_image, filter_text_min_length, filter_text_max_length, @@ -110,6 +116,8 @@ impl WatcherConfig { 5 * (1 << 20) } + pub const fn default_enable_secondary() -> bool { false } + pub fn default_sensitive_x11_atoms() -> HashSet { HashSet::from(["x-kde-passwordManagerHint".to_string()]) } @@ -206,7 +214,7 @@ impl SnippetConfig { &data, &mime::TEXT_PLAIN_UTF_8, clipcat_base::ClipboardKind::Clipboard, - None, + Some(OffsetDateTime::UNIX_EPOCH), ) .ok() } else { @@ -237,6 +245,7 @@ impl Default for WatcherConfig { load_current: true, enable_clipboard: true, enable_primary: true, + enable_secondary: Self::default_enable_secondary(), capture_image: true, filter_text_min_length: Self::default_filter_text_min_length(), filter_text_max_length: Self::default_filter_text_max_length(), diff --git a/clipcatd/src/main.rs b/clipcatd/src/main.rs index 703c31c1..7f5c50f5 100644 --- a/clipcatd/src/main.rs +++ b/clipcatd/src/main.rs @@ -3,8 +3,13 @@ mod config; mod error; mod pid_file; +use mimalloc::MiMalloc; + use self::{command::Cli, error::CommandError}; +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + fn main() { if let Err(err) = Cli::default().run() { eprintln!("Error: {err}"); diff --git a/crates/base/src/entry.rs b/crates/base/src/entry.rs index bff3eb84..1e8f6e49 100644 --- a/crates/base/src/entry.rs +++ b/crates/base/src/entry.rs @@ -1,6 +1,5 @@ use std::{ cmp::Ordering, - collections::hash_map::DefaultHasher, fmt, hash::{Hash, Hasher}, }; @@ -14,8 +13,6 @@ use crate::{ClipboardContent, ClipboardKind}; #[derive(Clone, Debug, Eq)] pub struct Entry { - id: u64, - content: ClipboardContent, clipboard_kind: ClipboardKind, @@ -65,13 +62,9 @@ impl Entry { }; let sha256_digest = compute_sha256_digest(&content); - Ok(Self { - id: Self::compute_id(&content), - content, - clipboard_kind, - timestamp: timestamp.unwrap_or_else(OffsetDateTime::now_utc), - sha256_digest, - }) + let timestamp = timestamp.unwrap_or_else(OffsetDateTime::now_utc); + + Ok(Self { content, clipboard_kind, timestamp, sha256_digest }) } #[inline] @@ -88,7 +81,6 @@ impl Entry { ) -> Self { let sha256_digest = compute_sha256_digest(&content); Self { - id: Self::compute_id(&content), content, clipboard_kind, timestamp: timestamp.unwrap_or_else(OffsetDateTime::now_utc), @@ -98,15 +90,7 @@ impl Entry { #[inline] #[must_use] - pub fn compute_id(data: &ClipboardContent) -> u64 { - let mut s = DefaultHasher::new(); - data.hash(&mut s); - s.finish() - } - - #[inline] - #[must_use] - pub const fn id(&self) -> u64 { self.id } + pub fn id(&self) -> u64 { self.content.id() } #[inline] #[must_use] @@ -121,14 +105,12 @@ impl Entry { #[inline] #[must_use] - pub const fn is_utf8_string(&self) -> bool { - matches!(self.content, ClipboardContent::Plaintext(_)) - } + pub const fn is_utf8_string(&self) -> bool { self.content.is_plaintext() } #[inline] #[must_use] pub fn as_utf8_string(&self) -> String { - if let ClipboardContent::Plaintext(text) = &self.content { + if let ClipboardContent::Plaintext(ref text) = self.content { text.clone() } else { String::new() @@ -137,24 +119,18 @@ impl Entry { #[must_use] pub fn basic_information(&self) -> String { - let (content_type, size) = match &self.content { - ClipboardContent::Plaintext(text) => (mime::TEXT_PLAIN_UTF_8, text.len()), - ClipboardContent::Image { width: _, height: _, bytes } => { - (mime::IMAGE_PNG, bytes.len()) - } - }; - let timestamp = self .timestamp .to_offset(UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC)) .format(&Rfc3339) .unwrap_or_default(); - let size = humansize::format_size(size, humansize::BINARY); + let size = humansize::format_size(self.content.len(), humansize::BINARY); + let content_type = self.content.mime(); format!("[{content_type} {size} {timestamp}]") } #[must_use] - pub fn printable_data(&self, line_length: Option) -> String { + pub fn preview_information(&self, line_length: Option) -> String { fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, @@ -196,9 +172,6 @@ impl Entry { self.timestamp = OffsetDateTime::now_utc(); } - #[must_use] - pub fn to_clipboard_content(&self) -> ClipboardContent { self.content.clone() } - #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.content.is_empty() } @@ -229,21 +202,16 @@ impl Entry { #[inline] #[must_use] - pub const fn mime(&self) -> mime::Mime { - match self.content { - ClipboardContent::Plaintext(_) => mime::TEXT_PLAIN_UTF_8, - ClipboardContent::Image { .. } => mime::IMAGE_PNG, - } - } + pub const fn mime(&self) -> mime::Mime { self.content.mime() } #[inline] pub fn metadata(&self, preview_length: Option) -> Metadata { Metadata { - id: self.id, + id: self.id(), kind: self.clipboard_kind, timestamp: self.timestamp, mime: self.mime(), - preview: self.printable_data(preview_length), + preview: self.preview_information(preview_length), } } @@ -255,7 +223,6 @@ impl Default for Entry { let content = ClipboardContent::Plaintext(String::new()); let sha256_digest = compute_sha256_digest(&content); Self { - id: 0, content, clipboard_kind: ClipboardKind::Clipboard, timestamp: OffsetDateTime::now_utc(), diff --git a/crates/base/src/lib.rs b/crates/base/src/lib.rs index 077cd6c8..31a5f3b4 100644 --- a/crates/base/src/lib.rs +++ b/crates/base/src/lib.rs @@ -7,6 +7,8 @@ pub mod utils; mod watcher_state; use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, net::{IpAddr, Ipv4Addr}, path::PathBuf, }; @@ -99,14 +101,28 @@ impl ClipboardContent { #[inline] pub const fn is_image(&self) -> bool { matches!(&self, Self::Image { .. }) } + #[inline] + pub const fn mime(&self) -> mime::Mime { + match self { + Self::Plaintext(_) => mime::TEXT_PLAIN_UTF_8, + Self::Image { .. } => mime::IMAGE_PNG, + } + } + + #[inline] pub fn basic_information(&self) -> String { - let (content_type, size) = match &self { - Self::Plaintext(text) => (mime::TEXT_PLAIN_UTF_8, text.len()), - Self::Image { width: _, height: _, bytes } => (mime::IMAGE_PNG, bytes.len()), - }; - let size = humansize::format_size(size, humansize::BINARY); + let content_type = self.mime(); + let size = humansize::format_size(self.len(), humansize::BINARY); format!("{content_type}, {size}") } + + #[inline] + #[must_use] + pub fn id(&self) -> u64 { + let mut s = DefaultHasher::new(); + self.hash(&mut s); + s.finish() + } } impl AsRef for ClipboardContent { diff --git a/crates/clipboard/src/lib.rs b/crates/clipboard/src/lib.rs index 6ed79774..e03cf0b7 100644 --- a/crates/clipboard/src/lib.rs +++ b/crates/clipboard/src/lib.rs @@ -1,7 +1,7 @@ mod default; mod error; mod listener; -mod mock; +mod local; mod pubsub; mod traits; @@ -11,7 +11,7 @@ pub use self::{ default::Clipboard, error::Error, listener::{WaylandListenerError, X11ListenerError}, - mock::Clipboard as MockClipboard, + local::Clipboard as LocalClipboard, pubsub::Subscriber, traits::{ EventObserver, Load as ClipboardLoad, LoadExt as ClipboardLoadExt, diff --git a/crates/clipboard/src/listener/wayland/mod.rs b/crates/clipboard/src/listener/wayland/mod.rs index bbd8a739..4e1cee70 100644 --- a/crates/clipboard/src/listener/wayland/mod.rs +++ b/crates/clipboard/src/listener/wayland/mod.rs @@ -10,7 +10,7 @@ use std::{ }; use wl_clipboard_rs::paste::{ - get_contents as wl_clipboard_get_contents, Error as WaylandError, MimeType, Seat, + get_mime_types as wl_clipboard_get_mime_types, Error as WaylandError, Seat, }; pub use self::error::Error; @@ -82,10 +82,23 @@ fn build_thread( while is_running.load(Ordering::Relaxed) { tracing::trace!("Wait for readiness events"); - match wl_clipboard_get_contents(clipboard_type, Seat::Unspecified, MimeType::Any) { - Ok((_pipe, mime_type)) => { - if let Ok(mime) = mime_type.parse() { - notifier.notify_all(mime); + match wl_clipboard_get_mime_types(clipboard_type, Seat::Unspecified) { + Ok(mime_types) => { + let mut mime_types = mime_types.into_iter().collect::>(); + mime_types.sort_unstable_by_key(|format| { + if format.starts_with("image") { + 1 + } else if format.starts_with("text") { + 2 + } else { + u8::MAX + } + }); + for mime_type in mime_types { + if let Ok(mime) = mime_type.parse() { + notifier.notify_all(mime); + break; + } } continue; } diff --git a/crates/clipboard/src/listener/x11/mod.rs b/crates/clipboard/src/listener/x11/mod.rs index 2c99ebbb..600558d3 100644 --- a/crates/clipboard/src/listener/x11/mod.rs +++ b/crates/clipboard/src/listener/x11/mod.rs @@ -108,6 +108,9 @@ fn build_thread( tracing::trace!("Wait for readiness events"); if let Err(err) = poll.poll(&mut events, Some(Duration::from_millis(200))) { + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } tracing::error!( "Error occurred while polling for readiness event, error: {err}" ); @@ -125,31 +128,8 @@ fn build_thread( continue; } - // sort available formats by type, some applications provide - // image in `text/html` format, we prefer to use `image` - formats.sort_unstable_by_key(|format| { - if format.starts_with("image/png") { - 1 - } else if format.starts_with("image") { - 2 - } else if format.starts_with("text") { - 3 - } else if format == "UTF8_STRING" { - 4 - } else { - u32::MAX - } - }); - - for format in formats { - if format == "UTF8_STRING" { - notifier.notify_all(mime::TEXT_PLAIN_UTF_8); - break; - } - if let Ok(mime) = format.parse() { - notifier.notify_all(mime); - break; - } + if let Some(mime) = extract_mime(&mut formats) { + notifier.notify_all(mime); } } Err(err) => { @@ -243,3 +223,37 @@ fn try_reconnect( ); Err(Error::RetryLimitReached { value: max_retry_count }) } + +#[inline] +fn extract_mime(formats: &mut Vec) -> Option { + // sort available formats by type, some applications provide + // image in `text/html` format, we prefer to use `image` + formats.sort_unstable_by_key(|format| -> u8 { + if format.starts_with("image/png") { + 1 + } else if format.starts_with("image") { + 2 + } else if format.starts_with("text") { + 3 + } else if format == "UTF8_STRING" { + 4 + } else { + u8::MAX + } + }); + + for format in formats.iter() { + if format == "UTF8_STRING" { + return Some(mime::TEXT_PLAIN_UTF_8); + } + if let Ok(mime) = format.parse() { + return Some(mime); + } + } + + if !formats.is_empty() { + tracing::warn!("Unable to extract MIME type from {formats:?}"); + } + + None +} diff --git a/crates/clipboard/src/mock.rs b/crates/clipboard/src/local.rs similarity index 83% rename from crates/clipboard/src/mock.rs rename to crates/clipboard/src/local.rs index 7ac833d5..c9d96d94 100644 --- a/crates/clipboard/src/mock.rs +++ b/crates/clipboard/src/local.rs @@ -55,11 +55,7 @@ impl ClipboardLoad for Clipboard { match maybe_data { Ok(content) => { if let Some(mime) = mime { - let content_mime = match content { - ClipboardContent::Plaintext(_) => mime::TEXT_PLAIN_UTF_8, - ClipboardContent::Image { .. } => mime::IMAGE_PNG, - }; - (content_mime == mime).then_some(content).ok_or(Error::Empty) + (content.mime() == mime).then_some(content).ok_or(Error::Empty) } else { Ok(content) } @@ -72,10 +68,7 @@ impl ClipboardLoad for Clipboard { impl ClipboardStore for Clipboard { #[inline] fn store(&self, content: ClipboardContent) -> Result<(), Error> { - let mime = match content { - ClipboardContent::Plaintext(_) => mime::TEXT_PLAIN_UTF_8, - ClipboardContent::Image { .. } => mime::IMAGE_PNG, - }; + let mime = content.mime(); match self.data.write() { Ok(mut data) => { *data = Some(content); diff --git a/crates/clipboard/tests/mock.rs b/crates/clipboard/tests/mock.rs index 400b2053..273e3f78 100644 --- a/crates/clipboard/tests/mock.rs +++ b/crates/clipboard/tests/mock.rs @@ -1,26 +1,26 @@ -use clipcat_clipboard::{Error, MockClipboard}; +use clipcat_clipboard::{Error, LocalClipboard}; mod common; use self::common::ClipboardTester; #[derive(Debug)] -pub struct MockClipboardTester; +pub struct LocalClipboardTester; -impl Default for MockClipboardTester { +impl Default for LocalClipboardTester { fn default() -> Self { Self::new() } } -impl MockClipboardTester { +impl LocalClipboardTester { #[must_use] pub const fn new() -> Self { Self } } -impl ClipboardTester for MockClipboardTester { - type Clipboard = MockClipboard; +impl ClipboardTester for LocalClipboardTester { + type Clipboard = LocalClipboard; - fn new_clipboard(&self) -> Result { Ok(MockClipboard::new()) } + fn new_clipboard(&self) -> Result { Ok(LocalClipboard::new()) } } #[test] -fn test_mock() -> Result<(), Error> { MockClipboardTester::new().run() } +fn test_local() -> Result<(), Error> { LocalClipboardTester::new().run() } diff --git a/crates/server/src/backend/default.rs b/crates/server/src/backend/default.rs index 23619d5c..4ade043e 100644 --- a/crates/server/src/backend/default.rs +++ b/crates/server/src/backend/default.rs @@ -11,20 +11,36 @@ use crate::backend::{error, traits, Error, Result, Subscriber}; #[derive(Clone)] pub struct Backend { clipboards: Vec>, + + supported_clipboard_kinds: Vec, } impl Backend { /// # Errors - pub fn new( + pub fn new( + kinds: I, clip_filter: &Arc, event_observers: &[Arc], - ) -> Result { - let mut clipboards = Vec::with_capacity(ClipboardKind::MAX_LENGTH); - for kind in [ClipboardKind::Clipboard, ClipboardKind::Primary, ClipboardKind::Secondary] { + ) -> Result + where + I: IntoIterator, + { + let kinds = { + let mut kinds = kinds.into_iter().collect::>(); + kinds.sort_unstable(); + kinds.dedup(); + kinds + }; + let mut clipboards = Vec::with_capacity(kinds.len()); + let mut supported_clipboard_kinds = Vec::with_capacity(kinds.len()); + for kind in kinds { match Clipboard::new(kind, clip_filter.clone(), event_observers.to_vec()) .context(error::InitializeClipboardSnafu) { - Ok(clipboard) => clipboards.push(Arc::new(clipboard)), + Ok(clipboard) => { + clipboards.push(Arc::new(clipboard)); + supported_clipboard_kinds.push(kind); + } Err(err) => { if kind == ClipboardKind::Clipboard { return Err(err); @@ -34,7 +50,7 @@ impl Backend { } } - Ok(Self { clipboards }) + Ok(Self { clipboards, supported_clipboard_kinds }) } #[inline] @@ -94,6 +110,6 @@ impl traits::Backend for Backend { #[inline] fn supported_clipboard_kinds(&self) -> Vec { - (0..self.clipboards.len()).map(ClipboardKind::from).collect() + self.supported_clipboard_kinds.clone() } } diff --git a/crates/server/src/backend/mock.rs b/crates/server/src/backend/local.rs similarity index 96% rename from crates/server/src/backend/mock.rs rename to crates/server/src/backend/local.rs index 4ed4f44e..5aa946bc 100644 --- a/crates/server/src/backend/mock.rs +++ b/crates/server/src/backend/local.rs @@ -1,13 +1,13 @@ use async_trait::async_trait; use clipcat_base::ClipboardContent; -use clipcat_clipboard::{ClipboardLoad, ClipboardStore, ClipboardSubscribe, MockClipboard}; +use clipcat_clipboard::{ClipboardLoad, ClipboardStore, ClipboardSubscribe, LocalClipboard}; use snafu::ResultExt; use tokio::task; use crate::backend::{error, traits, ClipboardKind, Error, Result, Subscriber}; #[derive(Clone, Debug, Default)] -pub struct Backend(MockClipboard); +pub struct Backend(LocalClipboard); impl Backend { #[must_use] diff --git a/crates/server/src/backend/mod.rs b/crates/server/src/backend/mod.rs index de71b0a8..80d64f4a 100644 --- a/crates/server/src/backend/mod.rs +++ b/crates/server/src/backend/mod.rs @@ -1,6 +1,6 @@ mod default; mod error; -mod mock; +mod local; mod subscriber; mod traits; @@ -12,22 +12,30 @@ use clipcat_clipboard::EventObserver; use self::error::Result; pub use self::{ default::Backend as DefaultClipboardBackend, error::Error, - mock::Backend as MockClipboardBackend, subscriber::Subscriber, + local::Backend as LocalClipboardBackend, subscriber::Subscriber, traits::Backend as ClipboardBackend, }; /// # Errors -pub fn new( +pub fn new( + kinds: I, clip_filter: &Arc, event_observers: &[Arc], -) -> Result> { - Ok(Box::new(DefaultClipboardBackend::new(clip_filter, event_observers)?)) +) -> Result> +where + I: IntoIterator, +{ + Ok(Box::new(DefaultClipboardBackend::new(kinds, clip_filter, event_observers)?)) } /// # Errors -pub fn new_shared( +pub fn new_shared( + kinds: I, clip_filter: &Arc, event_observers: &[Arc], -) -> Result> { - Ok(Arc::new(DefaultClipboardBackend::new(clip_filter, event_observers)?)) +) -> Result> +where + I: IntoIterator, +{ + Ok(Arc::new(DefaultClipboardBackend::new(kinds, clip_filter, event_observers)?)) } diff --git a/crates/server/src/history/driver/fs/migrate/v1.rs b/crates/server/src/history/driver/fs/migrate/v1.rs index b5809544..8956fb07 100644 --- a/crates/server/src/history/driver/fs/migrate/v1.rs +++ b/crates/server/src/history/driver/fs/migrate/v1.rs @@ -1,11 +1,15 @@ -use std::{fs::OpenOptions, path::Path}; +use std::path::Path; use clipcat_base::ClipEntry; use snafu::ResultExt; +use tokio::fs::OpenOptions; use crate::history::{driver::fs::model, error, Error}; -pub fn load>(clips_file_path: P) -> Result, Error> { +pub async fn load

(clips_file_path: P) -> Result, Error> +where + P: AsRef + Send, +{ tracing::info!("Load clips from v1 schema"); let clips_file_path = clips_file_path.as_ref().to_path_buf(); @@ -15,12 +19,19 @@ pub fn load>(clips_file_path: P) -> Result, Error> .read(true) .append(true) .open(&clips_file_path) - .context(error::OpenFileSnafu { file_path: clips_file_path })?; + .await + .context(error::OpenFileSnafu { file_path: clips_file_path })? + .into_std() + .await; - let mut clips = Vec::new(); - while let Ok(clip) = bincode::deserialize_from::<_, model::v1::ClipboardValue>(&clips_file) { - clips.push(ClipEntry::from(clip)); - } - - Ok(clips) + tokio::task::spawn_blocking(move || { + let mut clips = Vec::new(); + while let Ok(clip) = bincode::deserialize_from::<_, model::v1::ClipboardValue>(&clips_file) + { + clips.push(ClipEntry::from(clip)); + } + Ok(clips) + }) + .await + .context(error::JoinTaskSnafu)? } diff --git a/crates/server/src/history/driver/fs/migrate/v2.rs b/crates/server/src/history/driver/fs/migrate/v2.rs index 61ca41ac..cd943b52 100644 --- a/crates/server/src/history/driver/fs/migrate/v2.rs +++ b/crates/server/src/history/driver/fs/migrate/v2.rs @@ -1,24 +1,29 @@ -use std::{ - fs::OpenOptions, - io::{Seek, SeekFrom}, - path::Path, -}; +use std::path::Path; use clipcat_base::ClipEntry; use snafu::ResultExt; use time::OffsetDateTime; +use tokio::{ + fs::OpenOptions, + io::{AsyncSeekExt, AsyncWriteExt, SeekFrom}, +}; use crate::history::{ driver::fs::{image_dir_path, image_file_path_from_digest, model}, error, Error, }; -pub fn migrate_to, Q: AsRef, R: AsRef>( +pub async fn migrate_to( file_path: P, header_file_path: Q, clips_file_path: R, clips: Vec, -) -> Result<(), Error> { +) -> Result<(), Error> +where + P: AsRef + Send, + Q: AsRef + Send, + R: AsRef + Send, +{ tracing::info!("Migrate clips to v2 schema"); let file_path = file_path.as_ref().to_path_buf(); @@ -30,6 +35,7 @@ pub fn migrate_to, Q: AsRef, R: AsRef>( .read(true) .append(false) .open(&header_file_path) + .await .with_context(|_| error::OpenFileSnafu { file_path: header_file_path.clone() })?; let clips_file_path = clips_file_path.as_ref().to_path_buf(); @@ -37,14 +43,18 @@ pub fn migrate_to, Q: AsRef, R: AsRef>( .create(true) .write(true) .read(true) - .append(true) .open(&clips_file_path) + .await .context(error::OpenFileSnafu { file_path: clips_file_path.clone() })?; - clips_file.set_len(0).context(error::TruncateFileSnafu { file_path: clips_file_path })?; + clips_file + .set_len(0) + .await + .with_context(|_| error::TruncateFileSnafu { file_path: clips_file_path.clone() })?; let image_dir_path = image_dir_path(file_path); - std::fs::create_dir_all(&image_dir_path) + tokio::fs::create_dir_all(&image_dir_path) + .await .context(error::CreateDirectorySnafu { file_path: image_dir_path.clone() })?; for clip in clips { @@ -59,26 +69,34 @@ pub fn migrate_to, Q: AsRef, R: AsRef>( continue; } }; - std::fs::write(&file_path, content) + tokio::fs::write(&file_path, content) + .await .context(error::WriteFileSnafu { file_path: file_path.clone() })?; } - bincode::serialize_into(&mut clips_file, &model::v2::ClipboardValue::from(clip)) + let content = bincode::serialize(&model::v2::ClipboardValue::from(clip)) .context(error::SeriailizeClipSnafu)?; + clips_file + .write_all(content.as_ref()) + .await + .with_context(|_| error::WriteFileSnafu { file_path: clips_file_path.clone() })?; } header_file .set_len(0) - .with_context(|_| error::TruncateFileSnafu { file_path: header_file_path })?; + .await + .with_context(|_| error::TruncateFileSnafu { file_path: header_file_path.clone() })?; + drop(header_file.seek(SeekFrom::Start(0))); - serde_json::to_writer( - &mut header_file, - &model::v2::FileHeader { - schema: model::v2::FileHeader::SCHEMA_VERSION, - last_update: OffsetDateTime::now_utc(), - }, - ) + let header_content = serde_json::to_string_pretty(&model::v2::FileHeader { + schema: model::v2::FileHeader::SCHEMA_VERSION, + last_update: OffsetDateTime::now_utc(), + }) .context(error::SeriailizeHistoryHeaderSnafu)?; + header_file + .write_all(header_content.as_bytes()) + .await + .with_context(|_| error::WriteFileSnafu { file_path: header_file_path.clone() })?; Ok(()) } diff --git a/crates/server/src/history/driver/fs/mod.rs b/crates/server/src/history/driver/fs/mod.rs index 4e0c0e2f..aa3f1fcf 100644 --- a/crates/server/src/history/driver/fs/mod.rs +++ b/crates/server/src/history/driver/fs/mod.rs @@ -2,24 +2,27 @@ mod migrate; mod model; use std::{ - fs::{File, OpenOptions}, - io::{Seek, SeekFrom, Write}, + collections::HashSet, path::{Path, PathBuf}, - sync::Arc, }; use async_trait::async_trait; use clipcat_base::{ClipEntry, ClipboardKind}; -use parking_lot::Mutex; use snafu::ResultExt; use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset}; +use tokio::{ + fs::{File, OpenOptions}, + io::{AsyncSeekExt, AsyncWriteExt, SeekFrom}, +}; use crate::history::{driver::Driver, error, Error}; const CURRENT_SCHEMA: u64 = model::v2::FileHeader::SCHEMA_VERSION; pub struct FileSystemDriver { - inner: Arc>, + file_path: PathBuf, + clips_file: File, + header_file: File, } impl FileSystemDriver { @@ -27,117 +30,42 @@ impl FileSystemDriver { where P: AsRef + Send, { - let file_path = file_path.as_ref().to_path_buf(); - let inner = tokio::task::spawn_blocking(move || Inner::new(file_path)) - .await - .context(error::JoinTaskSnafu)??; - Ok(Self { inner: Arc::new(Mutex::new(inner)) }) - } - - async fn save_inner(&mut self, clips: I) -> Result<(), Error> - where - I: IntoIterator + Send + 'static, - { - let inner = self.inner.clone(); - tokio::task::spawn_blocking(move || { - let mut driver = inner.lock(); - driver.save(clips) - }) - .await - .context(error::JoinTaskSnafu)? - } -} - -#[async_trait] -impl Driver for FileSystemDriver { - async fn load(&mut self) -> Result, Error> { - let inner = self.inner.clone(); - tokio::task::spawn_blocking(move || { - let mut driver = inner.lock(); - Ok(driver.load()) - }) - .await - .context(error::JoinTaskSnafu)? - } - - async fn save(&mut self, clips: &[ClipEntry]) -> Result<(), Error> { - self.save_inner(clips.to_vec()).await - } - - async fn clear(&mut self) -> Result<(), Error> { - let inner = self.inner.clone(); - tokio::task::spawn_blocking(move || { - let mut driver = inner.lock(); - driver.clear() - }) - .await - .context(error::JoinTaskSnafu)? - } - - async fn put(&mut self, clip: &ClipEntry) -> Result<(), Error> { - let inner = self.inner.clone(); - let clip = clip.clone(); - tokio::task::spawn_blocking(move || { - let mut driver = inner.lock(); - driver.put(&clip) - }) - .await - .context(error::JoinTaskSnafu)? - } - - async fn shrink_to(&mut self, min_capacity: usize) -> Result<(), Error> { - let inner = self.inner.clone(); - tokio::task::spawn_blocking(move || { - let mut driver = inner.lock(); - driver.shrink_to(min_capacity) - }) - .await - .context(error::JoinTaskSnafu)? - } -} - -pub struct Inner { - file_path: PathBuf, - clips_file: File, - header_file: File, -} - -impl Inner { - pub fn new>(file_path: P) -> Result { let file_path = file_path.as_ref().to_path_buf(); let header_file_path = header_file_path(&file_path); let clips_file_path = clips_file_path(&file_path); - std::fs::create_dir_all(&file_path) + tokio::fs::create_dir_all(&file_path) + .await .context(error::CreateDirectorySnafu { file_path: file_path.clone() })?; - let header_content = std::fs::read(&header_file_path) - .context(error::ReadFileSnafu { file_path: header_file_path.clone() })?; - if let Ok(model::v2::FileHeader { schema, last_update }) = - serde_json::from_slice::(&header_content) - { - tracing::info!( - "Open `{}`, schema: {schema}, last update: {last_update}", - header_file_path.display(), - last_update = last_update - .to_offset(UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC)) - .format(&Rfc3339) - .unwrap_or_default() - ); - - let clips = match schema { - schema if schema > CURRENT_SCHEMA => { - return Err(Error::NewerSchema { new: schema, current: CURRENT_SCHEMA }) - } - model::v1::FileHeader::SCHEMA_VERSION => { - tracing::info!("Clip history schema `{schema}` is out-of-date"); - Some(migrate::v1::load(&clips_file_path)?) - } - _ => None, - }; + if let Ok(header_content) = tokio::fs::read(&header_file_path).await { + if let Ok(model::v2::FileHeader { schema, last_update }) = + serde_json::from_slice::(&header_content) + { + tracing::info!( + "Open `{}`, schema: {schema}, last update: {last_update}", + header_file_path.display(), + last_update = last_update + .to_offset(UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC)) + .format(&Rfc3339) + .unwrap_or_default() + ); + + let clips = match schema { + schema if schema > CURRENT_SCHEMA => { + return Err(Error::NewerSchema { new: schema, current: CURRENT_SCHEMA }) + } + model::v1::FileHeader::SCHEMA_VERSION => { + tracing::info!("Clip history schema `{schema}` is out-of-date"); + Some(migrate::v1::load(&clips_file_path).await?) + } + _ => None, + }; - if let Some(clips) = clips { - migrate::v2::migrate_to(&file_path, &header_file_path, &clips_file_path, clips)?; + if let Some(clips) = clips { + migrate::v2::migrate_to(&file_path, &header_file_path, &clips_file_path, clips) + .await?; + } } } @@ -147,6 +75,7 @@ impl Inner { .read(true) .append(false) .open(&header_file_path) + .await .context(error::OpenFileSnafu { file_path: header_file_path })?; let clips_file = OpenOptions::new() @@ -155,126 +84,223 @@ impl Inner { .read(true) .append(true) .open(&clips_file_path) + .await .context(error::OpenFileSnafu { file_path: clips_file_path })?; Ok(Self { file_path, clips_file, header_file }) } - fn save(&mut self, clips: I) -> Result<(), Error> - where - I: IntoIterator, - { + async fn update_header(&mut self) -> Result<(), Error> { + self.header_file + .set_len(0) + .await + .context(error::TruncateFileSnafu { file_path: self.header_file_path() })?; + drop(self.header_file.seek(SeekFrom::Start(0)).await); + + let content = serde_json::to_string_pretty(&model::v2::FileHeader { + schema: model::v2::FileHeader::SCHEMA_VERSION, + last_update: OffsetDateTime::now_utc(), + }) + .context(error::SeriailizeHistoryHeaderSnafu)?; + + self.header_file + .write_all(content.as_bytes()) + .await + .context(error::WriteFileSnafu { file_path: self.header_file_path() }) + } + + async fn store_file_content(&mut self, clip: ClipEntry) -> Result<(), Error> { + let image_dir_path = self.image_dir_path(); + if clip.mime().type_() == mime::IMAGE { + let content = match clip.encoded() { + Ok(content) => content, + Err(err) => { + tracing::error!("Error occurs while encoding clip, error: {err}"); + return Ok(()); + } + }; + let file_path = image_file_path_from_digest(image_dir_path, clip.sha256_digest()); + if let Some(parent) = file_path.parent() { + tokio::fs::create_dir_all(parent) + .await + .context(error::CreateDirectorySnafu { file_path: parent.to_path_buf() })?; + } + tokio::fs::write(&file_path, content) + .await + .context(error::WriteFileSnafu { file_path })?; + } + + let content = bincode::serialize(&model::v2::ClipboardValue::from(clip)) + .context(error::SeriailizeClipSnafu)?; + self.clips_file + .write_all(content.as_ref()) + .await + .with_context(|_| error::WriteFileSnafu { file_path: self.clips_file_path() }) + } + + pub fn header_file_path(&self) -> PathBuf { header_file_path(&self.file_path) } + + pub fn clips_file_path(&self) -> PathBuf { clips_file_path(&self.file_path) } + + pub fn image_dir_path(&self) -> PathBuf { image_dir_path(&self.file_path) } +} + +#[async_trait] +impl Driver for FileSystemDriver { + async fn save(&mut self, clips: &[ClipEntry]) -> Result<(), Error> { self.clips_file .set_len(0) + .await .context(error::TruncateFileSnafu { file_path: self.clips_file_path() })?; for clip in clips { - self.store_file_content(clip)?; + self.store_file_content(clip.clone()).await?; } - drop(self.clips_file.flush()); + drop(self.clips_file.flush().await); - self.update_header() + self.update_header().await } - fn load(&mut self) -> Vec { - drop(self.clips_file.seek(SeekFrom::Start(0))); - let mut clips = Vec::new(); - while let Ok(clip) = - bincode::deserialize_from::<_, model::v2::ClipboardValue>(&self.clips_file) - { - let model::v2::ClipboardValue { timestamp, mime, data } = clip; - let data = if mime.type_() == mime::IMAGE { - let file_path = image_file_path_from_digest(&self.image_dir_path(), &data); - let maybe_data = std::fs::read(&file_path) - .context(error::ReadFileSnafu { file_path: file_path.clone() }); - match maybe_data { - Ok(data) => data, - Err(err) => { - tracing::error!("{err}"); - continue; - } - } - } else { - data - }; + async fn load(&mut self) -> Result, Error> { + let clips_file_path = self.clips_file_path(); + let clips_file = OpenOptions::new() + .create(true) + .write(true) + .read(true) + .append(true) + .open(&clips_file_path) + .await + .with_context(|_| error::OpenFileSnafu { file_path: clips_file_path })? + .into_std() + .await; + let image_dir_path = self.image_dir_path(); + + tokio::task::spawn_blocking(move || { + let mut clips = Vec::new(); - if let Ok(clip) = - ClipEntry::new(&data, &mime, ClipboardKind::Clipboard, Some(timestamp)) + while let Ok(clip) = + bincode::deserialize_from::<_, model::v2::ClipboardValue>(&clips_file) { - clips.push(clip); + let model::v2::ClipboardValue { timestamp, mime, data } = clip; + let data = if mime.type_() == mime::IMAGE { + let file_path = image_file_path_from_digest(&image_dir_path, &data); + let maybe_data = std::fs::read(&file_path) + .context(error::ReadFileSnafu { file_path: file_path.clone() }); + match maybe_data { + Ok(data) => data, + Err(err) => { + tracing::error!("{err}"); + continue; + } + } + } else { + data + }; + + if let Ok(clip) = + ClipEntry::new(&data, &mime, ClipboardKind::Clipboard, Some(timestamp)) + { + clips.push(clip); + } } - } - clips + Ok(clips) + }) + .await + .context(error::JoinTaskSnafu)? } - fn clear(&mut self) -> Result<(), Error> { - self.update_header()?; + async fn clear(&mut self) -> Result<(), Error> { + self.update_header().await?; - drop(std::fs::remove_dir_all(image_dir_path(&self.file_path))); + drop(tokio::fs::remove_dir_all(image_dir_path(&self.file_path)).await); self.clips_file .set_len(0) + .await .context(error::TruncateFileSnafu { file_path: self.clips_file_path() }) } - fn put(&mut self, clip: &ClipEntry) -> Result<(), Error> { - self.update_header()?; + async fn put(&mut self, clip: &ClipEntry) -> Result<(), Error> { + self.update_header().await?; - drop(self.clips_file.seek(SeekFrom::End(0))); - self.store_file_content(clip.clone()) + drop(self.clips_file.seek(SeekFrom::End(0)).await); + self.store_file_content(clip.clone()).await } - fn shrink_to(&mut self, min_capacity: usize) -> Result<(), Error> { - let mut saved = self.load(); + async fn shrink_to(&mut self, min_capacity: usize) -> Result<(), Error> { + drop(self.clips_file.flush().await); - saved.sort_unstable(); - saved.truncate(min_capacity); - drop(std::fs::remove_dir_all(self.image_dir_path())); - self.save(saved) - } + let clips_file_path = self.clips_file_path(); + let clips_file = OpenOptions::new() + .create(true) + .write(true) + .read(true) + .open(&clips_file_path) + .await + .with_context(|_| error::OpenFileSnafu { file_path: clips_file_path })? + .into_std() + .await; + + let mut buffer_length = 1024; + let mut clips = tokio::task::spawn_blocking(move || { + let mut clips = Vec::new(); + while let Ok(clip) = + bincode::deserialize_from::<_, model::v2::ClipboardValue>(&clips_file) + { + let serialized_size = + usize::try_from(bincode::serialized_size(&clip).unwrap_or_default()) + .unwrap_or_default(); + buffer_length = buffer_length.max(serialized_size); + clips.push(clip); + } + clips + }) + .await + .context(error::JoinTaskSnafu)?; - fn update_header(&mut self) -> Result<(), Error> { - self.header_file - .set_len(0) - .context(error::TruncateFileSnafu { file_path: self.header_file_path() })?; - drop(self.header_file.seek(SeekFrom::Start(0))); - - serde_json::to_writer( - &mut self.header_file, - &model::v2::FileHeader { - schema: model::v2::FileHeader::SCHEMA_VERSION, - last_update: OffsetDateTime::now_utc(), - }, - ) - .context(error::SeriailizeHistoryHeaderSnafu) - } + clips.sort_unstable(); + clips.truncate(min_capacity); - fn store_file_content(&mut self, clip: ClipEntry) -> Result<(), Error> { + let mut image_files = HashSet::new(); let image_dir_path = self.image_dir_path(); - if clip.mime().type_() == mime::IMAGE { - let content = match clip.encoded() { - Ok(content) => content, - Err(err) => { - tracing::error!("Error occurs while encoding clip, error: {err}"); - return Ok(()); + + self.clips_file + .set_len(0) + .await + .with_context(|_| error::TruncateFileSnafu { file_path: self.clips_file_path() })?; + + { + let mut buffer = Vec::with_capacity(buffer_length); + for clip in clips { + buffer.clear(); + bincode::serialize_into(&mut buffer, &clip).context(error::SeriailizeClipSnafu)?; + + self.clips_file.write_all(&buffer).await.with_context(|_| { + error::WriteFileSnafu { file_path: self.clips_file_path() } + })?; + + if clip.mime.type_() == mime::IMAGE { + let _ = image_files + .insert(image_file_path_from_digest(&image_dir_path, &clip.data)); } - }; - let file_path = image_file_path_from_digest(image_dir_path, clip.sha256_digest()); - if let Some(parent) = file_path.parent() { - std::fs::create_dir_all(parent) - .context(error::CreateDirectorySnafu { file_path: parent.to_path_buf() })?; } - std::fs::write(&file_path, content).context(error::WriteFileSnafu { file_path })?; } - bincode::serialize_into(&mut self.clips_file, &model::v2::ClipboardValue::from(clip)) - .context(error::SeriailizeClipSnafu) - } + drop(self.clips_file.flush().await); - pub fn header_file_path(&self) -> PathBuf { header_file_path(&self.file_path) } + let mut entries = tokio::fs::read_dir(&image_dir_path) + .await + .with_context(|_| error::ReadDirectorySnafu { dir_path: image_dir_path.clone() })?; - pub fn clips_file_path(&self) -> PathBuf { clips_file_path(&self.file_path) } + while let Ok(Some(entry)) = entries.next_entry().await { + let file_path = entry.path(); + if !image_files.contains(&file_path) { + tracing::debug!("Remove image file `{}`", file_path.display()); + drop(tokio::fs::remove_file(file_path).await); + } + } - pub fn image_dir_path(&self) -> PathBuf { image_dir_path(&self.file_path) } + self.update_header().await + } } fn header_file_path

(file_path: P) -> PathBuf diff --git a/crates/server/src/history/driver/fs/model/v2.rs b/crates/server/src/history/driver/fs/model/v2.rs index 6115d16b..3ba93573 100644 --- a/crates/server/src/history/driver/fs/model/v2.rs +++ b/crates/server/src/history/driver/fs/model/v2.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use clipcat_base::ClipEntry; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; @@ -14,7 +16,7 @@ impl FileHeader { pub const SCHEMA_VERSION: u64 = 2; } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, Serialize)] pub struct ClipboardValue { pub timestamp: OffsetDateTime, @@ -41,3 +43,15 @@ impl From for ClipboardValue { } } } + +impl PartialOrd for ClipboardValue { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +impl Ord for ClipboardValue { + fn cmp(&self, other: &Self) -> Ordering { other.timestamp.cmp(&self.timestamp) } +} + +impl PartialEq for ClipboardValue { + fn eq(&self, other: &Self) -> bool { self.data == other.data } +} diff --git a/crates/server/src/history/driver/mod.rs b/crates/server/src/history/driver/mod.rs index de2552e2..233959fa 100644 --- a/crates/server/src/history/driver/mod.rs +++ b/crates/server/src/history/driver/mod.rs @@ -10,11 +10,11 @@ use crate::history::Error; pub trait Driver: Send + Sync { async fn load(&mut self) -> Result, Error>; - async fn save(&mut self, data: &[ClipEntry]) -> Result<(), Error>; + async fn save(&mut self, clip_entry: &[ClipEntry]) -> Result<(), Error>; async fn clear(&mut self) -> Result<(), Error>; - async fn put(&mut self, data: &ClipEntry) -> Result<(), Error>; + async fn put(&mut self, clip_entry: &ClipEntry) -> Result<(), Error>; async fn shrink_to(&mut self, min_capacity: usize) -> Result<(), Error>; diff --git a/crates/server/src/history/error.rs b/crates/server/src/history/error.rs index 59626032..f965a23b 100644 --- a/crates/server/src/history/error.rs +++ b/crates/server/src/history/error.rs @@ -26,6 +26,9 @@ pub enum Error { #[snafu(display("Failed to read file {}, error: {source}", file_path.display()))] ReadFile { source: std::io::Error, file_path: PathBuf }, + #[snafu(display("Failed to read directory {}, error: {source}", dir_path.display()))] + ReadDirectory { source: std::io::Error, dir_path: PathBuf }, + #[snafu(display("Failed to serialize clip, error: {source}"))] SeriailizeClip { source: bincode::Error }, diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 85559ce4..e38af525 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -57,9 +57,12 @@ pub async fn serve_with_shutdown( desktop_notification_config.long_plaintext_length, ); - let clipboard_backend = - backend::new_shared(&clip_filter, &[Arc::new(desktop_notification.clone())]) - .context(error::CreateClipboardBackendSnafu)?; + let clipboard_backend = backend::new_shared( + watcher_opts.clipboard_kinds(), + &clip_filter, + &[Arc::new(desktop_notification.clone())], + ) + .context(error::CreateClipboardBackendSnafu)?; let (clipboard_manager, history_manager) = { tracing::info!("History file path: `{path}`", path = history_file_path.display()); diff --git a/crates/server/src/manager/mod.rs b/crates/server/src/manager/mod.rs index d3af4145..a893a53b 100644 --- a/crates/server/src/manager/mod.rs +++ b/crates/server/src/manager/mod.rs @@ -211,7 +211,7 @@ where if let Some(clip) = self.clips.get_mut(&id) { clip.mark(clipboard_kind); self.backend - .store(clipboard_kind, clip.to_clipboard_content()) + .store(clipboard_kind, clip.as_ref().clone()) .await .context(error::StoreClipboardContentSnafu)?; } @@ -230,9 +230,9 @@ mod tests { use clipcat_base::{ClipEntry, ClipboardKind}; use crate::{ - backend::MockClipboardBackend, + backend::LocalClipboardBackend, manager::{ClipboardManager, DEFAULT_CAPACITY}, - notification::MockNotification, + notification::DummyNotification, }; fn create_clips(n: usize) -> Vec { @@ -241,8 +241,8 @@ mod tests { #[test] fn test_construction() { - let notification = MockNotification::default(); - let backend = Arc::new(MockClipboardBackend::new()); + let notification = DummyNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); let mgr = ClipboardManager::new(backend, notification); assert!(mgr.is_empty()); assert_eq!(mgr.len(), 0); @@ -251,7 +251,7 @@ mod tests { assert!(mgr.get_current_clip(ClipboardKind::Primary).is_none()); let cap = 20; - let backend = Arc::new(MockClipboardBackend::new()); + let backend = Arc::new(LocalClipboardBackend::new()); let mgr = ClipboardManager::with_capacity(backend, cap, notification); assert!(mgr.is_empty()); assert_eq!(mgr.len(), 0); @@ -262,8 +262,8 @@ mod tests { #[test] fn test_capacity() { - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let cap = 10; let mut mgr = ClipboardManager::with_capacity(backend, cap, notification); assert_eq!(mgr.len(), 0); @@ -297,8 +297,8 @@ mod tests { fn test_insert() { let n = 20; let clips = create_clips(n); - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let mut mgr = ClipboardManager::new(backend, notification); for clip in &clips { let _ = mgr.insert(clip.clone()); @@ -318,8 +318,8 @@ mod tests { fn test_import() { let n = 10; let mut clips = create_clips(n); - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let mut mgr = ClipboardManager::with_capacity(backend, 20, notification); mgr.import(&clips); @@ -343,8 +343,8 @@ mod tests { let data1 = "ABCDEFG"; let data2 = "АБВГД"; let clip = ClipEntry::new(data1.as_bytes(), &MIME, ClipboardKind::Clipboard, None).unwrap(); - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let mut mgr = ClipboardManager::new(backend, notification); let old_id = mgr.insert(clip); assert_eq!(mgr.len(), 1); @@ -361,8 +361,8 @@ mod tests { #[test] fn test_remove() { - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let mut mgr = ClipboardManager::new(backend, notification); assert_eq!(mgr.len(), 0); assert!(!mgr.remove(43)); @@ -385,8 +385,8 @@ mod tests { #[test] fn test_clear() { - let backend = Arc::new(MockClipboardBackend::new()); - let notification = MockNotification::default(); + let backend = Arc::new(LocalClipboardBackend::new()); + let notification = DummyNotification::default(); let n = 20; let clips = create_clips(n); let mut mgr = ClipboardManager::new(backend, notification); diff --git a/crates/server/src/notification/mock.rs b/crates/server/src/notification/dummy.rs similarity index 100% rename from crates/server/src/notification/mock.rs rename to crates/server/src/notification/dummy.rs diff --git a/crates/server/src/notification/mod.rs b/crates/server/src/notification/mod.rs index 785e3805..c0f46a16 100644 --- a/crates/server/src/notification/mod.rs +++ b/crates/server/src/notification/mod.rs @@ -1,9 +1,9 @@ mod desktop; -mod mock; +mod dummy; mod traits; pub use self::{ desktop::{Notification as DesktopNotification, Worker as DesktopNotificationWorker}, - mock::Notification as MockNotification, + dummy::Notification as DummyNotification, traits::Notification, }; diff --git a/crates/server/src/watcher/options.rs b/crates/server/src/watcher/options.rs index 459e1c27..22d1c963 100644 --- a/crates/server/src/watcher/options.rs +++ b/crates/server/src/watcher/options.rs @@ -13,6 +13,8 @@ pub struct Options { pub enable_primary: bool, + pub enable_secondary: bool, + pub capture_image: bool, pub filter_text_min_length: usize, @@ -38,6 +40,21 @@ impl Options { filter.add_sensitive_atoms(self.sensitive_x11_atoms.clone()); Ok(filter) } + + #[must_use] + pub fn clipboard_kinds(&self) -> Vec { + let mut kinds = Vec::with_capacity(ClipboardKind::MAX_LENGTH); + if self.enable_clipboard { + kinds.push(ClipboardKind::Clipboard); + } + if self.enable_primary { + kinds.push(ClipboardKind::Primary); + } + if self.enable_secondary { + kinds.push(ClipboardKind::Secondary); + } + kinds + } } impl Default for Options { @@ -46,6 +63,7 @@ impl Default for Options { load_current: true, enable_clipboard: true, enable_primary: true, + enable_secondary: false, capture_image: true, filter_text_min_length: 1, filter_text_max_length: 5 * (1 << 20), diff --git a/flake.lock b/flake.lock index 8226b79a..3ed89425 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1701484200, - "narHash": "sha256-TwMMvkbNozyHmfu/rtItEMscOGT6gwe/zQ++NWeZl1Y=", + "lastModified": 1702488130, + "narHash": "sha256-Bz4KTuBARAQY8952CpmYVD9o/LoScYjdw8KrK2OjEoA=", "owner": "ipetkov", "repo": "crane", - "rev": "216e37209b788e68b895c5f1a1d43f6a8206eef2", + "rev": "33dbb6a8342e1cf6252c8976d02ff8a7632aa071", "type": "github" }, "original": { @@ -28,11 +28,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1701498074, - "narHash": "sha256-UNYTZtBYa/4G5+dRzNNXNcEi1RVm6yOUQNHYkcRag2Q=", + "lastModified": 1702534966, + "narHash": "sha256-jMJ54aI5xk5vpoGIKFHw0HMOIS8a03tZ46gBtW+ghIk=", "owner": "nix-community", "repo": "fenix", - "rev": "ce8747b0d8d6605264651f9ab5c84db6d7a56728", + "rev": "aa8f8228044e2fc224ceb9f751b5e0d5b580745a", "type": "github" }, "original": { @@ -46,11 +46,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", "type": "github" }, "original": { @@ -61,11 +61,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1701253981, - "narHash": "sha256-ztaDIyZ7HrTAfEEUt9AtTDNoCYxUdSd6NrRHaYOIxtk=", + "lastModified": 1702312524, + "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e92039b55bcd58469325ded85d4f58dd5a4eaf58", + "rev": "a9bf124c46ef298113270b1f84a164865987a91c", "type": "github" }, "original": { @@ -86,11 +86,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1701447636, - "narHash": "sha256-WaCcxLNAqo/FAK0QtYqweKCUVTGcbKpFIHClc+k2YlI=", + "lastModified": 1702503018, + "narHash": "sha256-KTz3cZL2NypvIPb594j9GUizhifhfX6BJ1ks58fTWbM=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "e402c494b7c7d94a37c6d789a216187aaf9ccd3e", + "rev": "dd07f1f2fbfd7e6ea581240af07131a1b7368b0f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 23fec1f8..ed6b6642 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ outputs = { self, nixpkgs, flake-utils, fenix, crane }: let name = "clipcat"; - version = "0.14.0"; + version = "0.15.0"; in (flake-utils.lib.eachDefaultSystem (system: