full functionality and first polish

This commit is contained in:
glyph 2022-08-11 08:28:37 +01:00
parent e5f784abf9
commit 9ec99dc0be
13 changed files with 711 additions and 346 deletions

295
Cargo.lock generated
View File

@ -92,14 +92,14 @@ dependencies = [
[[package]]
name = "async-global-executor"
version = "2.0.4"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43"
checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940"
dependencies = [
"async-channel",
"async-executor",
"async-io",
"async-mutex",
"async-lock",
"blocking",
"futures-lite",
"num_cpus",
@ -108,9 +108,9 @@ dependencies = [
[[package]]
name = "async-io"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
dependencies = [
"concurrent-queue",
"futures-lite",
@ -134,15 +134,6 @@ dependencies = [
"event-listener",
]
[[package]]
name = "async-mutex"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
dependencies = [
"event-listener",
]
[[package]]
name = "async-process"
version = "1.4.0"
@ -162,9 +153,9 @@ dependencies = [
[[package]]
name = "async-std"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel",
@ -181,7 +172,6 @@ dependencies = [
"kv-log-macro",
"log",
"memchr",
"num_cpus",
"once_cell",
"pin-project-lite",
"pin-utils",
@ -239,9 +229,9 @@ checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
[[package]]
name = "async-trait"
version = "0.1.53"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
dependencies = [
"proc-macro2",
"quote",
@ -368,9 +358,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.9.1"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]]
name = "byte-tools"
@ -429,7 +419,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"time 0.1.43",
"time 0.1.44",
"winapi 0.3.9",
]
@ -487,7 +477,7 @@ dependencies = [
"rand",
"sha2",
"subtle",
"time 0.3.9",
"time 0.3.11",
"version_check",
]
@ -511,26 +501,26 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"crossbeam-utils",
"lazy_static",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
dependencies = [
"cfg-if 1.0.0",
"lazy_static",
"once_cell",
]
[[package]]
@ -631,6 +621,15 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
@ -943,13 +942,13 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -970,9 +969,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "globset"
version = "0.4.8"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [
"aho-corasick",
"bstr",
@ -1006,7 +1005,8 @@ dependencies = [
[[package]]
name = "golgi"
version = "0.1.1"
version = "0.2.4"
source = "git+https://git.coopcloud.tech/golgi-ssb/golgi.git#9981b64bb23e1dbb95e31cfa1c545d23bb4f40d2"
dependencies = [
"async-std",
"async-stream 0.3.3",
@ -1016,6 +1016,7 @@ dependencies = [
"kuska-handshake",
"kuska-sodiumoxide",
"kuska-ssb",
"log",
"serde",
"serde_json",
"sha2",
@ -1036,15 +1037,15 @@ dependencies = [
"indexmap",
"slab",
"tokio",
"tokio-util 0.7.1",
"tokio-util 0.7.3",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
[[package]]
name = "hermit-abi"
@ -1081,9 +1082,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"fnv",
@ -1092,9 +1093,9 @@ dependencies = [
[[package]]
name = "http-body"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
@ -1127,9 +1128,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.18"
version = "0.14.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f"
dependencies = [
"bytes",
"futures-channel",
@ -1169,9 +1170,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.8.1"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
@ -1224,15 +1225,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
[[package]]
name = "js-sys"
version = "0.3.57"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
dependencies = [
"wasm-bindgen",
]
@ -1273,13 +1274,13 @@ dependencies = [
[[package]]
name = "kuska-ssb"
version = "0.4.0"
source = "git+https://github.com/Kuska-ssb/ssb#fb7062de606e7c9cae8dd4df402a122db46c1b77"
version = "0.4.1"
source = "git+https://github.com/Kuska-ssb/ssb#315c7e31aa6255d5657665ce9357034113d2b552"
dependencies = [
"async-std",
"async-stream 0.2.1",
"base64 0.11.0",
"dirs",
"dirs 2.0.2",
"futures",
"get_if_addrs",
"hex",
@ -1316,9 +1317,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.125"
version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libsodium-sys"
@ -1354,9 +1355,9 @@ dependencies = [
[[package]]
name = "loom"
version = "0.5.4"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309"
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
dependencies = [
"cfg-if 1.0.0",
"generator",
@ -1383,6 +1384,8 @@ dependencies = [
"serde",
"serde_json",
"sled",
"uri_encode",
"xdg",
]
[[package]]
@ -1442,9 +1445,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.3"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
@ -1492,7 +1495,7 @@ dependencies = [
"mime",
"spin",
"tokio",
"tokio-util 0.6.9",
"tokio-util 0.6.10",
"version_check",
]
@ -1574,9 +1577,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.10.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
[[package]]
name = "opaque-debug"
@ -1609,9 +1612,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.12.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core 0.9.3",
@ -1815,11 +1818,11 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.38"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
"unicode-xid",
"unicode-ident",
]
[[package]]
@ -1837,9 +1840,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
"proc-macro2",
]
@ -1916,9 +1919,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.5.5"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
dependencies = [
"aho-corasick",
"memchr",
@ -1936,9 +1939,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
[[package]]
name = "remove_dir_all"
@ -1969,7 +1972,7 @@ dependencies = [
"memchr",
"multer",
"num_cpus",
"parking_lot 0.12.0",
"parking_lot 0.12.1",
"pin-project-lite",
"rand",
"ref-cast",
@ -1978,10 +1981,10 @@ dependencies = [
"serde",
"state",
"tempfile",
"time 0.3.9",
"time 0.3.11",
"tokio",
"tokio-stream",
"tokio-util 0.7.1",
"tokio-util 0.7.3",
"ubyte",
"version_check",
"yansi",
@ -2005,16 +2008,14 @@ dependencies = [
[[package]]
name = "rocket_dyn_templates"
version = "0.1.0-rc.1"
version = "0.1.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c83f1287ad8fa034410928297a91db37518d5c46d7cc7e1e1b4a77aec0cd8807"
checksum = "bab13df598440527c200f46fb944dc55d8d67a1818b617eb5a3981dcd8b63fd2"
dependencies = [
"glob",
"normpath",
"notify",
"rocket",
"serde",
"serde_json",
"tera",
]
@ -2040,22 +2041,22 @@ dependencies = [
"smallvec",
"stable-pattern",
"state",
"time 0.3.9",
"time 0.3.11",
"tokio",
"uncased",
]
[[package]]
name = "rustversion"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
[[package]]
name = "ryu"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "same-file"
@ -2144,9 +2145,9 @@ dependencies = [
[[package]]
name = "signal-hook"
version = "0.3.13"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
dependencies = [
"libc",
"signal-hook-registry",
@ -2200,9 +2201,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
[[package]]
name = "socket2"
@ -2246,13 +2247,13 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.92"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
"unicode-ident",
]
[[package]]
@ -2271,9 +2272,9 @@ dependencies = [
[[package]]
name = "tera"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d"
checksum = "7c9783d6ff395ae80cf17ed9a25360e7ba37742a79fa8fddabb073c5c7c8856d"
dependencies = [
"chrono",
"chrono-tz",
@ -2331,19 +2332,20 @@ dependencies = [
[[package]]
name = "time"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
[[package]]
name = "time"
version = "0.3.9"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
dependencies = [
"itoa",
"libc",
@ -2359,14 +2361,14 @@ checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tokio"
version = "1.18.2"
version = "1.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
dependencies = [
"bytes",
"libc",
"memchr",
"mio 0.8.3",
"mio 0.8.4",
"num_cpus",
"once_cell",
"pin-project-lite",
@ -2378,9 +2380,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [
"proc-macro2",
"quote",
@ -2389,9 +2391,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
dependencies = [
"futures-core",
"pin-project-lite",
@ -2400,9 +2402,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.6.9"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
dependencies = [
"bytes",
"futures-core",
@ -2414,9 +2416,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764"
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
dependencies = [
"bytes",
"futures-core",
@ -2437,15 +2439,15 @@ dependencies = [
[[package]]
name = "tower-service"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.34"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@ -2466,11 +2468,11 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.26"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f"
checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
dependencies = [
"lazy_static",
"once_cell",
"valuable",
]
@ -2517,9 +2519,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "ubyte"
version = "0.10.1"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42756bb9e708855de2f8a98195643dff31a97f0485d90d8467b39dc24be9e8fe"
checksum = "a58e29f263341a29bb79e14ad7fda5f63b1c7e48929bad4c685d7876b1d04e94"
dependencies = [
"serde",
]
@ -2532,9 +2534,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uncased"
version = "0.9.6"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0"
checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622"
dependencies = [
"serde",
"version_check",
@ -2590,6 +2592,12 @@ dependencies = [
"unic-common",
]
[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]]
name = "unicode-xid"
version = "0.2.3"
@ -2606,6 +2614,12 @@ dependencies = [
"subtle",
]
[[package]]
name = "uri_encode"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34acb51c736f8784bdbca2692c3cc57213bfc1b329fd6eb7668d5e4af8f87ceb"
[[package]]
name = "valuable"
version = "0.1.0"
@ -2657,9 +2671,9 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
@ -2669,9 +2683,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -2679,9 +2693,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
dependencies = [
"bumpalo",
"lazy_static",
@ -2694,9 +2708,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.30"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@ -2706,9 +2720,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2716,9 +2730,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
dependencies = [
"proc-macro2",
"quote",
@ -2729,15 +2743,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.80"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
[[package]]
name = "web-sys"
version = "0.3.57"
version = "0.3.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2848,6 +2862,15 @@ dependencies = [
"winapi-build",
]
[[package]]
name = "xdg"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
dependencies = [
"dirs 4.0.0",
]
[[package]]
name = "yansi"
version = "0.5.1"

View File

@ -2,18 +2,24 @@
name = "lykin"
version = "0.1.0"
edition = "2021"
authors = ["glyph <glyph@mycelial.technology>"]
readme = "README.md"
description = "Symbiosis of SSB, key-value store and web server."
repository = "https://git.coopcloud.tech/glyph/lykin"
keywords = ["scuttlebutt", "ssb", "decentralized", "peer-for-peer", "p4p"]
[dependencies]
async-std = "1.10"
bincode = "1.3"
chrono = "0.4"
uri_encode = "1"
env_logger = "0.9"
futures = "0.3"
#golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
golgi = { path = "../golgi" }
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
log = "0.4"
rocket = "0.5.0-rc.1"
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["tera"] }
serde = "1"
serde_json = "1"
sled = "0.34"
xdg = "2.4.1"

131
src/db.rs
View File

@ -15,22 +15,65 @@ use log::{debug, info};
use serde::{Deserialize, Serialize};
use sled::{Batch, Db, IVec, Result, Tree};
/// The latest sequence number of a Scuttlebutt peer.
/// Scuttlebutt peer data.
#[derive(Debug, Deserialize, Serialize)]
pub struct Peer {
/// The public key of the peer, also known as an SSB ID.
pub public_key: String,
/// The sequence number of the most recent post stored for this peer.
pub latest_sequence: u64,
//pub name: String,
//pub posts: u16,
/// The name of the peer.
pub name: String,
}
impl Peer {
/// Create a new instance of the Peer struct using the given public
/// key. Default values are set for latest_sequence and name.
pub fn new(public_key: &str) -> Peer {
Peer {
public_key: public_key.to_string(),
latest_sequence: 0,
name: "".to_string(),
}
}
/// Modify the name field of an instance of the Peer struct, leaving
/// the other values unchanged.
pub fn set_name(self, name: &str) -> Peer {
Self {
name: name.to_string(),
..self
}
}
/// Modify the latest_sequence field of an instance of the Peer struct,
/// leaving the other values unchanged.
pub fn set_latest_sequence(self, latest_sequence: u64) -> Peer {
Self {
latest_sequence,
..self
}
}
}
/// The text and metadata of a Scuttlebutt root post.
#[derive(Debug, Deserialize, Serialize)]
pub struct Post {
/// The key of the post-type message, also known as a message reference.
pub key: String,
/// The text of the post (may be formatted as markdown).
pub text: String,
/// The date the post was published (e.g. 17 May 2021).
pub date: String,
/// The sequence number of the post-type message.
pub sequence: u64,
/// The read state of the post; true if read, false if unread.
pub read: bool,
/// The timestamp representing the date the post was published.
pub timestamp: i64,
/// The subject of the post, represented as the first 53 characters of
/// the post text.
pub subject: Option<String>,
}
/// Convenience type which facilitates converting an IVec into a String.
@ -57,13 +100,16 @@ impl From<IVec> for IVecString {
}
/// An instance of the key-value database and relevant trees.
#[allow(dead_code)]
#[derive(Clone)]
pub struct Database {
/// Stores the sled database instance.
/// The sled database instance.
db: Db,
/// Stores the public keys of all the peers we are subscribed to.
/// A database tree containing Peer struct instances for all the peers
/// we are subscribed to.
peer_tree: Tree,
/// Stores the posts (content and metadata) for all the feeds we are subscribed to.
/// A database tree containing Post struct instances for all of the posts
/// we have downloaded from the peer to whom we subscribe.
pub post_tree: Tree,
}
@ -74,16 +120,16 @@ impl Database {
// Open the database at the given path.
// The database will be created if it does not yet exist.
// This code will panic if an IO error is encountered.
info!("initialising the sled database");
let db = sled::open(path).expect("failed to open database");
debug!("opening the 'peers' database tree");
info!("Initialising sled database");
let db = sled::open(path).expect("Failed to open database");
debug!("Opening 'peers' database tree");
let peer_tree = db
.open_tree("peers")
.expect("failed to open database peers tree");
debug!("opening the 'posts' database tree");
.expect("Failed to open database peers tree");
debug!("Opening 'posts' database tree");
let post_tree = db
.open_tree("posts")
.expect("failed to open database posts tree");
.expect("Failed to open database posts tree");
Database {
db,
@ -94,8 +140,10 @@ impl Database {
/// Add a peer to the database by inserting the public key into the peer
/// tree.
pub fn add_peer(&self, public_key: &str) -> Result<Option<IVec>> {
self.peer_tree.insert(&public_key, vec![0])
pub fn add_peer(&self, peer: Peer) -> Result<Option<IVec>> {
let peer_bytes = bincode::serialize(&peer).unwrap();
self.peer_tree.insert(&peer.public_key, peer_bytes)
}
/// Remove a peer from the database, as represented by the given public
@ -104,15 +152,31 @@ impl Database {
self.peer_tree.remove(&public_key).map(|_| ())
}
/// Get a list of all peers in the peer tree. The public key of each peer
/// is returned.
pub fn get_peers(&self) -> Vec<String> {
/// Get a single peer from the peer tree, defined by the given public key.
/// The byte value for the matching entry, if found, is deserialized from
/// bincode into an instance of the Peer struct.
pub fn get_peer(&self, public_key: &str) -> Result<Option<Peer>> {
let peer = self
.peer_tree
.get(public_key.as_bytes())
.unwrap()
.map(|peer| bincode::deserialize(&peer).unwrap());
Ok(peer)
}
/// Get a list of all peers in the peer tree. The byte value for each
/// peer entry is deserialized from bincode into an instance of the Peer
/// struct.
pub fn get_peers(&self) -> Vec<Peer> {
let mut peers = Vec::new();
self.peer_tree
.iter()
.keys()
.map(|bytes| IVecString::from(bytes.unwrap()))
.map(|ivec_string| ivec_string.string)
.collect()
.map(|peer| peer.unwrap())
.for_each(|peer| peers.push(bincode::deserialize(&peer.1).unwrap()));
peers
}
/// Add a post to the database by inserting an instance of the Post struct
@ -148,8 +212,9 @@ impl Database {
}
/// Get a list of all posts in the post tree authored by the given public
/// key. The byte value for each matching entry is deserialized from
/// bincode into an instance of the Post struct.
/// key and sort them by timestamp in descending order. The byte value for
/// each matching entry is deserialized from bincode into an instance of
/// the Post struct.
pub fn get_posts(&self, public_key: &str) -> Result<Vec<Post>> {
let mut posts = Vec::new();
@ -158,6 +223,8 @@ impl Database {
.map(|post| post.unwrap())
.for_each(|post| posts.push(bincode::deserialize(&post.1).unwrap()));
posts.sort_by(|a: &Post, b: &Post| b.timestamp.cmp(&a.timestamp));
Ok(posts)
}
@ -188,4 +255,22 @@ impl Database {
// map the Option to ().
self.post_tree.remove(post_key.as_bytes()).map(|_| ())
}
/// Sum the total number of unread posts for the peer represented by the
/// given public key.
pub fn get_unread_post_count(&self, public_key: &str) -> u16 {
let mut unread_post_counter = 0;
self.post_tree
.scan_prefix(public_key.as_bytes())
.map(|post| post.unwrap())
.for_each(|post| {
let deserialized_post: Post = bincode::deserialize(&post.1).unwrap();
if !deserialized_post.read {
unread_post_counter += 1
}
});
unread_post_counter
}
}

View File

@ -4,7 +4,7 @@ mod sbot;
mod task_loop;
mod utils;
use std::{env, path::Path};
use std::env;
use async_std::channel;
use log::info;
@ -14,6 +14,7 @@ use rocket::{
launch, routes,
};
use rocket_dyn_templates::Template;
use xdg::BaseDirectories;
use crate::{db::Database, routes::*, task_loop::Task};
@ -25,18 +26,30 @@ pub struct WhoAmI {
async fn rocket() -> _ {
env_logger::init();
let public_key: String = sbot::whoami().await.expect("whoami sbot call failed");
// Retrieve and store the public key of the local sbot.
let public_key: String = sbot::whoami()
.await
.expect("whoami rpc call failed. please ensure the sbot is running before trying again");
info!("Public key of the local sbot instance: {}", public_key);
let whoami = WhoAmI { public_key };
let db = Database::init(Path::new("lykin_db"));
// Create the key-value database.
let xdg_dirs = BaseDirectories::with_prefix("lykin").unwrap();
let db_path = xdg_dirs
.place_config_file("database")
.expect("cannot create database directory");
let db = Database::init(&db_path);
let db_clone = db.clone();
// Create a message passing channel.
let (tx, rx) = channel::unbounded();
let tx_clone = tx.clone();
task_loop::spawn(rx, db_clone).await;
// Spawn the task loop.
info!("Spawning task loop");
task_loop::spawn(db_clone, rx).await;
info!("launching the web server");
info!("Launching web server");
rocket::build()
.manage(db)
.manage(whoami)
@ -45,19 +58,21 @@ async fn rocket() -> _ {
"/",
routes![
home,
delete_post,
download_latest_posts,
mark_post_read,
mark_post_unread,
post,
posts,
subscribe_form,
unsubscribe_form,
posts,
post,
mark_post_read,
mark_post_unread
],
)
.mount("/", FileServer::from(relative!("static")))
.attach(Template::fairing())
.attach(AdHoc::on_shutdown("cancel task loop", |_| {
Box::pin(async move {
tx_clone.send(Task::Cancel).await;
tx_clone.send(Task::Cancel).await.unwrap();
})
}))
}

View File

@ -1,14 +1,17 @@
use async_std::channel::Sender;
use log::{debug, warn};
use markdown;
use rocket::{form::Form, get, post, response::Redirect, uri, FromForm, State};
use rocket_dyn_templates::{tera::Context, Template};
use uri_encode;
use crate::{db::Database, sbot, task_loop::Task, utils, WhoAmI};
use crate::{
db::{Database, Peer},
sbot,
task_loop::Task,
utils, WhoAmI,
};
#[derive(FromForm)]
pub struct Peer {
pub struct PeerForm {
pub public_key: String,
}
@ -17,19 +20,44 @@ pub async fn home(db: &State<Database>) -> Template {
let peers = db.get_peers();
let mut context = Context::new();
context.insert("peers", &peers);
let mut peers_unread = Vec::new();
for peer in peers {
let unread_count = db.get_unread_post_count(&peer.public_key);
peers_unread.push((peer, unread_count.to_string()));
}
context.insert("peers", &peers_unread);
Template::render("base", &context.into_json())
}
#[get("/posts/<public_key>/<msg_id>/delete")]
pub async fn delete_post(db: &State<Database>, public_key: &str, msg_id: &str) -> Redirect {
// Delete the post from the database. This method cannot panic, so we're
// safe to unwrap the result.
db.remove_post(public_key, msg_id).unwrap();
Redirect::to(uri!(posts(public_key)))
}
#[get("/posts/<public_key>")]
pub async fn posts(db: &State<Database>, public_key: &str) -> Template {
let peers = db.get_peers();
let posts = db.get_posts(public_key).unwrap();
let mut context = Context::new();
let mut peers_unread = Vec::new();
for peer in peers {
let unread_count = db.get_unread_post_count(&peer.public_key);
peers_unread.push((peer, unread_count.to_string()));
}
context.insert("peers", &peers_unread);
let posts = db.get_posts(public_key).unwrap();
context.insert("selected_peer", &public_key);
context.insert("peers", &peers);
context.insert("posts", &posts);
Template::render("base", &context.into_json())
@ -38,10 +66,20 @@ pub async fn posts(db: &State<Database>, public_key: &str) -> Template {
#[get("/posts/<public_key>/<msg_id>")]
pub async fn post(db: &State<Database>, public_key: &str, msg_id: &str) -> Template {
let peers = db.get_peers();
let mut context = Context::new();
let mut peers_unread = Vec::new();
for peer in peers {
let unread_count = db.get_unread_post_count(&peer.public_key);
peers_unread.push((peer, unread_count.to_string()));
}
context.insert("peers", &peers_unread);
let posts = db.get_posts(public_key).unwrap();
let post = db.get_post(public_key, msg_id).unwrap();
let mut context = Context::new();
context.insert("selected_peer", &public_key);
context.insert(
"selected_peer_encoded",
@ -52,9 +90,7 @@ pub async fn post(db: &State<Database>, public_key: &str, msg_id: &str) -> Templ
"selected_post_encoded",
&uri_encode::encode_uri_component(msg_id),
);
context.insert("peers", &peers);
context.insert("posts", &posts);
// TODO: consider converting markdown to html here
context.insert("post", &post);
context.insert("post_is_selected", &true);
@ -93,44 +129,76 @@ pub async fn mark_post_unread(db: &State<Database>, public_key: &str, msg_id: &s
Redirect::to(uri!(post(public_key, msg_id)))
}
#[get("/posts/download_latest")]
pub async fn download_latest_posts(db: &State<Database>, tx: &State<Sender<Task>>) -> Redirect {
for peer in db.get_peers() {
// Fetch the latest root posts authored by each peer we're
// subscribed to. Posts will be added to the key-value database.
if let Err(e) = tx
.send(Task::FetchLatestPosts(peer.public_key.clone()))
.await
{
warn!("task loop error: {}", e)
}
// Fetch the latest name for each peer we're subscribed to and update
// the database.
if let Err(e) = tx.send(Task::FetchLatestName(peer.public_key)).await {
warn!("task loop error: {}", e)
}
}
Redirect::to(uri!(home))
}
#[post("/subscribe", data = "<peer>")]
pub async fn subscribe_form(
db: &State<Database>,
whoami: &State<WhoAmI>,
tx: &State<Sender<Task>>,
peer: Form<Peer>,
peer: Form<PeerForm>,
) -> Redirect {
if utils::validate_public_key(&peer.public_key).is_ok() {
debug!("public key {} is valid", &peer.public_key);
// TODO: consider getting the peer name here so we can insert it
match db.add_peer(&peer.public_key) {
Ok(_) => {
debug!("added {} to peer tree in database", &peer.public_key);
// TODO: i don't think we actually want to follow...
// we might still have the data in our ssb db, even if we don't follow
match sbot::is_following(&whoami.public_key, &peer.public_key).await {
Ok(status) if status.as_str() == "false" => {
match sbot::follow_peer(&peer.public_key).await {
Ok(_) => debug!("followed {}", &peer.public_key),
Err(e) => warn!("failed to follow {}: {}", &peer.public_key, e),
}
}
Ok(status) if status.as_str() == "true" => {
debug!("we already follow {}", &peer.public_key)
}
_ => (),
}
let peer = peer.public_key.to_string();
// Fetch all root posts authored by the peer we're subscribing
// to. Posts will be added to the key-value database.
if let Err(e) = tx.send(Task::FetchAll(peer)).await {
warn!("task loop error: {}", e)
}
// Retrieve the name of the peer to which we are subscribing.
let peer_name = match sbot::get_name(&peer.public_key).await {
Ok(name) => name,
Err(e) => {
warn!("failed to fetch name for {}: {}", &peer.public_key, e);
String::from("")
}
Err(_e) => warn!(
};
let peer_info = Peer::new(&peer.public_key).set_name(&peer_name);
// Add the peer to the database and then check the follow state.
// Follow the peer if our local instance is not already following.
if db.add_peer(peer_info).is_ok() {
debug!("added {} to peer tree in database", &peer.public_key);
match sbot::is_following(&whoami.public_key, &peer.public_key).await {
Ok(status) if status.as_str() == "false" => {
match sbot::follow_peer(&peer.public_key).await {
Ok(_) => debug!("followed {}", &peer.public_key),
Err(e) => warn!("failed to follow {}: {}", &peer.public_key, e),
}
}
Ok(status) if status.as_str() == "true" => {
debug!("we already follow {}", &peer.public_key)
}
_ => (),
}
let peer_id = peer.public_key.to_string();
// Fetch all root posts authored by the peer we're subscribing
// to. Posts will be added to the key-value database.
if let Err(e) = tx.send(Task::FetchAllPosts(peer_id)).await {
warn!("task loop error: {}", e)
}
} else {
warn!(
"failed to add {} to peer tree in database",
&peer.public_key
),
)
}
} else {
warn!("{} is invalid", &peer.public_key);
@ -140,20 +208,18 @@ pub async fn subscribe_form(
}
#[post("/unsubscribe", data = "<peer>")]
pub fn unsubscribe_form(db: &State<Database>, peer: Form<Peer>) -> Redirect {
// validate the public key
match utils::validate_public_key(&peer.public_key) {
Ok(_) => {
debug!("public key {} is valid", &peer.public_key);
match db.remove_peer(&peer.public_key) {
Ok(_) => debug!("removed {} from peer tree in database", &peer.public_key),
Err(_e) => warn!(
"failed to remove {} from peer tree in database",
&peer.public_key
),
}
pub fn unsubscribe_form(db: &State<Database>, peer: Form<PeerForm>) -> Redirect {
if let Err(e) = utils::validate_public_key(&peer.public_key) {
warn!("{} is invalid: {}", &peer.public_key, e)
} else {
debug!("public key {} is valid", &peer.public_key);
match db.remove_peer(&peer.public_key) {
Ok(_) => debug!("removed {} from peer tree in database", &peer.public_key),
Err(_e) => warn!(
"failed to remove {} from peer tree in database",
&peer.public_key
),
}
Err(e) => warn!("{} is invalid: {}", &peer.public_key, e),
}
Redirect::to(uri!(home))

View File

@ -3,30 +3,39 @@
use async_std::stream::StreamExt;
use chrono::NaiveDateTime;
use golgi::{
api::friends::RelationshipQuery,
api::{friends::RelationshipQuery, history_stream::CreateHistoryStream},
messages::{SsbMessageContentType, SsbMessageKVT},
sbot::Keystore,
GolgiError, Sbot,
};
use log::warn;
use log::{debug, info, warn};
use serde_json::value::Value;
use crate::db::Post;
/// Initialise a connection to a Scuttlebutt server.
async fn init_sbot() -> Result<Sbot, String> {
let keystore = Keystore::GoSbot;
let ip_port = Some("127.0.0.1:8021".to_string());
let net_id = None;
debug!("Initialising the sbot connection");
Sbot::init(keystore, ip_port, net_id)
.await
.map_err(|e| e.to_string())
}
/// Return the public key of the local sbot instance.
pub async fn whoami() -> Result<String, String> {
let mut sbot = Sbot::init(Keystore::Patchwork, None, None)
.await
.map_err(|e| e.to_string())?;
let mut sbot = init_sbot().await?;
info!("Executing `whoami` call");
sbot.whoami().await.map_err(|e| e.to_string())
}
/// Follow a peer.
pub async fn follow_peer(public_key: &str) -> Result<String, String> {
let mut sbot = Sbot::init(Keystore::Patchwork, None, None)
.await
.map_err(|e| e.to_string())?;
let mut sbot = init_sbot().await?;
sbot.follow(public_key).await.map_err(|e| e.to_string())
}
@ -35,9 +44,7 @@ pub async fn follow_peer(public_key: &str) -> Result<String, String> {
///
/// Is peer A (`public_key_a`) following peer B (`public_key_b`)?
pub async fn is_following(public_key_a: &str, public_key_b: &str) -> Result<String, String> {
let mut sbot = Sbot::init(Keystore::Patchwork, None, None)
.await
.map_err(|e| e.to_string())?;
let mut sbot = init_sbot().await?;
let query = RelationshipQuery {
source: public_key_a.to_string(),
@ -54,17 +61,29 @@ pub async fn is_following(public_key_a: &str, public_key_b: &str) -> Result<Stri
/// This returns all messages regardless of type.
pub async fn get_message_stream(
public_key: &str,
sequence_number: u64,
) -> impl futures::Stream<Item = Result<SsbMessageKVT, GolgiError>> {
let mut sbot = Sbot::init(Keystore::Patchwork, None, None)
.await
.map_err(|e| e.to_string())
.unwrap();
let mut sbot = init_sbot().await.unwrap();
sbot.create_history_stream(public_key.to_string())
let history_stream_args = CreateHistoryStream::new(public_key.to_string())
.keys_values(true, true)
.after_seq(sequence_number);
sbot.create_history_stream(history_stream_args)
.await
.unwrap()
}
/// Return the name (self-identifier) for the peer associated with the given
/// public key.
///
/// The public key of the peer will be returned if a name is not found.
pub async fn get_name(public_key: &str) -> Result<String, String> {
let mut sbot = init_sbot().await?;
sbot.get_name(public_key).await.map_err(|e| e.to_string())
}
/// Filter a stream of messages and return a vector of root posts.
///
/// Each returned vector element includes the key of the post, the content
@ -72,7 +91,8 @@ pub async fn get_message_stream(
/// and whether it is read or unread.
pub async fn get_root_posts(
history_stream: impl futures::Stream<Item = Result<SsbMessageKVT, GolgiError>>,
) -> Vec<Post> {
) -> (u64, Vec<Post>) {
let mut latest_sequence = 0;
let mut posts = Vec::new();
futures::pin_mut!(history_stream);
@ -84,18 +104,23 @@ pub async fn get_root_posts(
let content = msg.value.content.to_owned();
if let Value::Object(content_map) = content {
if !content_map.contains_key("root") {
let timestamp_int = msg.value.timestamp.round() as i64 / 1000;
let datetime = NaiveDateTime::from_timestamp(timestamp_int, 0);
let timestamp = msg.value.timestamp.round() as i64 / 1000;
let datetime = NaiveDateTime::from_timestamp(timestamp, 0);
let date = datetime.format("%d %b %Y").to_string();
let text = content_map.get_key_value("text").unwrap();
let text = content_map.get_key_value("text").unwrap().1.to_string();
let subject = text.get(0..52).map(|s| s.to_string());
latest_sequence = msg.value.sequence;
posts.push(Post {
key: msg.key.to_owned(),
text: text.1.to_string(),
text,
timestamp,
date,
sequence: msg.value.sequence,
read: false,
subject,
})
}
}
@ -108,5 +133,5 @@ pub async fn get_root_posts(
}
}
posts
(latest_sequence, posts)
}

View File

@ -1,42 +1,89 @@
use async_std::{channel::Receiver, task};
use log::{debug, info, warn};
use log::{info, warn};
use crate::{db::Database, sbot};
pub enum Task {
Cancel,
FetchAll(String),
//FetchLatest(String),
FetchAllPosts(String),
FetchLatestPosts(String),
FetchLatestName(String),
}
pub async fn spawn(rx: Receiver<Task>, db: Database) {
async fn fetch_posts_and_update_db(db: &Database, peer_id: String, after_sequence: u64) {
let peer_msgs = sbot::get_message_stream(&peer_id, after_sequence).await;
let (latest_sequence, root_posts) = sbot::get_root_posts(peer_msgs).await;
match db.add_post_batch(&peer_id, root_posts) {
Ok(_) => {
info!(
"Inserted batch of posts into database post tree for peer: {}",
&peer_id
)
}
Err(e) => warn!(
"Failed to insert batch of posts into database post tree for peer: {}: {}",
&peer_id, e
),
}
// Update the value of the latest sequence number for
// the peer (this is stored in the database).
if let Ok(Some(peer)) = db.get_peer(&peer_id) {
db.add_peer(peer.set_latest_sequence(latest_sequence))
.unwrap();
}
}
async fn fetch_name_and_update_db(db: &Database, peer_id: String) {
match sbot::get_name(&peer_id).await {
Ok(name) => {
if let Ok(Some(peer)) = db.get_peer(&peer_id) {
let updated_peer = peer.set_name(&name);
match db.add_peer(updated_peer) {
Ok(_) => info!("Updated name for peer: {}", &peer_id),
Err(e) => {
warn!("Failed to update name for peer: {}: {}", &peer_id, e)
}
}
}
}
Err(e) => warn!("Failed to fetch name for {}: {}", &peer_id, e),
}
}
pub async fn spawn(db: Database, rx: Receiver<Task>) {
task::spawn(async move {
while let Ok(task) = rx.recv().await {
match task {
// Fetch all messages authored by the given peer, filter
// the root posts and insert them into the peer tree of the
// the root posts and insert them into the posts tree of the
// database.
Task::FetchAll(peer) => {
let peer_msgs = sbot::get_message_stream(&peer).await;
// TODO: return a tuple from sbot::get_root_posts
//let (root_posts, latest_sequence) = sbot::get_root_posts(peer_msgs).await;
// TODO: update the sequence number if required
//if latest_sequence > db.get_latest_sequence(&peer) {
// db.update_sequence(&peer, latest_sequence)
//}
let root_posts = sbot::get_root_posts(peer_msgs).await;
match db.add_post_batch(&peer, root_posts) {
Ok(_) => debug!("inserted batch of posts into post tree for {}", &peer),
Err(e) => warn!(
"failed to insert batch of posts into post tree for {}: {}",
&peer, e
),
Task::FetchAllPosts(peer_id) => {
info!("Fetching all posts for peer: {}", peer_id);
fetch_posts_and_update_db(&db, peer_id, 0).await;
}
// Fetch only the latest messages authored by the given peer,
// ie. messages with sequence numbers greater than those
// which are already stored in the database.
//
// Retrieve the root posts from those messages and insert them
// into the posts tree of the database.
Task::FetchLatestPosts(peer_id) => {
if let Ok(Some(peer)) = db.get_peer(&peer_id) {
info!("Fetching latest posts for peer: {}", peer_id);
fetch_posts_and_update_db(&db, peer_id, peer.latest_sequence).await;
}
}
// TODO: fetch all msgs with sequence number > peer.latest_sequence for peer
//Task::FetchLatest(peer) => {
// Fetch the latest name for the given peer and update the
// peer entry in the peers tree of the database.
Task::FetchLatestName(peer_id) => {
info!("Fetching latest name for peer: {}", peer_id);
fetch_name_and_update_db(&db, peer_id).await;
}
// Break out of the task loop.
Task::Cancel => {
info!("exiting task loop...");
info!("Exiting task loop...");
break;
}
}

View File

@ -2,26 +2,26 @@
///
/// Return an error string if the key is invalid.
pub fn validate_public_key(public_key: &str) -> Result<(), String> {
// ensure the id starts with the correct sigil link
// Ensure the ID starts with the correct sigil link.
if !public_key.starts_with('@') {
return Err("expected '@' sigil as first character".to_string());
}
// find the dot index denoting the start of the algorithm definition tag
// Find the dot index denoting the start of the algorithm definition tag.
let dot_index = match public_key.rfind('.') {
Some(index) => index,
None => return Err("no dot index was found".to_string()),
};
// check hashing algorithm (must end with ".ed25519")
// Check the hashing algorithm (must end with ".ed25519").
if !&public_key.ends_with(".ed25519") {
return Err("hashing algorithm must be ed25519".to_string());
}
// obtain the base64 portion (substring) of the public key
// Obtain the base64 portion (substring) of the public key.
let base64_str = &public_key[1..dot_index];
// length of a base64 encoded ed25519 public key
// Ensure the length of the base64 encoded ed25519 public key is correct.
if base64_str.len() != 44 {
return Err("base64 data length is incorrect".to_string());
}

View File

@ -1,34 +1,6 @@
.nav {
background-color: lightgreen;
border: 5px solid #19A974;
border-radius: 15px;
grid-area: nav;
padding: 1rem;
}
.peers {
background-color: lightblue;
border: 5px solid #357EDD;
border-radius: 15px;
grid-area: peers;
}
.posts {
background-color: bisque;
border: 5px solid #FF6300;
border-radius: 15px;
grid-area: posts;
overflow-y: scroll;
}
.post > ul {
padding-left: 25px;
padding-right: 25px;
}
.content {
background-color: lightyellow;
border: 5px solid #FFD700;
border: 5px solid #ffd700;
border-radius: 15px;
grid-area: content;
padding: 1.5rem;
@ -42,34 +14,9 @@
margin: 0;
}
/*
.flex-container {
display: flex;
justify-content: space-between;
}
*/
.grid-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1fr 3fr 4fr;
grid-template-areas:
'nav nav nav nav nav'
'peers posts posts posts posts'
'peers content content content content';
grid-gap: 10px;
padding-left: 15px;
padding-right: 15px;
padding-top: 5px;
overflow: hidden;
height: 85vh;
}
.grid-container > div {
/* background-color: rgba(255, 255, 255, 0.8); */
/* text-align: center; */
/* padding: 20px 0; */
/* font-size: 30px; */
.disabled {
opacity: 0.4;
pointer-events: none;
}
.flex-container {
@ -82,4 +29,119 @@
margin: 5px;
}
a { text-decoration: none; color: black; }
.grid-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr 1fr 3fr;
grid-template-areas:
'nav'
'peers'
'posts'
'content';
grid-gap: 10px;
padding-left: 15px;
padding-right: 15px;
padding-top: 5px;
overflow: hidden;
height: 85vh;
}
@media only screen and (min-width: 600px) {
.grid-container {
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1fr 3fr 4fr;
grid-template-areas:
'nav nav nav nav nav'
'peers posts posts posts posts'
'peers content content content content';
}
}
.icon {
margin-left: 20px;
}
.nav {
background-color: lightgreen;
border: 5px solid #19a974;
border-radius: 15px;
grid-area: nav;
padding: 1rem;
}
.peers {
background-color: lightblue;
border: 5px solid #357edd;
border-radius: 15px;
grid-area: peers;
text-align: left;
}
.peers > ul {
padding-left: 1rem;
}
.peers > ul > li > a {
justify-content: space-between;
}
.peers > ul > li > a > p {
margin: 0;
}
.post > ul {
padding-left: 25px;
padding-right: 25px;
}
.posts {
background-color: bisque;
border: 5px solid #ff6300;
border-radius: 15px;
grid-area: posts;
overflow-y: scroll;
}
.posts > ul {
padding-left: 1rem;
padding-right: 1rem;
}
.posts > ul > li > a {
justify-content: space-between;
}
.posts > ul > li > a > p {
margin: 0;
}
.selected {
background-color: #f9c587;
}
a {
text-decoration: none;
color: black;
}
code {
word-wrap: anywhere;
}
form {
margin-left: auto;
margin-right: 10px;
}
h1 {
margin-left: 15px;
}
img {
width: 55px;
}
li {
list-style: none;
font-size: 12px;
}

View File

@ -9,7 +9,9 @@
<link rel="stylesheet" href="/css/lykin.css">
</head>
<body class="container">
<a href="/"><h1 style="margin-left: 15px;">lykin</h1></a>
<a href="/">
<h1>lykin</h1>
</a>
<div class="grid-container">
{% include "topbar" %}
{% include "peer_list" %}

View File

@ -1,9 +1,18 @@
<div class="peers" style="text-align: center;">
<ul style="padding-left: 0;">
<div class="peers">
<ul>
{% for peer in peers -%}
<li style="list-style: none; font-size: 12px;">
<a href="/posts/{{ peer | replace(from="/", to="%2F") }}">
<code style="word-wrap: anywhere;{% if selected_peer and peer == selected_peer %} font-weight: bold;{% endif %}">{{ peer }}</code>
<li>
<a class="flex-container" href="/posts/{{ peer.0.public_key | urlencode_strict }}">
<code{% if selected_peer and peer.0.public_key == selected_peer %} style="font-weight: bold;"{% endif %}>
{% if peer.0.name %}
{{ peer.0.name }}
{% else %}
{{ peer.0.public_key }}
{% endif %}
</code>
<p style="font-weight: bold; padding-right: 1rem;">
{% if peer.1 != "0" %}{{ peer.1 }}{% endif %}
</p>
</a>
</li>
{%- endfor %}

View File

@ -1,11 +1,11 @@
<div class="posts">
{% if posts %}
<ul style="padding-left: 25px; padding-right: 25px;">
<ul>
{% for post in posts -%}
<li style="list-style: none; font-size: 12px; margin-bottom: 5px;{% if selected_post and post.key == selected_post %} background-color: #FF6300;{% endif %}">
<a class="flex-container" style="justify-content: space-between;{% if not post.read %} font-weight: bold;{% endif %}" href="/posts/{{ selected_peer | urlencode_strict }}/{{ post.key | urlencode_strict }}">
<code style="word-wrap: anywhere;">{{ post.key }}</code>
<p style="margin: 0;">{{ post.date }}</p>
<li{% if selected_post and post.key == selected_post %} class="selected"{% endif %}>
<a class="flex-container"{% if not post.read %} style="font-weight: bold;"{% endif %} href="/posts/{{ selected_peer | urlencode_strict }}/{{ post.key | urlencode_strict }}">
<code>{% if post.subject %}{{ post.subject | trim_start_matches(pat='"') }}...{% else %}{{ post.text | trim_start_matches(pat='"') | trim_end_matches(pat='"') }}{% endif %}</code>
<p>{{ post.date }}</p>
</a>
</li>
{%- endfor %}

View File

@ -1,19 +1,44 @@
<div class="nav">
<div class="flex-container">
<a href="/" title="Download latest posts"><img src="/icons/download.png" style="width: 55px;"></a>
<a href="/posts/download_latest" title="Download latest posts">
<img src="/icons/download.png">
</a>
{% if post_is_selected %}
{% if post.read %}
{% set mark_unread_url = "/posts/" ~ selected_peer_encoded ~ "/" ~ selected_post_encoded ~ "/unread" %}
<a href={{ mark_unread_url }} style="margin-left: 20px;" title="Mark as unread"><img src="/icons/unread_post.png" style="width: 55px;"></a>
<a class="disabled icon" title="Mark as read">
<img src="/icons/read_post.png">
</a>
<a href={{ mark_unread_url }} class="icon" title="Mark as unread">
<img src="/icons/unread_post.png">
</a>
{% else %}
{% set mark_read_url = "/posts/" ~ selected_peer_encoded ~ "/" ~ selected_post_encoded ~ "/read" %}
<a href={{ mark_read_url }} style="margin-left: 20px;" title="Mark as read"><img src="/icons/read_post.png" style="width: 55px;"></a>
<a href={{ mark_read_url }} class="icon" title="Mark as read">
<img src="/icons/read_post.png">
</a>
<a class="disabled icon" title="Mark as unread">
<img src="/icons/unread_post.png">
</a>
{% endif %}
<a href="/" style="margin-left: 20px;" title="Delete post"><img src="/icons/delete_post.png" style="width: 55px;"></a>
{% set delete_post_url = "/posts/" ~ selected_peer_encoded ~ "/" ~ selected_post_encoded ~ "/delete" %}
<a href={{ delete_post_url }} class="icon" title="Delete post">
<img src="/icons/delete_post.png">
</a>
{% else %}
<a class="disabled icon" title="Mark as read">
<img src="/icons/read_post.png">
</a>
<a class="disabled icon" title="Mark as unread">
<img src="/icons/unread_post.png">
</a>
<a class="disabled icon" title="Delete post">
<img src="/icons/delete_post.png">
</a>
{% endif %}
<form class="flex-container" style="margin-left: auto; margin-right: 10px;" action="/subscribe" method="post">
<form class="flex-container" action="/subscribe" method="post">
<label for="public_key">Public Key</label>
<input type="text" id="public_key" name="public_key" size=50 maxlength=53>
<input type="text" id="public_key" name="public_key" maxlength=53>
<input type="submit" value="Subscribe">
<input type="submit" value="Unsubscribe" formaction="/unsubscribe">
</form>