diff --git a/Cargo.lock b/Cargo.lock index 4830ca9..447ff84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,15 +28,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "aho-corasick" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" -dependencies = [ - "memchr", -] - [[package]] name = "anyhow" version = "1.0.44" @@ -95,7 +86,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -112,7 +103,7 @@ checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -198,6 +189,12 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -214,7 +211,7 @@ dependencies = [ "num-integer", "num-traits", "time", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -284,7 +281,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -294,7 +291,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crossbeam-utils", ] @@ -304,10 +301,35 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] +[[package]] +name = "crossterm" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio 0.7.13", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi 0.3.9", +] + +[[package]] +name = "crossterm_winapi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "darling" version = "0.12.4" @@ -413,7 +435,7 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -429,16 +451,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] -name = "env_logger" -version = "0.9.0" +name = "filetime" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.2.10", + "winapi 0.3.9", ] [[package]] @@ -479,6 +500,41 @@ dependencies = [ "syn", ] +[[package]] +name = "fsevent" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +dependencies = [ + "bitflags", + "fsevent-sys", +] + +[[package]] +name = "fsevent-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +dependencies = [ + "libc", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "funty" version = "1.1.0" @@ -606,7 +662,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] @@ -617,7 +673,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.10.0+wasi-snapshot-preview1", ] @@ -723,12 +779,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.13" @@ -781,15 +831,13 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5fc8f41dbaa9c8492a96c8afffda4f76896ee041d6a57606e70581b80c901f" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" dependencies = [ "bitflags", - "futures-core", "inotify-sys", "libc", - "tokio", ] [[package]] @@ -807,7 +855,16 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", ] [[package]] @@ -834,12 +891,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.7.6" @@ -848,7 +921,7 @@ checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec", "bitflags", - "cfg-if", + "cfg-if 1.0.0", "ryu", "static_assertions", ] @@ -885,7 +958,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -916,6 +989,25 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.2", + "net2", + "slab", + "winapi 0.2.8", +] + [[package]] name = "mio" version = "0.7.13" @@ -924,9 +1016,33 @@ checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" dependencies = [ "libc", "log", - "miow", + "miow 0.3.7", "ntapi", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +dependencies = [ + "lazycell", + "log", + "mio 0.6.23", + "slab", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", ] [[package]] @@ -935,7 +1051,18 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", ] [[package]] @@ -962,13 +1089,31 @@ dependencies = [ "version_check", ] +[[package]] +name = "notify" +version = "4.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +dependencies = [ + "bitflags", + "filetime", + "fsevent", + "fsevent-sys", + "inotify", + "libc", + "mio 0.6.23", + "mio-extras", + "walkdir", + "winapi 0.3.9", +] + [[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1034,8 +1179,8 @@ dependencies = [ "derivative", "futures", "hyper", - "inotify", "log", + "notify", "panorama-imap", "panorama-smtp", "serde", @@ -1071,24 +1216,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "panorama-mbsync" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bitflags", - "clap", - "derivative", - "derive_builder", - "env_logger", - "futures", - "log", - "panorama-imap", - "serde", - "tokio", -] - [[package]] name = "panorama-proto-common" version = "0.0.1" @@ -1111,6 +1238,13 @@ dependencies = [ "panorama-proto-common", ] +[[package]] +name = "panorama-tui" +version = "0.1.0" +dependencies = [ + "crossterm", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -1128,12 +1262,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall 0.2.10", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1246,29 +1380,12 @@ dependencies = [ "rust-argon2", ] -[[package]] -name = "regex" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -[[package]] -name = "regex-syntax" -version = "0.6.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" - [[package]] name = "ring" version = "0.16.20" @@ -1281,7 +1398,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1321,6 +1438,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[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 = "scopeguard" version = "1.1.0" @@ -1376,12 +1502,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", "opaque-debug", ] +[[package]] +name = "signal-hook" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" +dependencies = [ + "libc", + "mio 0.7.13", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1393,9 +1540,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" @@ -1616,7 +1763,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1644,21 +1791,21 @@ dependencies = [ "bytes", "libc", "memchr", - "mio", + "mio 0.7.13", "num_cpus", "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", "tokio-macros", - "winapi", + "winapi 0.3.9", ] [[package]] name = "tokio-macros" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154794c8f499c2619acd19e839294703e9e32e7630ef5f46ea80d4ef0fbee5eb" +checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" dependencies = [ "proc-macro2", "quote", @@ -1722,7 +1869,7 @@ version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "pin-project-lite", "tracing-core", ] @@ -1823,6 +1970,17 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -1851,7 +2009,7 @@ version = "0.2.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -1938,6 +2096,12 @@ dependencies = [ "web-sys", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1948,6 +2112,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1960,7 +2130,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1969,6 +2139,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "wyz" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index b477b33..9293d78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,11 @@ members = [ "daemon", "imap", - "smtp", + # "mbsync", "proto-common", - "mbsync", + "smtp", + "tui", ] [profile.release] -lto = true \ No newline at end of file +lto = true diff --git a/daemon/.gitignore b/daemon/.gitignore new file mode 100644 index 0000000..f31a51d --- /dev/null +++ b/daemon/.gitignore @@ -0,0 +1 @@ +/test.db* diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index e269020..5df3674 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -3,6 +3,10 @@ name = "panorama-daemon" version = "0.0.1" edition = "2018" +[features] +default = ["config-watch"] +config-watch = ["notify"] + [dependencies] anyhow = { version = "1.0.42", features = ["backtrace"] } async-trait = "0.1.50" @@ -10,7 +14,6 @@ clap = "3.0.0-beta.2" derivative = "2.2.0" futures = "0.3.16" hyper = { version = "0.14.11", features = ["server", "http2", "stream"] } -inotify = { version = "0.9.3", features = ["stream"] } log = "0.4.14" panorama-imap = { path = "../imap" } panorama-smtp = { path = "../smtp" } @@ -21,6 +24,8 @@ tokio-rustls = "0.22.0" toml = "0.5.8" xdg = "2.2.0" +notify = { version = "4.0.17", optional = true } + [dependencies.sqlx] version = "0.5.9" features = ["runtime-tokio-rustls", "sqlite", "json", "chrono"] diff --git a/daemon/migrations/20211013053733_initial.up.sql b/daemon/migrations/20211013053733_initial.up.sql index e3bbd39..1a8af5d 100644 --- a/daemon/migrations/20211013053733_initial.up.sql +++ b/daemon/migrations/20211013053733_initial.up.sql @@ -1,5 +1,5 @@ CREATE TABLE "accounts" ( - "id" PRIMARY KEY AUTOINCREMENT + "id" INTEGER PRIMARY KEY AUTOINCREMENT ); CREATE TABLE "mailboxes" ( diff --git a/daemon/src/config/mod.rs b/daemon/src/config/mod.rs index 516ba09..65059b1 100644 --- a/daemon/src/config/mod.rs +++ b/daemon/src/config/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "config-watch")] mod watcher; use std::collections::HashMap; @@ -7,7 +8,8 @@ use std::path::{Path, PathBuf}; use anyhow::Result; -pub use self::watcher::spawn_config_watcher_system; +#[cfg(feature = "config-watch")] +pub use self::watcher::{spawn_config_watcher_system, ConfigWatcher}; /// Configuration #[derive(Default, Serialize, Deserialize, Clone, Debug)] diff --git a/daemon/src/config/watcher.rs b/daemon/src/config/watcher.rs index 51f1bf8..4a03b13 100644 --- a/daemon/src/config/watcher.rs +++ b/daemon/src/config/watcher.rs @@ -1,10 +1,17 @@ use std::fs; use std::path::{Path, PathBuf}; +use std::sync::mpsc as stdmpsc; +use std::time::Duration; use anyhow::{Context, Result}; -use futures::{future::TryFutureExt, stream::StreamExt}; -use inotify::{Inotify, WatchMask}; -use tokio::{sync::watch, task::JoinHandle}; +use futures::future::TryFutureExt; +use notify::{ + watcher, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher, +}; +use tokio::{ + sync::{mpsc, watch}, + task::JoinHandle, +}; use xdg::BaseDirectories; use super::Config; @@ -16,33 +23,34 @@ pub type ConfigWatcher = watch::Receiver; /// config update events. pub fn spawn_config_watcher_system() -> Result<(JoinHandle<()>, ConfigWatcher)> { - let mut inotify = Inotify::init()?; + let (tx, rx) = stdmpsc::channel(); + let mut dir_watcher = watcher(tx, Duration::from_secs(10))?; + let xdg = BaseDirectories::new()?; let config_home = xdg.get_config_home().join("panorama"); if !config_home.exists() { fs::create_dir_all(&config_home)?; } - inotify - .add_watch(&config_home, WatchMask::CLOSE_WRITE) + dir_watcher + .watch(&config_home, RecursiveMode::Recursive) .context("adding watch for config home")?; debug!("watching {:?}", config_home); let (config_tx, config_update) = watch::channel(Config::default()); let handle = tokio::spawn( - start_inotify_stream(inotify, config_home, config_tx) + start_notify_stream(dir_watcher, rx, config_home, config_tx) .unwrap_or_else(|_err| todo!()), ); Ok((handle, config_update)) } -async fn start_inotify_stream( - mut inotify: Inotify, +async fn start_notify_stream( + mut watcher: RecommendedWatcher, + rx: stdmpsc::Receiver, config_home: impl AsRef, config_tx: watch::Sender, ) -> Result<()> { - let mut buffer = vec![0u8; 1024]; - let mut event_stream = inotify.event_stream(&mut buffer)?; let config_home = config_home.as_ref().to_path_buf(); let config_path = config_home.join("panorama.toml"); @@ -53,33 +61,42 @@ async fn start_inotify_stream( } debug!("listening for inotify events"); - while let Some(v) = event_stream.next().await { - let event = v.context("event")?; - debug!("inotify event: {:?}", event); - if let Some(name) = event.name { - let path = PathBuf::from(name); - let path_c = config_home - .clone() - .join(path.clone()) - .canonicalize() - .context("osu")?; - if !path_c.exists() { - debug!("path {:?} doesn't exist", path_c); - continue; - } - // TODO: any better way to do this? - let config_path_c = - config_path.canonicalize().context("cfg_path")?; - if config_path_c != path_c { - debug!("did not match {:?} {:?}", config_path_c, path_c); - continue; + loop { + let event = rx.recv()?; + debug!("notify event: {:?}", event); + + match event { + DebouncedEvent::NoticeRemove(_) + | DebouncedEvent::NoticeWrite(_) => { + // TODO: should this be handled somehow? + // neovim sends these since it's writing to a temp buffer } - debug!("reading config from {:?}", path_c); - let config = Config::from_file(path_c).await.context("read")?; - // debug!("sending config {:?}", config); - config_tx.send(config)?; + DebouncedEvent::Create(path) | DebouncedEvent::Write(path) => { + let path_c = config_home + .clone() + .join(path.clone()) + .canonicalize() + .context("osu")?; + if !path_c.exists() { + debug!("path {:?} doesn't exist", path_c); + continue; + } + // TODO: any better way to do this? + let config_path_c = + config_path.canonicalize().context("cfg_path")?; + if config_path_c != path_c { + debug!("did not match {:?} {:?}", config_path_c, path_c); + continue; + } + + debug!("reading config from {:?}", path_c); + let config = Config::from_file(path_c).await.context("read")?; + // debug!("sending config {:?}", config); + config_tx.send(config)?; + } + + _ => {} } } - Ok(()) } diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 12f7149..bf632e5 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -9,7 +9,3 @@ extern crate derivative; pub mod config; pub mod mail; - -use sqlx::migrate::Migrator; - -static MIGRATOR: Migrator = sqlx::migrate!(); diff --git a/daemon/src/mail/mod.rs b/daemon/src/mail/mod.rs index b1217f6..568d886 100644 --- a/daemon/src/mail/mod.rs +++ b/daemon/src/mail/mod.rs @@ -11,12 +11,16 @@ use panorama_imap::{ response::{MailboxData, Response}, }, }; +use sqlx::migrate::Migrator; use tokio::sync::mpsc::UnboundedSender; use crate::config::{ImapAuth, MailAccountConfig, TlsMethod}; -use self::store::MailStore; +pub use self::store::MailStore; +static MIGRATOR: Migrator = sqlx::migrate!(); + +#[derive(Debug)] pub enum MailEvent {} /// The main function for the IMAP syncing thread @@ -26,7 +30,9 @@ pub async fn sync_main( _mail2ui_tx: UnboundedSender, _mail_store: MailStore, ) -> Result<()> { - let _acct_name = acct_name.as_ref().to_owned(); + let acct_name = acct_name.as_ref(); + debug!("Starting main synchronization procedure for {}", acct_name); + // loop ensures that the connection is retried after it dies loop { let client = ConfigBuilder::default() @@ -36,16 +42,22 @@ pub async fn sync_main( .open() .await .map_err(|err| anyhow!("err: {}", err))?; + debug!("Connected to {}:{}.", &acct.imap.server, acct.imap.port); - debug!("connected to {}:{}", &acct.imap.server, acct.imap.port); + debug!("TLS Upgrade option: {:?}", acct.imap.tls); let unauth = if matches!(acct.imap.tls, TlsMethod::Starttls) { debug!("attempting to upgrade"); - let client = client.upgrade().await?; + let client = client + .upgrade() + .await + .context("could not upgrade connection")?; debug!("upgrade successful"); client } else { + warn!("Continuing with unencrypted connection!"); client }; + debug!("preparing to auth"); // check if the authentication method is supported let mut authed = match &acct.imap.auth { @@ -57,12 +69,14 @@ pub async fn sync_main( unauth.auth(login).await? } }; + debug!("authentication successful!"); let folder_list = authed.list().await?; // let _ = mail2ui_tx.send(MailEvent::FolderList( // acct_name.clone(), // folder_list.clone(), // )); + debug!("mailbox list: {:?}", folder_list); for folder in folder_list.iter() { debug!("folder: {:?}", folder); @@ -102,10 +116,12 @@ pub async fn sync_main( } } tokio::time::sleep(std::time::Duration::from_secs(50)).await; + // TODO: remove this later // continue; // let's just select INBOX for now, maybe have a config for default // mailbox later? + debug!("selecting the INBOX mailbox"); let select = authed.select("INBOX").await?; debug!("select result: {:?}", select); @@ -127,8 +143,10 @@ pub async fn sync_main( // attrs); TODO: probably odn't care about this? // let _ = mail2ui_tx.send(evt); } - // check if IDLE is supported + + // TODO: check if IDLE is supported let supports_idle = true; // authed.has_capability("IDLE").await?; + if supports_idle { let mut idle_stream = authed.idle().await?; loop { @@ -136,6 +154,7 @@ pub async fn sync_main( Some(v) => v, None => break, }; + debug!("got an event: {:?}", evt); match evt { Response::MailboxData(MailboxData::Exists(uid)) => { diff --git a/daemon/src/mail/store.rs b/daemon/src/mail/store.rs index fd5ebcb..af0572f 100644 --- a/daemon/src/mail/store.rs +++ b/daemon/src/mail/store.rs @@ -1,7 +1,7 @@ use anyhow::Result; use sqlx::sqlite::{SqlitePool, SqlitePoolOptions}; -use crate::MIGRATOR; +use super::MIGRATOR; pub struct MailStore { pool: SqlitePool, diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 6dfa19d..c6ed7e3 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -3,16 +3,23 @@ extern crate log; #[macro_use] extern crate futures; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Clap; use futures::future::{ select, Either::{Left, Right}, FutureExt, }; -use panorama_daemon::config::{self, Config, MailAccountConfig, TlsMethod}; +use panorama_daemon::{ + config::{Config, MailAccountConfig, TlsMethod}, + mail::{sync_main, MailStore}, +}; use panorama_imap::client::ConfigBuilder; -use tokio::sync::oneshot; +use tokio::{ + fs::{self, OpenOptions}, + sync::{mpsc, oneshot}, +}; +use xdg::BaseDirectories; type ExitListener = oneshot::Receiver<()>; @@ -34,31 +41,68 @@ async fn main() -> Result<()> { stderrlog::new() .module(module_path!()) + .module("panorama_daemon") + .module("panorama_imap") .verbosity(opt.verbose) .init() .unwrap(); - let (_, mut config_watcher) = config::spawn_config_watcher_system()?; + // if we're using a config-watcher, then start the watcher system + #[cfg(feature = "config-watch")] + { + use panorama_daemon::config; - loop { - let (exit_tx, exit_rx) = oneshot::channel(); - let new_config = config_watcher.borrow().clone(); - tokio::spawn(run_with_config(new_config, exit_rx)); + let (_, mut config_watcher) = config::spawn_config_watcher_system()?; - // wait till the config has changed, then tell the current thread to - // stop - config_watcher.changed().await?; - let _ = exit_tx.send(()); + loop { + let (exit_tx, exit_rx) = oneshot::channel(); + let new_config = config_watcher.borrow().clone(); + tokio::spawn(run_with_config(new_config, exit_rx)); + + // wait till the config has changed, then tell the current thread to + // stop + config_watcher.changed().await?; + let _ = exit_tx.send(()); + } + } + + // TODO: handle SIGHUP on Unix? pretty common for systemd to send this when + // reloading config files + + // if not, just read the config once and run the daemon + #[cfg(not(feature = "config-watch"))] + { + let xdg = BaseDirectories::new()?; + let config_home = xdg.get_config_home().join("panorama"); + if !config_home.exists() { + fs::create_dir_all(&config_home).await?; + } + + let config_path = config_home.join("panorama.toml"); + let config_path_c = config_path.canonicalize().context("cfg_path")?; + let config = Config::from_file(config_path_c).await.context("read")?; + + let (_, exit_rx) = oneshot::channel(); + run_with_config(config, exit_rx).await } } async fn run_with_config(config: Config, exit: ExitListener) -> Result<()> { - debug!("new config"); + debug!("New config: {:?}", config); + // keep track of which threads need to be stopped when this function is + // stopped let mut notify_mail_threads = Vec::new(); + for (account_name, account) in config.mail_accounts { let (exit_tx, exit_rx) = oneshot::channel(); - tokio::spawn(run_single_mail_account(account_name, account, exit_rx)); + tokio::spawn(async { + match run_single_mail_account(account_name, account, exit_rx).await + { + Ok(_) => {} + Err(err) => panic!("failed: {:?}", err), + } + }); notify_mail_threads.push(exit_tx); } @@ -79,30 +123,59 @@ async fn run_single_mail_account( account: MailAccountConfig, exit: ExitListener, ) -> Result<()> { - debug!("connecting to account {}", account_name); - - // set up the connection - let mut builder = ConfigBuilder::default(); - let imap_cookie = builder - .hostname(account.imap.server.clone()) - .port(account.imap.port) - .tls(matches!(account.imap.tls, TlsMethod::On)) - .open(); - - pin_mut!(imap_cookie); - pin_mut!(exit); - - let (imap, exit) = match select(imap_cookie, exit).await { - Left(res) => res, - Right(_) => return Ok(()), - }; - - debug!("connected to {}", account.imap.server); - let _imap = imap?; + debug!("run_single_mail_account({}, {:?})", account_name, account); let mut exit = exit.fuse(); + let xdg = BaseDirectories::new()?; + let data_home = xdg.get_data_home().join("panorama"); + if !data_home.exists() { + fs::create_dir_all(&data_home) + .await + .context("could not create config directory")?; + } + let db_path = data_home.join("db.sqlite3"); + debug!("Opening database at path: {:?}", db_path); + if !db_path.exists() { + OpenOptions::new() + .create(true) + .write(true) + .open(&db_path) + .await + .context("could not touch db path")?; + } + let db_path = db_path + .canonicalize() + .context("could not canonicalize db path")?; + let store = + MailStore::open(format!("sqlite://{}", db_path.to_string_lossy())) + .await + .context("couldn't open mail store")?; + + let (tx, mut rx) = mpsc::unbounded_channel(); + + let sync_fut = sync_main(&account_name, account, tx, store).fuse(); + pin_mut!(sync_fut); + + debug!("Mail account loop for {}.", account_name); loop { select! { + res = sync_fut => match res { + Ok(_) => {}, + Err(err) => { + error!("sync_main died with: {:?}", err); + break; + } + }, + + evt_opt = rx.recv().fuse() => { + let evt = match evt_opt { + Some(evt) => evt, + None => break, + }; + + debug!("Event: {:?}", evt); + }, + // we're being told to exit the loop _ = exit => break, } diff --git a/imap/src/client/inner.rs b/imap/src/client/inner.rs index 9160bfe..d383624 100644 --- a/imap/src/client/inner.rs +++ b/imap/src/client/inner.rs @@ -4,7 +4,7 @@ use std::sync::{ Arc, }; -use anyhow::Result; +use anyhow::{Context, Result}; use futures::{ future::{self, FutureExt, TryFutureExt}, stream::StreamExt, @@ -134,8 +134,17 @@ where &mut self, cap: impl AsRef, ) -> Result { - let cap_bytes = cap.as_ref().as_bytes().to_vec(); - let (_, cap) = parse_capability(Bytes::from(cap_bytes))?; + let mut cap_slice = cap.as_ref().as_bytes().to_vec(); + + // since we're doing incremental parsing, we have to finish this off + // with something that's invalid + cap_slice.push(b'\n'); + + let cap_bytes = Bytes::from(cap_slice); + trace!("CAP_BYTES: {:?}", cap_bytes); + + let (_, cap) = parse_capability(cap_bytes) + .context("could not parse capability")?; let contains = { let read = self.capabilities.read().await; @@ -145,10 +154,21 @@ where std::mem::drop(read); let cmd = self.execute(Command::Capability).await?; - let done = cmd.done().await?; - todo!("done: {:?}", done); + let (done, res) = cmd.wait().await?; - // todo!() + let mut capabilities = HashSet::new(); + // todo!("done: {:?} {:?}", done, res); + + for caps in res.iter().filter_map(|res| match res { + Response::Capabilities(caps) => Some(caps), + _ => None, + }) { + capabilities.extend(caps.clone()); + } + + let mut write = self.capabilities.write().await; + *write = Some(capabilities); + true } }; @@ -160,28 +180,48 @@ where // check that this capability exists // if it doesn't exist, then it's not an IMAP4-compliant server - if !self.has_capability("STARTTLS").await? { + if !self + .has_capability("STARTTLS") + .await + .context("could not check starttls capability")? + { bail!("Server does not have the STARTTLS capability"); } // issue the STARTTLS command to the server - let resp = self.execute(Command::Starttls).await?; - dbg!(resp.wait().await?); + let resp = self + .execute(Command::Starttls) + .await + .context("could not send starttls command")?; + dbg!(resp + .wait() + .await + .context("could not receive starttls response")?); debug!("received OK from server"); // issue exit to the read loop and retrieve the read half let _ = self.read_exit.send(()); - let read_half = self.read_handle.await?; + let read_half = self + .read_handle + .await + .context("could not retrieve read half of connection")?; // issue exit to the write loop and retrieve the write half let _ = self.write_exit.send(()); - let write_half = self.write_handle.await?; + let write_half = self + .write_handle + .await + .context("could not retrieve write half of connection")?; // put the read half and write half back together let stream = read_half.unsplit(write_half); - let tls_stream = wrap_tls(stream, &self.config.hostname).await?; + let tls_stream = wrap_tls(stream, &self.config.hostname) + .await + .context("could not initialize tls stream")?; - Inner::new(tls_stream, self.config).await + Inner::new(tls_stream, self.config) + .await + .context("could not construct new client") } pub async fn wait_for_greeting(&mut self) -> Result<()> { diff --git a/imap/src/proto/response.rs b/imap/src/proto/response.rs index 3272629..4dcef75 100644 --- a/imap/src/proto/response.rs +++ b/imap/src/proto/response.rs @@ -62,6 +62,7 @@ pub enum Response { impl DisplayBytes for Response { fn display_bytes(&self, w: &mut dyn Write) -> io::Result<()> { + #[allow(unreachable_patterns)] match self { Response::Capabilities(caps) => { write_bytes!(w, b"CAPABILITY")?; @@ -72,7 +73,7 @@ impl DisplayBytes for Response { } Response::Continue(cont) => write_bytes!(w, b"+ {}\r\n", cont), Response::Condition(cond) => write_bytes!(w, b"* {}\r\n", cond), - Response::Done(done) => write_bytes!(w, b""), + Response::Done(_) => write_bytes!(w, b""), Response::MailboxData(data) => write_bytes!(w, b"* {}\r\n", data), Response::Fetch(n, attrs) => { write_bytes!(w, b"{} FETCH (", n)?; @@ -127,7 +128,7 @@ pub enum MessageAttribute { } impl DisplayBytes for MessageAttribute { - fn display_bytes(&self, w: &mut dyn Write) -> io::Result<()> { + fn display_bytes(&self, _: &mut dyn Write) -> io::Result<()> { match self { _ => todo!(), } @@ -245,7 +246,7 @@ pub enum UidSetMember { Uid(u32), } -#[derive(Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] pub enum Capability { Imap4rev1, diff --git a/mbsync/Cargo.toml b/mbsync/Cargo.toml index 5bf3bf0..484a602 100644 --- a/mbsync/Cargo.toml +++ b/mbsync/Cargo.toml @@ -9,7 +9,6 @@ license = "GPL-3.0-or-later" categories = ["email"] repository = "https://git.mzhang.io/michael/panorama" readme = "README.md" -workspace = ".." [dependencies] anyhow = { version = "1.0.42", features = ["backtrace"] } diff --git a/tui/Cargo.toml b/tui/Cargo.toml new file mode 100644 index 0000000..3c9b1ba --- /dev/null +++ b/tui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "panorama-tui" +version = "0.1.0" +edition = "2018" + +[dependencies] +crossterm = "0.20" diff --git a/tui/src/lib.rs b/tui/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tui/src/lib.rs @@ -0,0 +1 @@ + diff --git a/tui/src/main.rs b/tui/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/tui/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}