A local-first private AI assistant for everyday use. Runs on-device models with encrypted P2P sync, and supports sharing chats publicly on ATProto.
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

Added p2p chat sync

authored by

Anandu Pavanan and committed by
GitHub
120d1b2e a7114126

+1613 -177
+594 -16
Cargo.lock
··· 409 409 checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063" 410 410 411 411 [[package]] 412 + name = "asn1-rs" 413 + version = "0.7.1" 414 + source = "registry+https://github.com/rust-lang/crates.io-index" 415 + checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" 416 + dependencies = [ 417 + "asn1-rs-derive", 418 + "asn1-rs-impl", 419 + "displaydoc", 420 + "nom 7.1.3", 421 + "num-traits", 422 + "rusticata-macros", 423 + "thiserror 2.0.18", 424 + "time", 425 + ] 426 + 427 + [[package]] 428 + name = "asn1-rs-derive" 429 + version = "0.6.0" 430 + source = "registry+https://github.com/rust-lang/crates.io-index" 431 + checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" 432 + dependencies = [ 433 + "proc-macro2", 434 + "quote", 435 + "syn 2.0.117", 436 + "synstructure", 437 + ] 438 + 439 + [[package]] 440 + name = "asn1-rs-impl" 441 + version = "0.2.0" 442 + source = "registry+https://github.com/rust-lang/crates.io-index" 443 + checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" 444 + dependencies = [ 445 + "proc-macro2", 446 + "quote", 447 + "syn 2.0.117", 448 + ] 449 + 450 + [[package]] 412 451 name = "assert-json-diff" 413 452 version = "2.0.2" 414 453 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 712 751 ] 713 752 714 753 [[package]] 754 + name = "bao-tree" 755 + version = "0.16.0" 756 + source = "registry+https://github.com/rust-lang/crates.io-index" 757 + checksum = "06384416b1825e6e04fde63262fda2dc408f5b64c02d04e0d8b70ae72c17a52b" 758 + dependencies = [ 759 + "blake3", 760 + "bytes", 761 + "futures-lite", 762 + "genawaiter", 763 + "iroh-io", 764 + "positioned-io", 765 + "range-collections", 766 + "self_cell", 767 + "serde", 768 + "smallvec", 769 + "tokio", 770 + ] 771 + 772 + [[package]] 715 773 name = "base-x" 716 774 version = "0.2.11" 717 775 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 758 816 checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 759 817 760 818 [[package]] 819 + name = "binary-merge" 820 + version = "0.1.2" 821 + source = "registry+https://github.com/rust-lang/crates.io-index" 822 + checksum = "597bb81c80a54b6a4381b23faba8d7774b144c94cbd1d6fe3f1329bd776554ab" 823 + 824 + [[package]] 761 825 name = "bincode" 762 826 version = "1.3.3" 763 827 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 915 979 ] 916 980 917 981 [[package]] 982 + name = "cesu8" 983 + version = "1.1.0" 984 + source = "registry+https://github.com/rust-lang/crates.io-index" 985 + checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 986 + 987 + [[package]] 918 988 name = "cfg-if" 919 989 version = "1.0.4" 920 990 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 933 1003 checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" 934 1004 dependencies = [ 935 1005 "iana-time-zone", 1006 + "js-sys", 936 1007 "num-traits", 937 1008 "serde", 1009 + "wasm-bindgen", 938 1010 "windows-link", 939 1011 ] 940 1012 ··· 1036 1108 checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" 1037 1109 1038 1110 [[package]] 1111 + name = "combine" 1112 + version = "4.6.7" 1113 + source = "registry+https://github.com/rust-lang/crates.io-index" 1114 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 1115 + dependencies = [ 1116 + "bytes", 1117 + "memchr", 1118 + ] 1119 + 1120 + [[package]] 1039 1121 name = "concurrent-queue" 1040 1122 version = "2.5.0" 1041 1123 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1504 1586 "const-oid 0.10.2", 1505 1587 "pem-rfc7468 1.0.0", 1506 1588 "zeroize", 1589 + ] 1590 + 1591 + [[package]] 1592 + name = "der-parser" 1593 + version = "10.0.0" 1594 + source = "registry+https://github.com/rust-lang/crates.io-index" 1595 + checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" 1596 + dependencies = [ 1597 + "asn1-rs", 1598 + "displaydoc", 1599 + "nom 7.1.3", 1600 + "num-bigint", 1601 + "num-traits", 1602 + "rusticata-macros", 1507 1603 ] 1508 1604 1509 1605 [[package]] ··· 1895 1991 1896 1992 [[package]] 1897 1993 name = "env_logger" 1898 - version = "0.11.9" 1994 + version = "0.11.10" 1899 1995 source = "registry+https://github.com/rust-lang/crates.io-index" 1900 - checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" 1996 + checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" 1901 1997 dependencies = [ 1998 + "anstream", 1999 + "anstyle", 1902 2000 "env_filter", 2001 + "jiff", 1903 2002 "log", 1904 2003 ] 1905 2004 ··· 2374 2473 ] 2375 2474 2376 2475 [[package]] 2476 + name = "genawaiter" 2477 + version = "0.99.1" 2478 + source = "registry+https://github.com/rust-lang/crates.io-index" 2479 + checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" 2480 + dependencies = [ 2481 + "futures-core", 2482 + "genawaiter-macro", 2483 + "genawaiter-proc-macro", 2484 + "proc-macro-hack", 2485 + ] 2486 + 2487 + [[package]] 2488 + name = "genawaiter-macro" 2489 + version = "0.99.1" 2490 + source = "registry+https://github.com/rust-lang/crates.io-index" 2491 + checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" 2492 + 2493 + [[package]] 2494 + name = "genawaiter-proc-macro" 2495 + version = "0.99.1" 2496 + source = "registry+https://github.com/rust-lang/crates.io-index" 2497 + checksum = "784f84eebc366e15251c4a8c3acee82a6a6f427949776ecb88377362a9621738" 2498 + dependencies = [ 2499 + "proc-macro-error", 2500 + "proc-macro-hack", 2501 + "proc-macro2", 2502 + "quote", 2503 + "syn 1.0.109", 2504 + ] 2505 + 2506 + [[package]] 2377 2507 name = "generator" 2378 2508 version = "0.8.8" 2379 2509 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3014 3144 ] 3015 3145 3016 3146 [[package]] 3147 + name = "inplace-vec-builder" 3148 + version = "0.1.1" 3149 + source = "registry+https://github.com/rust-lang/crates.io-index" 3150 + checksum = "cf64c2edc8226891a71f127587a2861b132d2b942310843814d5001d99a1d307" 3151 + dependencies = [ 3152 + "smallvec", 3153 + ] 3154 + 3155 + [[package]] 3017 3156 name = "integer-encoding" 3018 3157 version = "3.0.4" 3019 3158 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3145 3284 ] 3146 3285 3147 3286 [[package]] 3287 + name = "iroh-blobs" 3288 + version = "0.99.0" 3289 + source = "registry+https://github.com/rust-lang/crates.io-index" 3290 + checksum = "51b06914e77bd07bc1b3600096be66e2a63d391e8f4a901f61771630e20f2116" 3291 + dependencies = [ 3292 + "arrayvec", 3293 + "bao-tree", 3294 + "bytes", 3295 + "cfg_aliases", 3296 + "chrono", 3297 + "data-encoding", 3298 + "derive_more", 3299 + "futures-lite", 3300 + "genawaiter", 3301 + "hex", 3302 + "iroh", 3303 + "iroh-base", 3304 + "iroh-io", 3305 + "iroh-metrics", 3306 + "iroh-tickets", 3307 + "irpc", 3308 + "n0-error", 3309 + "n0-future", 3310 + "nested_enum_utils", 3311 + "noq", 3312 + "postcard", 3313 + "rand 0.9.2", 3314 + "range-collections", 3315 + "redb", 3316 + "ref-cast", 3317 + "reflink-copy", 3318 + "self_cell", 3319 + "serde", 3320 + "smallvec", 3321 + "tokio", 3322 + "tracing", 3323 + ] 3324 + 3325 + [[package]] 3148 3326 name = "iroh-gossip" 3149 3327 version = "0.97.0" 3150 3328 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3175 3353 ] 3176 3354 3177 3355 [[package]] 3356 + name = "iroh-io" 3357 + version = "0.6.2" 3358 + source = "registry+https://github.com/rust-lang/crates.io-index" 3359 + checksum = "e0a5feb781017b983ff1b155cd1faf8174da2acafd807aa482876da2d7e6577a" 3360 + dependencies = [ 3361 + "bytes", 3362 + "futures-lite", 3363 + "pin-project", 3364 + "smallvec", 3365 + "tokio", 3366 + ] 3367 + 3368 + [[package]] 3178 3369 name = "iroh-metrics" 3179 3370 version = "0.38.3" 3180 3371 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3200 3391 "proc-macro2", 3201 3392 "quote", 3202 3393 "syn 2.0.117", 3203 - ] 3204 - 3205 - [[package]] 3206 - name = "iroh-ping" 3207 - version = "0.9.0" 3208 - source = "registry+https://github.com/rust-lang/crates.io-index" 3209 - checksum = "ae0d3040396ce546281e3716e3fa88bcc425a99ab2fd2715484a86f050a5d36e" 3210 - dependencies = [ 3211 - "anyhow", 3212 - "iroh", 3213 - "iroh-metrics", 3214 - "n0-error", 3215 3394 ] 3216 3395 3217 3396 [[package]] ··· 3281 3460 source = "registry+https://github.com/rust-lang/crates.io-index" 3282 3461 checksum = "4f47b7c52662d673df377b5ac40c121c7ff56eb764e520fae6543686132f7957" 3283 3462 dependencies = [ 3463 + "futures-buffered", 3284 3464 "futures-util", 3285 3465 "irpc-derive", 3286 3466 "n0-error", 3287 3467 "n0-future", 3468 + "noq", 3469 + "postcard", 3470 + "rcgen", 3471 + "rustls", 3288 3472 "serde", 3473 + "smallvec", 3289 3474 "tokio", 3290 3475 "tokio-util", 3291 3476 "tracing", ··· 3324 3509 checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 3325 3510 3326 3511 [[package]] 3512 + name = "jiff" 3513 + version = "0.2.23" 3514 + source = "registry+https://github.com/rust-lang/crates.io-index" 3515 + checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" 3516 + dependencies = [ 3517 + "jiff-static", 3518 + "log", 3519 + "portable-atomic", 3520 + "portable-atomic-util", 3521 + "serde_core", 3522 + ] 3523 + 3524 + [[package]] 3525 + name = "jiff-static" 3526 + version = "0.2.23" 3527 + source = "registry+https://github.com/rust-lang/crates.io-index" 3528 + checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" 3529 + dependencies = [ 3530 + "proc-macro2", 3531 + "quote", 3532 + "syn 2.0.117", 3533 + ] 3534 + 3535 + [[package]] 3536 + name = "jni" 3537 + version = "0.21.1" 3538 + source = "registry+https://github.com/rust-lang/crates.io-index" 3539 + checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 3540 + dependencies = [ 3541 + "cesu8", 3542 + "cfg-if", 3543 + "combine", 3544 + "jni-sys 0.3.1", 3545 + "log", 3546 + "thiserror 1.0.69", 3547 + "walkdir", 3548 + "windows-sys 0.45.0", 3549 + ] 3550 + 3551 + [[package]] 3552 + name = "jni-sys" 3553 + version = "0.3.1" 3554 + source = "registry+https://github.com/rust-lang/crates.io-index" 3555 + checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" 3556 + dependencies = [ 3557 + "jni-sys 0.4.1", 3558 + ] 3559 + 3560 + [[package]] 3561 + name = "jni-sys" 3562 + version = "0.4.1" 3563 + source = "registry+https://github.com/rust-lang/crates.io-index" 3564 + checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" 3565 + dependencies = [ 3566 + "jni-sys-macros", 3567 + ] 3568 + 3569 + [[package]] 3570 + name = "jni-sys-macros" 3571 + version = "0.4.1" 3572 + source = "registry+https://github.com/rust-lang/crates.io-index" 3573 + checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" 3574 + dependencies = [ 3575 + "quote", 3576 + "syn 2.0.117", 3577 + ] 3578 + 3579 + [[package]] 3327 3580 name = "jobserver" 3328 3581 version = "0.1.34" 3329 3582 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3872 4125 ] 3873 4126 3874 4127 [[package]] 4128 + name = "nested_enum_utils" 4129 + version = "0.2.3" 4130 + source = "registry+https://github.com/rust-lang/crates.io-index" 4131 + checksum = "b1d5475271bdd36a4a2769eac1ef88df0f99428ea43e52dfd8b0ee5cb674695f" 4132 + dependencies = [ 4133 + "proc-macro-crate", 4134 + "proc-macro2", 4135 + "quote", 4136 + "syn 2.0.117", 4137 + ] 4138 + 4139 + [[package]] 3875 4140 name = "netdev" 3876 4141 version = "0.40.1" 3877 4142 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4075 4340 "rustc-hash", 4076 4341 "rustls", 4077 4342 "rustls-pki-types", 4343 + "rustls-platform-verifier", 4078 4344 "slab", 4079 4345 "sorted-index-buffer", 4080 4346 "thiserror 2.0.18", ··· 4301 4567 ] 4302 4568 4303 4569 [[package]] 4570 + name = "oid-registry" 4571 + version = "0.8.1" 4572 + source = "registry+https://github.com/rust-lang/crates.io-index" 4573 + checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" 4574 + dependencies = [ 4575 + "asn1-rs", 4576 + ] 4577 + 4578 + [[package]] 4304 4579 name = "once_cell" 4305 4580 version = "1.21.4" 4306 4581 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4493 4768 checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 4494 4769 4495 4770 [[package]] 4771 + name = "pem" 4772 + version = "3.0.6" 4773 + source = "registry+https://github.com/rust-lang/crates.io-index" 4774 + checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" 4775 + dependencies = [ 4776 + "base64", 4777 + "serde_core", 4778 + ] 4779 + 4780 + [[package]] 4496 4781 name = "pem-rfc7468" 4497 4782 version = "0.7.0" 4498 4783 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4663 4948 ] 4664 4949 4665 4950 [[package]] 4951 + name = "portable-atomic-util" 4952 + version = "0.2.6" 4953 + source = "registry+https://github.com/rust-lang/crates.io-index" 4954 + checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" 4955 + dependencies = [ 4956 + "portable-atomic", 4957 + ] 4958 + 4959 + [[package]] 4666 4960 name = "portmapper" 4667 4961 version = "0.15.0" 4668 4962 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4690 4984 "tower-layer", 4691 4985 "tracing", 4692 4986 "url", 4987 + ] 4988 + 4989 + [[package]] 4990 + name = "positioned-io" 4991 + version = "0.3.5" 4992 + source = "registry+https://github.com/rust-lang/crates.io-index" 4993 + checksum = "d4ec4b80060f033312b99b6874025d9503d2af87aef2dd4c516e253fbfcdada7" 4994 + dependencies = [ 4995 + "libc", 4996 + "winapi", 4693 4997 ] 4694 4998 4695 4999 [[package]] ··· 4770 5074 ] 4771 5075 4772 5076 [[package]] 5077 + name = "proc-macro-error" 5078 + version = "0.4.12" 5079 + source = "registry+https://github.com/rust-lang/crates.io-index" 5080 + checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" 5081 + dependencies = [ 5082 + "proc-macro-error-attr", 5083 + "proc-macro2", 5084 + "quote", 5085 + "syn 1.0.109", 5086 + "version_check", 5087 + ] 5088 + 5089 + [[package]] 5090 + name = "proc-macro-error-attr" 5091 + version = "0.4.12" 5092 + source = "registry+https://github.com/rust-lang/crates.io-index" 5093 + checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" 5094 + dependencies = [ 5095 + "proc-macro2", 5096 + "quote", 5097 + "syn 1.0.109", 5098 + "syn-mid", 5099 + "version_check", 5100 + ] 5101 + 5102 + [[package]] 5103 + name = "proc-macro-hack" 5104 + version = "0.5.20+deprecated" 5105 + source = "registry+https://github.com/rust-lang/crates.io-index" 5106 + checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" 5107 + 5108 + [[package]] 4773 5109 name = "proc-macro2" 4774 5110 version = "1.0.106" 4775 5111 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4969 5305 ] 4970 5306 4971 5307 [[package]] 5308 + name = "range-collections" 5309 + version = "0.4.6" 5310 + source = "registry+https://github.com/rust-lang/crates.io-index" 5311 + checksum = "861706ea9c4aded7584c5cd1d241cec2ea7f5f50999f236c22b65409a1f1a0d0" 5312 + dependencies = [ 5313 + "binary-merge", 5314 + "inplace-vec-builder", 5315 + "ref-cast", 5316 + "serde", 5317 + "smallvec", 5318 + ] 5319 + 5320 + [[package]] 4972 5321 name = "rayon" 4973 5322 version = "1.11.0" 4974 5323 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4989 5338 ] 4990 5339 4991 5340 [[package]] 5341 + name = "rcgen" 5342 + version = "0.14.7" 5343 + source = "registry+https://github.com/rust-lang/crates.io-index" 5344 + checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" 5345 + dependencies = [ 5346 + "pem", 5347 + "ring", 5348 + "rustls-pki-types", 5349 + "time", 5350 + "x509-parser", 5351 + "yasna", 5352 + ] 5353 + 5354 + [[package]] 5355 + name = "redb" 5356 + version = "2.6.3" 5357 + source = "registry+https://github.com/rust-lang/crates.io-index" 5358 + checksum = "8eca1e9d98d5a7e9002d0013e18d5a9b000aee942eb134883a82f06ebffb6c01" 5359 + dependencies = [ 5360 + "libc", 5361 + ] 5362 + 5363 + [[package]] 4992 5364 name = "redox_syscall" 4993 5365 version = "0.5.18" 4994 5366 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5026 5398 "proc-macro2", 5027 5399 "quote", 5028 5400 "syn 2.0.117", 5401 + ] 5402 + 5403 + [[package]] 5404 + name = "reflink-copy" 5405 + version = "0.1.29" 5406 + source = "registry+https://github.com/rust-lang/crates.io-index" 5407 + checksum = "13362233b147e57674c37b802d216b7c5e3dcccbed8967c84f0d8d223868ae27" 5408 + dependencies = [ 5409 + "cfg-if", 5410 + "libc", 5411 + "rustix", 5412 + "windows", 5029 5413 ] 5030 5414 5031 5415 [[package]] ··· 5266 5650 ] 5267 5651 5268 5652 [[package]] 5653 + name = "rusticata-macros" 5654 + version = "4.1.0" 5655 + source = "registry+https://github.com/rust-lang/crates.io-index" 5656 + checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" 5657 + dependencies = [ 5658 + "nom 7.1.3", 5659 + ] 5660 + 5661 + [[package]] 5269 5662 name = "rustix" 5270 5663 version = "1.1.4" 5271 5664 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5294 5687 ] 5295 5688 5296 5689 [[package]] 5690 + name = "rustls-native-certs" 5691 + version = "0.8.3" 5692 + source = "registry+https://github.com/rust-lang/crates.io-index" 5693 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 5694 + dependencies = [ 5695 + "openssl-probe", 5696 + "rustls-pki-types", 5697 + "schannel", 5698 + "security-framework 3.7.0", 5699 + ] 5700 + 5701 + [[package]] 5297 5702 name = "rustls-pki-types" 5298 5703 version = "1.14.0" 5299 5704 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5304 5709 ] 5305 5710 5306 5711 [[package]] 5712 + name = "rustls-platform-verifier" 5713 + version = "0.6.2" 5714 + source = "registry+https://github.com/rust-lang/crates.io-index" 5715 + checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" 5716 + dependencies = [ 5717 + "core-foundation 0.10.1", 5718 + "core-foundation-sys", 5719 + "jni", 5720 + "log", 5721 + "once_cell", 5722 + "rustls", 5723 + "rustls-native-certs", 5724 + "rustls-platform-verifier-android", 5725 + "rustls-webpki", 5726 + "security-framework 3.7.0", 5727 + "security-framework-sys", 5728 + "webpki-root-certs", 5729 + "windows-sys 0.61.2", 5730 + ] 5731 + 5732 + [[package]] 5733 + name = "rustls-platform-verifier-android" 5734 + version = "0.1.1" 5735 + source = "registry+https://github.com/rust-lang/crates.io-index" 5736 + checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 5737 + 5738 + [[package]] 5307 5739 name = "rustls-webpki" 5308 5740 version = "0.103.10" 5309 5741 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5347 5779 version = "1.0.23" 5348 5780 source = "registry+https://github.com/rust-lang/crates.io-index" 5349 5781 checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 5782 + 5783 + [[package]] 5784 + name = "same-file" 5785 + version = "1.0.6" 5786 + source = "registry+https://github.com/rust-lang/crates.io-index" 5787 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 5788 + dependencies = [ 5789 + "winapi-util", 5790 + ] 5350 5791 5351 5792 [[package]] 5352 5793 name = "scc" ··· 5745 6186 version = "1.15.1" 5746 6187 source = "registry+https://github.com/rust-lang/crates.io-index" 5747 6188 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 6189 + dependencies = [ 6190 + "serde", 6191 + ] 5748 6192 5749 6193 [[package]] 5750 6194 name = "smol_str" ··· 5936 6380 ] 5937 6381 5938 6382 [[package]] 6383 + name = "syn-mid" 6384 + version = "0.5.4" 6385 + source = "registry+https://github.com/rust-lang/crates.io-index" 6386 + checksum = "fea305d57546cc8cd04feb14b62ec84bf17f50e3f7b12560d7bfa9265f39d9ed" 6387 + dependencies = [ 6388 + "proc-macro2", 6389 + "quote", 6390 + "syn 1.0.109", 6391 + ] 6392 + 6393 + [[package]] 5939 6394 name = "sync_wrapper" 5940 6395 version = "1.0.2" 5941 6396 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6084 6539 6085 6540 [[package]] 6086 6541 name = "tiles" 6087 - version = "0.4.5" 6542 + version = "0.4.6" 6088 6543 dependencies = [ 6089 6544 "anyhow", 6090 6545 "async-std", ··· 6092 6547 "axum-macros", 6093 6548 "clap", 6094 6549 "data-encoding", 6550 + "env_logger", 6095 6551 "futures-util", 6096 6552 "hf-hub", 6097 6553 "iroh", 6554 + "iroh-blobs", 6098 6555 "iroh-gossip", 6099 - "iroh-ping", 6100 6556 "iroh-tickets", 6101 6557 "keyring", 6558 + "log", 6102 6559 "owo-colors", 6103 6560 "postcard", 6104 6561 "reqwest", ··· 6723 7180 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 6724 7181 6725 7182 [[package]] 7183 + name = "walkdir" 7184 + version = "2.5.0" 7185 + source = "registry+https://github.com/rust-lang/crates.io-index" 7186 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 7187 + dependencies = [ 7188 + "same-file", 7189 + "winapi-util", 7190 + ] 7191 + 7192 + [[package]] 6726 7193 name = "want" 6727 7194 version = "0.3.1" 6728 7195 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6882 7349 ] 6883 7350 6884 7351 [[package]] 7352 + name = "webpki-root-certs" 7353 + version = "1.0.6" 7354 + source = "registry+https://github.com/rust-lang/crates.io-index" 7355 + checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" 7356 + dependencies = [ 7357 + "rustls-pki-types", 7358 + ] 7359 + 7360 + [[package]] 6885 7361 name = "webpki-roots" 6886 7362 version = "0.26.11" 6887 7363 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6922 7398 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 6923 7399 6924 7400 [[package]] 7401 + name = "winapi-util" 7402 + version = "0.1.11" 7403 + source = "registry+https://github.com/rust-lang/crates.io-index" 7404 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 7405 + dependencies = [ 7406 + "windows-sys 0.61.2", 7407 + ] 7408 + 7409 + [[package]] 6925 7410 name = "winapi-x86_64-pc-windows-gnu" 6926 7411 version = "0.4.0" 6927 7412 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7041 7526 7042 7527 [[package]] 7043 7528 name = "windows-sys" 7529 + version = "0.45.0" 7530 + source = "registry+https://github.com/rust-lang/crates.io-index" 7531 + checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 7532 + dependencies = [ 7533 + "windows-targets 0.42.2", 7534 + ] 7535 + 7536 + [[package]] 7537 + name = "windows-sys" 7044 7538 version = "0.48.0" 7045 7539 source = "registry+https://github.com/rust-lang/crates.io-index" 7046 7540 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" ··· 7086 7580 7087 7581 [[package]] 7088 7582 name = "windows-targets" 7583 + version = "0.42.2" 7584 + source = "registry+https://github.com/rust-lang/crates.io-index" 7585 + checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 7586 + dependencies = [ 7587 + "windows_aarch64_gnullvm 0.42.2", 7588 + "windows_aarch64_msvc 0.42.2", 7589 + "windows_i686_gnu 0.42.2", 7590 + "windows_i686_msvc 0.42.2", 7591 + "windows_x86_64_gnu 0.42.2", 7592 + "windows_x86_64_gnullvm 0.42.2", 7593 + "windows_x86_64_msvc 0.42.2", 7594 + ] 7595 + 7596 + [[package]] 7597 + name = "windows-targets" 7089 7598 version = "0.48.5" 7090 7599 source = "registry+https://github.com/rust-lang/crates.io-index" 7091 7600 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" ··· 7143 7652 7144 7653 [[package]] 7145 7654 name = "windows_aarch64_gnullvm" 7655 + version = "0.42.2" 7656 + source = "registry+https://github.com/rust-lang/crates.io-index" 7657 + checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 7658 + 7659 + [[package]] 7660 + name = "windows_aarch64_gnullvm" 7146 7661 version = "0.48.5" 7147 7662 source = "registry+https://github.com/rust-lang/crates.io-index" 7148 7663 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" ··· 7158 7673 version = "0.53.1" 7159 7674 source = "registry+https://github.com/rust-lang/crates.io-index" 7160 7675 checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 7676 + 7677 + [[package]] 7678 + name = "windows_aarch64_msvc" 7679 + version = "0.42.2" 7680 + source = "registry+https://github.com/rust-lang/crates.io-index" 7681 + checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 7161 7682 7162 7683 [[package]] 7163 7684 name = "windows_aarch64_msvc" ··· 7179 7700 7180 7701 [[package]] 7181 7702 name = "windows_i686_gnu" 7703 + version = "0.42.2" 7704 + source = "registry+https://github.com/rust-lang/crates.io-index" 7705 + checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 7706 + 7707 + [[package]] 7708 + name = "windows_i686_gnu" 7182 7709 version = "0.48.5" 7183 7710 source = "registry+https://github.com/rust-lang/crates.io-index" 7184 7711 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" ··· 7209 7736 7210 7737 [[package]] 7211 7738 name = "windows_i686_msvc" 7739 + version = "0.42.2" 7740 + source = "registry+https://github.com/rust-lang/crates.io-index" 7741 + checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 7742 + 7743 + [[package]] 7744 + name = "windows_i686_msvc" 7212 7745 version = "0.48.5" 7213 7746 source = "registry+https://github.com/rust-lang/crates.io-index" 7214 7747 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" ··· 7227 7760 7228 7761 [[package]] 7229 7762 name = "windows_x86_64_gnu" 7763 + version = "0.42.2" 7764 + source = "registry+https://github.com/rust-lang/crates.io-index" 7765 + checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 7766 + 7767 + [[package]] 7768 + name = "windows_x86_64_gnu" 7230 7769 version = "0.48.5" 7231 7770 source = "registry+https://github.com/rust-lang/crates.io-index" 7232 7771 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" ··· 7245 7784 7246 7785 [[package]] 7247 7786 name = "windows_x86_64_gnullvm" 7787 + version = "0.42.2" 7788 + source = "registry+https://github.com/rust-lang/crates.io-index" 7789 + checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 7790 + 7791 + [[package]] 7792 + name = "windows_x86_64_gnullvm" 7248 7793 version = "0.48.5" 7249 7794 source = "registry+https://github.com/rust-lang/crates.io-index" 7250 7795 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" ··· 7260 7805 version = "0.53.1" 7261 7806 source = "registry+https://github.com/rust-lang/crates.io-index" 7262 7807 checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 7808 + 7809 + [[package]] 7810 + name = "windows_x86_64_msvc" 7811 + version = "0.42.2" 7812 + source = "registry+https://github.com/rust-lang/crates.io-index" 7813 + checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 7263 7814 7264 7815 [[package]] 7265 7816 name = "windows_x86_64_msvc" ··· 7456 8007 ] 7457 8008 7458 8009 [[package]] 8010 + name = "x509-parser" 8011 + version = "0.18.1" 8012 + source = "registry+https://github.com/rust-lang/crates.io-index" 8013 + checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" 8014 + dependencies = [ 8015 + "asn1-rs", 8016 + "data-encoding", 8017 + "der-parser", 8018 + "lazy_static", 8019 + "nom 7.1.3", 8020 + "oid-registry", 8021 + "ring", 8022 + "rusticata-macros", 8023 + "thiserror 2.0.18", 8024 + "time", 8025 + ] 8026 + 8027 + [[package]] 7459 8028 name = "xml-rs" 7460 8029 version = "0.8.28" 7461 8030 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7468 8037 checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" 7469 8038 dependencies = [ 7470 8039 "xml-rs", 8040 + ] 8041 + 8042 + [[package]] 8043 + name = "yasna" 8044 + version = "0.5.2" 8045 + source = "registry+https://github.com/rust-lang/crates.io-index" 8046 + checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" 8047 + dependencies = [ 8048 + "time", 7471 8049 ] 7472 8050 7473 8051 [[package]]
-1
Cargo.toml
··· 4 4 "tilekit", 5 5 "tiles", 6 6 ] 7 -
+2
justfile
··· 31 31 bundle_pkg_full: 32 32 ./pkg/build.sh 33 33 ./pkg/build_full.sh 34 + 35 + # runtiles: RUST_LOG=tiles=info,iroh=off cargo run
+17 -3
tilekit/src/modelfile.rs
··· 38 38 } 39 39 } 40 40 41 - #[derive(Debug, Clone, Copy, serde::Serialize)] 41 + #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] 42 42 #[serde(rename_all = "lowercase")] 43 43 pub enum Role { 44 44 System, ··· 61 61 } 62 62 } 63 63 64 + #[derive(Debug)] 65 + pub struct RoleError { 66 + pub error: String, 67 + } 68 + 69 + impl std::error::Error for RoleError {} 70 + impl Display for RoleError { 71 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 72 + write!(f, "{}", self.error) 73 + } 74 + } 75 + 64 76 impl FromStr for Role { 65 - type Err = String; 77 + type Err = RoleError; 66 78 fn from_str(s: &str) -> Result<Self, Self::Err> { 67 79 match s.to_lowercase().as_str() { 68 80 "system" => Ok(Role::System), 69 81 "user" => Ok(Role::User), 70 82 "assistant" => Ok(Role::Assistant), 71 - _ => Err("Invalid Role".to_owned()), 83 + _ => Err(RoleError { 84 + error: "Invalid Role".to_owned(), 85 + }), 72 86 } 73 87 } 74 88 }
+16 -17
tilekit/src/optimize.rs
··· 113 113 } 114 114 115 115 pub async fn optimize( 116 - modelfile_path: String, 116 + modelfile_path: &str, 117 117 data_path: Option<String>, 118 - model: String, 118 + model: &str, 119 119 ) -> Result<Modelfile, String> { 120 120 println!("Optimizing Modelfile: {}", modelfile_path); 121 121 122 122 // 1. Read Modelfile 123 - let content = fs::read_to_string(&modelfile_path) 123 + let content = fs::read_to_string(modelfile_path) 124 124 .map_err(|e| format!("Error reading Modelfile: {}", e))?; 125 125 126 126 let mut modelfile: Modelfile = content ··· 136 136 println!("Current SYSTEM prompt: \"{}\"", system_prompt); 137 137 138 138 // 2. Configure DSRs 139 - let lm = LM::builder().model(model).build().await.map_err(|e| { 140 - format!( 141 - "Error configuring LM: {}. Make sure appropriate API keys are set.", 142 - e 143 - ) 144 - })?; 139 + let lm = LM::builder() 140 + .model(model.to_owned()) 141 + .build() 142 + .await 143 + .map_err(|e| { 144 + format!( 145 + "Error configuring LM: {}. Make sure appropriate API keys are set.", 146 + e 147 + ) 148 + })?; 145 149 146 150 configure(lm, ChatAdapter); 147 151 ··· 336 340 337 341 #[tokio::test] 338 342 async fn test_optimize_missing_file() { 339 - let result = optimize( 340 - "nonexistent_file.modelfile".to_string(), 341 - None, 342 - "openai:gpt-4o-mini".to_string(), 343 - ) 344 - .await; 343 + let result = optimize("nonexistent_file.modelfile", None, "openai:gpt-4o-mini").await; 345 344 346 345 assert!(result.is_err()); 347 346 assert!(result.unwrap_err().contains("Error reading Modelfile")); ··· 355 354 std::fs::write(&temp_file, "FROM llama3.2\n").unwrap(); 356 355 357 356 let result = optimize( 358 - temp_file.to_string_lossy().to_string(), 357 + temp_file.to_str().expect("File path is invalid"), 359 358 None, 360 - "openai:gpt-4o-mini".to_string(), 359 + "openai:gpt-4o-mini", 361 360 ) 362 361 .await; 363 362
+4 -2
tiles/Cargo.toml
··· 1 1 [package] 2 2 name = "tiles" 3 - version = "0.4.5" 3 + version = "0.4.6" 4 4 edition = "2024" 5 5 6 6 [dependencies] ··· 22 22 uuid = {version = "1.21.0", features = ["v7"]} 23 23 axum = "0.8.8" 24 24 iroh = {version = "0.97.0", features = ["address-lookup-mdns"]} 25 - iroh-ping = "0.9.0" 26 25 iroh-tickets = "0.4.0" 27 26 axum-macros = "0.5.0" 28 27 iroh-gossip = "0.97.0" 29 28 postcard = "1.1.3" 30 29 data-encoding = "2.10.0" 31 30 sha2 = "0.10.9" 31 + iroh-blobs = "0.99.0" 32 + log = "0.4.29" 33 + env_logger = "0.11.10" 32 34 33 35 [dev-dependencies] 34 36 tempfile = "3"
+70 -59
tiles/src/core/accounts.rs
··· 2 2 // Stuff related to account and identity system 3 3 use anyhow::{Result, anyhow}; 4 4 use iroh::SecretKey; 5 - use rusqlite::{Connection, types::FromSqlError}; 5 + use rusqlite::{Connection, Row, types::FromSqlError}; 6 6 use std::{ 7 7 fmt::Display, 8 8 time::{SystemTime, UNIX_EPOCH}, ··· 71 71 #[allow(dead_code)] 72 72 #[derive(Debug, Clone)] 73 73 pub struct User { 74 - pub id: uuid::Uuid, 74 + pub id: String, 75 75 pub user_id: String, 76 76 pub username: String, 77 77 pub active_profile: bool, ··· 208 208 209 209 fetch_current_user 210 210 .query_one([], |row| { 211 - let id: String = row.get(0)?; 212 211 let account_type: String = row.get(3)?; 213 212 let created_at: f64 = row.get(6)?; 214 213 let updated_at: f64 = row.get(7)?; 215 214 Ok(User { 216 - id: Uuid::try_parse(&id).map_err(FromSqlError::other)?, 215 + id: row.get(0)?, 217 216 user_id: row.get(1)?, 218 217 username: row.get(2)?, 219 218 account_type: ACCOUNT::try_from(account_type).map_err(FromSqlError::other)?, ··· 232 231 233 232 fetch_current_user 234 233 .query_one([did], |row| { 235 - let id: String = row.get(0)?; 236 234 let account_type: String = row.get(3)?; 237 235 let created_at: f64 = row.get(6)?; 238 236 let updated_at: f64 = row.get(7)?; 239 237 Ok(User { 240 - id: Uuid::try_parse(&id).map_err(FromSqlError::other)?, 238 + id: row.get(0)?, 241 239 user_id: row.get(1)?, 242 240 username: row.get(2)?, 243 241 account_type: ACCOUNT::try_from(account_type).map_err(FromSqlError::other)?, ··· 256 254 let config = get_or_create_config()?; 257 255 let root_user = get_root_user_details(&config)?; 258 256 let user = User { 259 - id: Uuid::now_v7(), 257 + id: Uuid::now_v7().to_string(), 260 258 user_id: root_user.id, 261 259 username: root_user.nickname, 262 260 account_type: ACCOUNT::LOCAL, ··· 290 288 // we will wait for it until we solve the sync part 291 289 pub fn save_peer_account_db(db_conn: &Connection, user_id: &str, nickname: &str) -> Result<()> { 292 290 let user = User { 293 - id: Uuid::now_v7(), 291 + id: Uuid::now_v7().to_string(), 294 292 user_id: String::from(user_id), 295 293 username: String::from(nickname), 296 294 account_type: ACCOUNT::PEER, ··· 320 318 Ok(()) 321 319 } 322 320 323 - pub fn get_user_by_user_id(conn: &Connection, user_id: String) -> Result<()> { 324 - let mut fetch_root_user = conn.prepare("select id from users where user_id = ?1")?; 325 - 326 - match fetch_root_user.query_one([user_id], |_row| Ok(())) { 327 - Ok(_) => Ok(()), 328 - Err(rusqlite::Error::QueryReturnedNoRows) => Err(anyhow!("User doesnt exist")), 329 - Err(_err) => Err(anyhow!("Fetching user from db failed")), 330 - } 331 - } 332 - 333 321 fn create_root_user(root_user_config: &Table, nickname: Option<String>) -> Result<Table> { 334 322 let mut root_user_table = root_user_config.clone(); 335 323 let app_name = if cfg!(debug_assertions) { ··· 349 337 } 350 338 } 351 339 340 + fn parse_user_from_row(row: &Row<'_>) -> Result<User, rusqlite::Error> { 341 + let account_type: String = row.get(3)?; 342 + let created_at: f64 = row.get(6)?; 343 + let updated_at: f64 = row.get(7)?; 344 + Ok(User { 345 + id: row.get(0)?, 346 + user_id: row.get(1)?, 347 + username: row.get(2)?, 348 + account_type: ACCOUNT::try_from(account_type).map_err(FromSqlError::other)?, 349 + active_profile: row.get(4)?, 350 + root: row.get(5)?, 351 + 352 + created_at: created_at as u64, 353 + updated_at: updated_at as u64, 354 + }) 355 + } 356 + /// Gets a user account by its DID 357 + pub fn get_user_info(conn: &Connection, did: &str) -> Result<User> { 358 + let mut fetch_user = conn.prepare("select id, user_id, username, account_type, active_profile, root, created_at, updated_at from users where user_id = ?1")?; 359 + 360 + match fetch_user.query_one([did], parse_user_from_row) { 361 + Ok(user) => Ok(user), 362 + Err(rusqlite::Error::QueryReturnedNoRows) => Err(anyhow!("Peer doesnt exist")), 363 + Err(err) => { 364 + log::error!("{:?}", err); 365 + Err(anyhow!("Fetching user from db failed due to {:?}", err)) 366 + } 367 + } 368 + } 369 + 352 370 pub fn get_peer_list(db_conn: &Connection) -> Result<Vec<User>> { 353 371 let mut stmt= db_conn.prepare("select id, user_id, username, account_type, active_profile, root, created_at, updated_at from users where account_type != \'local\'")?; 354 372 355 373 let user_rows = stmt 356 - .query_map([], |row| { 357 - let id: String = row.get(0)?; 358 - let account_type: String = row.get(3)?; 359 - let created_at: f64 = row.get(6)?; 360 - let updated_at: f64 = row.get(7)?; 361 - Ok(User { 362 - id: Uuid::try_parse(&id).map_err(FromSqlError::other)?, 363 - user_id: row.get(1)?, 364 - username: row.get(2)?, 365 - account_type: ACCOUNT::try_from(account_type).map_err(FromSqlError::other)?, 366 - active_profile: row.get(4)?, 367 - root: row.get(5)?, 368 - 369 - created_at: created_at as u64, 370 - updated_at: updated_at as u64, 371 - }) 372 - }) 374 + .query_map([], parse_user_from_row) 373 375 .map_err(<rusqlite::Error as Into<anyhow::Error>>::into)?; 374 376 375 377 let mut peer_list: Vec<User> = vec![]; ··· 407 409 Ok(SecretKey::from_bytes(&signing_key)) 408 410 } 409 411 412 + pub fn create_dummy_user() -> User { 413 + let id = Uuid::now_v7().to_string(); 414 + let chunk = id.split('-').collect::<Vec<&str>>()[0]; 415 + let user_id = format!("did:key:{}", id.split('-').collect::<Vec<&str>>()[0]); 416 + let username = format!("nickname-{}", chunk); 417 + User { 418 + id, 419 + user_id, 420 + username, 421 + account_type: ACCOUNT::PEER, 422 + active_profile: true, 423 + root: true, 424 + created_at: SystemTime::now() 425 + .duration_since(UNIX_EPOCH) 426 + .expect("time went backwards") 427 + .as_secs(), 428 + updated_at: SystemTime::now() 429 + .duration_since(UNIX_EPOCH) 430 + .expect("time went backwards") 431 + .as_secs(), 432 + } 433 + } 410 434 #[cfg(test)] 411 435 mod tests { 412 436 use super::*; ··· 657 681 fn test_get_current_user_valid() { 658 682 let conn = setup_db_schema(); 659 683 let user = User { 660 - id: Uuid::now_v7(), 684 + id: Uuid::now_v7().to_string(), 661 685 user_id: String::from("did"), 662 686 username: String::from("nickname"), 663 687 account_type: ACCOUNT::LOCAL, ··· 691 715 } 692 716 693 717 #[test] 694 - fn test_get_current_user_invalid_uuid_fails() { 695 - let conn = setup_db_schema(); 696 - conn.execute( 697 - "insert into users (id, user_id, username, active_profile, account_type, root, created_at, updated_at) 698 - values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", 699 - ( 700 - "not-a-uuid", 701 - "did:key:test", 702 - "nickname", 703 - true, 704 - "local", 705 - true, 706 - 1_i64, 707 - 1_i64, 708 - ), 709 - ) 710 - .unwrap(); 711 - 712 - assert!(get_current_user(&conn).is_err()); 713 - } 714 - 715 - #[test] 716 718 fn test_get_current_user_invalid_account_type_fails() { 717 719 let conn = setup_db_schema(); 718 720 conn.execute( ··· 758 760 759 761 fn create_user(conn: &Connection, account_type: ACCOUNT) -> User { 760 762 let user = User { 761 - id: Uuid::now_v7(), 763 + id: Uuid::now_v7().to_string(), 762 764 user_id: String::from("did"), 763 765 username: String::from("nickname"), 764 766 account_type, ··· 819 821 let local_user = create_user(&conn, ACCOUNT::LOCAL); 820 822 821 823 assert!(unlink(&conn, &local_user.user_id).is_err()) 824 + } 825 + 826 + #[test] 827 + fn test_get_user_info() { 828 + let conn = setup_db_schema(); 829 + let _local_user = create_user(&conn, ACCOUNT::LOCAL); 830 + save_peer_account_db(&conn, "did:jey:varathan", "varathan").unwrap(); 831 + let user_info = get_user_info(&conn, "did:jey:varathan"); 832 + assert!(user_info.is_ok()) 822 833 } 823 834 }
+490 -21
tiles/src/core/chats.rs
··· 3 3 //! Stuff related to chats with the models 4 4 //! 5 5 6 + use std::str::FromStr; 7 + 6 8 use crate::core::accounts::User; 9 + use crate::core::storage::db::get_db_conn; 7 10 use crate::runtime::mlx::ChatResponse; 8 11 use crate::utils::get_unix_time_now; 9 - use anyhow::Result; 10 - use rusqlite::Connection; 12 + use anyhow::{Result, anyhow}; 13 + use log::info; 14 + use rusqlite::types::FromSqlError; 15 + use rusqlite::{Connection, params}; 11 16 use tilekit::modelfile::Role; 17 + use tokio::sync::mpsc::{self, Sender}; 18 + use tokio::sync::oneshot; 12 19 use uuid::Uuid; 13 20 // model the chats table 14 21 22 + // TODO: foreign types on foreign traits, lul 23 + // someday we can do this for traits sake 24 + // https://dev.to/iprosk/generics-in-rust-murky-waters-of-implementing-foreign-traits-on-foreign-types-584n 25 + 26 + // impl FromSql for Uuid { 27 + // fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> { 28 + // let value_str = String::column_result(value)?; 29 + // Uuid::from_str(&value_str).map_err(|_| FromSqlError::InvalidType) 30 + // } 31 + // } 32 + 15 33 #[derive(serde::Serialize, Clone, Debug)] 16 34 pub struct Message { 17 35 pub r#type: String, ··· 19 37 pub content: String, 20 38 } 21 39 40 + #[derive(Debug, serde::Serialize, serde::Deserialize)] 22 41 pub struct Chats { 23 - pub id: Uuid, 42 + pub id: String, 24 43 content: String, 25 44 // The id of the responses api obj 26 45 response_id: Option<String>, ··· 28 47 role: Role, 29 48 user_id: String, 30 49 // The parent Id of a model's reply 31 - context_id: Option<Uuid>, 50 + context_id: Option<String>, 32 51 created_at: u64, 33 52 updated_at: u64, 53 + row_counter: i64, 54 + } 55 + 56 + type Responder<T> = oneshot::Sender<T>; 57 + pub enum SyncOp { 58 + GetLastRowCounter { 59 + user_id: String, 60 + resp: Responder<Result<i64>>, 61 + }, 62 + GetEncodedData { 63 + user_id: String, 64 + last_row_counter: i64, 65 + resp: Responder<Result<Vec<u8>>>, 66 + }, 67 + ApplyDelta { 68 + delta: Vec<u8>, 69 + resp: Responder<Result<()>>, 70 + }, 34 71 } 35 72 36 73 pub fn save_chat( ··· 39 76 input: &str, 40 77 chat_resp: Option<&ChatResponse>, 41 78 ) -> Result<Chats> { 79 + let row_counter = get_last_row_counter(conn, &user.user_id)?; 42 80 if let Some(chat_response) = chat_resp { 43 81 let chat_resp_cloned = chat_response.clone(); 82 + 44 83 let chat = Chats { 45 - id: Uuid::now_v7(), 84 + id: Uuid::now_v7().to_string(), 46 85 user_id: user.user_id.clone(), 47 86 content: input.to_owned(), 48 87 response_id: Some(chat_resp_cloned.prev_response_id), ··· 50 89 context_id: chat_resp_cloned.parent_chat_id, 51 90 created_at: get_unix_time_now(), 52 91 updated_at: get_unix_time_now(), 92 + row_counter: row_counter + 1, 53 93 }; 54 94 55 - conn.execute("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", (&chat.id.to_string(), &chat.user_id, &chat.content, &chat.response_id, Into::<String>::into(chat.role), &chat.context_id.unwrap_or(Uuid::nil()).to_string(), &chat.created_at.to_string(), &chat.updated_at.to_string()))?; 95 + conn.execute("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at, row_counter) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", (&chat.id.to_string(), &chat.user_id, &chat.content, &chat.response_id, Into::<String>::into(chat.role), &chat.context_id, &chat.created_at.to_string(), &chat.updated_at.to_string(), &chat.row_counter))?; 56 96 57 97 Ok(chat) 58 98 } else { 59 99 let chat = Chats { 60 - id: Uuid::now_v7(), 100 + id: Uuid::now_v7().to_string(), 61 101 user_id: user.user_id.clone(), 62 102 content: input.to_owned(), 63 103 response_id: None, ··· 65 105 context_id: None, 66 106 created_at: get_unix_time_now(), 67 107 updated_at: get_unix_time_now(), 108 + row_counter: row_counter + 1, 68 109 }; 69 110 70 - conn.execute("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)", (&chat.id.to_string(), &chat.user_id, &chat.content, &chat.response_id, Into::<String>::into(chat.role), &chat.context_id.unwrap_or(Uuid::nil()).to_string(), &chat.created_at.to_string(), &chat.updated_at.to_string()))?; 111 + conn.execute("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at, row_counter) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", (&chat.id, &chat.user_id, &chat.content, &chat.response_id, Into::<String>::into(chat.role), &chat.context_id, &chat.created_at.to_string(), &chat.updated_at.to_string(), &chat.row_counter))?; 71 112 72 113 Ok(chat) 73 114 } 74 115 } 75 116 117 + /// Returns the `id` of the last entry of the given user_id 118 + /// Used as the offset point for fetching the chat delta from the user_id 119 + pub fn get_last_entry_id(conn: &Connection, user_id: &str) -> Result<Option<Uuid>> { 120 + match conn.query_row( 121 + "select id from chats where user_id = ?1 order by id desc limit 1", 122 + [user_id], 123 + |row| row.get::<usize, String>(0), 124 + ) { 125 + Ok(res) => Uuid::from_str(&res).map_err(Into::into).map(Some), 126 + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None), 127 + Err(err) => Err(<rusqlite::Error as Into<anyhow::Error>>::into(err)), 128 + } 129 + } 130 + 131 + /// Returns the `row_counter` of the last entry of the given user_id 132 + /// Used as the offset point for fetching the chat delta from the user_id 133 + pub fn get_last_row_counter(conn: &Connection, user_id: &str) -> Result<i64> { 134 + match conn.query_row( 135 + "select max(row_counter) from chats where user_id = ?1", 136 + [user_id], 137 + |row| row.get::<usize, i64>(0), 138 + ) { 139 + Ok(res) => Ok(res), 140 + Err(rusqlite::Error::QueryReturnedNoRows) => Ok(0), 141 + // It returns NULL, if there are now no rows 142 + Err(rusqlite::Error::InvalidColumnType(_, _, _)) => Ok(0), 143 + Err(err) => Err(<rusqlite::Error as Into<anyhow::Error>>::into(err)), 144 + } 145 + } 146 + /// Return list of rows for the given `user_id` since `last_row_counter` 147 + pub fn get_delta(conn: &Connection, user_id: &str, last_row_couter: i64) -> Result<Vec<Chats>> { 148 + let mut stmt = conn.prepare("select id, user_id, content, resp_id, role, context_id, created_at, updated_at , row_counter from chats where user_id = ?1 and row_counter > ?2 order by id")?; 149 + 150 + let chat_rows = stmt.query_map(params![user_id, last_row_couter], |row| { 151 + let id: String = row.get(0)?; 152 + let role: String = row.get(4)?; 153 + let created_at: f64 = row.get(6)?; 154 + let updated_at: f64 = row.get(7)?; 155 + let resp_id: Option<String> = row.get(3)?; 156 + let ctx_id = row.get(5)?; 157 + Ok(Chats { 158 + id, 159 + content: row.get(2)?, 160 + response_id: resp_id, 161 + role: Role::from_str(&role).map_err(FromSqlError::other)?, 162 + user_id: row.get(1)?, 163 + context_id: ctx_id, 164 + created_at: created_at as u64, 165 + updated_at: updated_at as u64, 166 + row_counter: row.get(8)?, 167 + }) 168 + })?; 169 + 170 + let mut chats: Vec<Chats> = vec![]; 171 + 172 + for chat in chat_rows { 173 + chats.push(chat?); 174 + } 175 + 176 + Ok(chats) 177 + } 178 + 179 + pub fn apply_delta(chat_conn: &mut Connection, delta_chats: &Vec<Chats>) -> Result<()> { 180 + // TODO: Handle primary key conflict, for now reject it (in a way its impossible to have this scenario, and if its occuring then that means 181 + // some issue in syncing, so ignore it, by rejecting it), later 182 + // do LWW based on issuer of UCAN 183 + let txn = chat_conn.transaction()?; 184 + { 185 + let mut stmt = txn.prepare("insert into chats(id, user_id, content, resp_id, role, context_id, created_at, updated_at, row_counter) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)")?; 186 + 187 + for chat in delta_chats { 188 + match stmt.execute(params![ 189 + &chat.id.to_string(), 190 + &chat.user_id, 191 + &chat.content, 192 + &chat.response_id, 193 + Into::<String>::into(chat.role), 194 + &chat.context_id, 195 + &chat.created_at.to_string(), 196 + &chat.updated_at.to_string(), 197 + &chat.row_counter, 198 + ]) { 199 + Err(rusqlite::Error::SqliteFailure(_, Some(reason))) 200 + if reason == "UNIQUE constraint failed: chats.id" => 201 + { 202 + log::error!( 203 + "err in writing row {:?}, already exists, skipping", 204 + &chat.id 205 + ); 206 + } 207 + Err(err) => log::error!("err in writing row due to {:?}", err), 208 + Ok(_) => (), 209 + } 210 + } 211 + } 212 + txn.commit()?; 213 + 214 + Ok(()) 215 + } 216 + 217 + pub fn get_encoded_delta( 218 + conn: &Connection, 219 + user_id: &str, 220 + last_row_couter: i64, 221 + ) -> Result<Vec<u8>> { 222 + let delta = get_delta(conn, user_id, last_row_couter)?; 223 + Ok(encode_delta_to_bytes(&delta)) 224 + } 225 + 226 + pub fn create_sync_channel() -> Sender<SyncOp> { 227 + let (tx, mut rx) = mpsc::channel::<SyncOp>(32); 228 + 229 + tokio::spawn(async move { 230 + let mut chat_db_conn = get_db_conn(super::storage::db::DBTYPE::CHAT)?; 231 + info!("DB sync channel ready.."); 232 + while let Some(msg) = rx.recv().await { 233 + match msg { 234 + SyncOp::GetLastRowCounter { user_id, resp } => { 235 + let counter = get_last_row_counter(&chat_db_conn, &user_id); 236 + resp.send(counter) 237 + .map_err(|_op| anyhow!("Error sending counter"))?; 238 + } 239 + SyncOp::GetEncodedData { 240 + user_id, 241 + last_row_counter, 242 + resp, 243 + } => { 244 + let encoded_res = get_encoded_delta(&chat_db_conn, &user_id, last_row_counter); 245 + resp.send(encoded_res) 246 + .map_err(|_op| anyhow!("Error sending encoded_delta"))?; 247 + } 248 + SyncOp::ApplyDelta { delta, resp } => { 249 + let chat_rows = decode_delta_from_bytes(&delta)?; 250 + let apply_res = apply_delta(&mut chat_db_conn, &chat_rows); 251 + resp.send(apply_res) 252 + .map_err(|_| anyhow!("Error sending apply delta response"))?; 253 + } 254 + } 255 + } 256 + Ok::<(), anyhow::Error>(()) 257 + }); 258 + tx 259 + } 260 + 261 + fn encode_delta_to_bytes(delta_chats: &Vec<Chats>) -> Vec<u8> { 262 + postcard::to_stdvec(delta_chats).expect("Failed to convert to bytes with postcard") 263 + } 264 + 265 + fn decode_delta_from_bytes(bytes: &[u8]) -> Result<Vec<Chats>> { 266 + postcard::from_bytes(bytes).map_err(Into::into) 267 + } 268 + 76 269 #[cfg(test)] 77 270 mod tests { 271 + 78 272 use std::time::{SystemTime, UNIX_EPOCH}; 79 273 80 274 use rusqlite::Connection; ··· 84 278 use crate::{ 85 279 core::{ 86 280 accounts::{ACCOUNT, User}, 87 - chats::save_chat, 281 + chats::{ 282 + apply_delta, decode_delta_from_bytes, encode_delta_to_bytes, get_delta, 283 + get_last_row_counter, save_chat, 284 + }, 88 285 }, 89 286 runtime::mlx::ChatResponse, 287 + utils::test_logger, 90 288 }; 91 289 92 290 #[test] ··· 105 303 assert_eq!(saved.resp_id, None); 106 304 assert_eq!(saved.role, Into::<String>::into(Role::User)); 107 305 assert_eq!(saved.user_id, user.user_id); 108 - assert_eq!(saved.context_id, Uuid::nil().to_string()); 306 + assert_eq!(saved.context_id, None); 109 307 } 110 308 111 309 #[test] 112 310 fn test_valid_response_save_chat() { 113 311 let conn = setup_db_schema(); 114 312 let user = create_user(); 115 - let parent_chat_id = Uuid::now_v7(); 313 + let parent_chat_id = Uuid::now_v7().to_string(); 116 314 let chat_resp = ChatResponse { 117 315 reply: "reply".to_owned(), 118 316 code: "code".to_owned(), 119 317 prev_response_id: String::from("resp_prev"), 120 - parent_chat_id: Some(parent_chat_id), 318 + parent_chat_id: Some(parent_chat_id.clone()), 121 319 metrics: None, 122 320 }; 123 321 let input = "2+2"; ··· 125 323 126 324 assert_eq!(chat.user_id, user.user_id); 127 325 assert_eq!(chat.response_id.as_deref(), Some("resp_prev")); 128 - assert_eq!(chat.context_id, Some(parent_chat_id)); 326 + assert_eq!(chat.context_id, Some(parent_chat_id.clone())); 129 327 130 328 let saved = fetch_saved_chat_row(&conn, &chat.id); 131 329 assert_eq!(saved.content, input); 132 330 assert_eq!(saved.resp_id, Some(String::from("resp_prev"))); 133 331 assert_eq!(saved.role, Into::<String>::into(Role::Assistant)); 134 332 assert_eq!(saved.user_id, user.user_id); 135 - assert_eq!(saved.context_id, parent_chat_id.to_string()); 333 + assert_eq!(saved.context_id, Some(parent_chat_id.clone())); 136 334 } 137 335 138 336 #[test] ··· 143 341 reply: "reply".to_owned(), 144 342 code: "code".to_owned(), 145 343 prev_response_id: String::from("resp_prev"), 146 - parent_chat_id: None, 344 + parent_chat_id: Some(Uuid::now_v7().to_string()), 147 345 metrics: None, 148 346 }; 149 347 150 348 let chat = 151 349 save_chat(&conn, &user, "hello", Some(&chat_resp)).expect("chat should be saved"); 152 350 153 - assert_eq!(chat.context_id, None); 351 + assert!(chat.context_id.is_some()); 154 352 let saved = fetch_saved_chat_row(&conn, &chat.id); 155 353 assert_eq!(saved.role, Into::<String>::into(Role::Assistant)); 156 - assert_eq!(saved.context_id, Uuid::nil().to_string()); 354 + assert!(saved.context_id.is_some()); 157 355 } 158 356 159 357 #[test] ··· 178 376 assert!(result.is_err()); 179 377 } 180 378 379 + #[test] 380 + fn test_last_row_counter() { 381 + let conn = setup_db_schema(); 382 + let user = create_user(); 383 + let input = "2+2"; 384 + let chat = save_chat(&conn, &user, input, None).expect("chat should be saved"); 385 + 386 + assert_eq!(chat.user_id, user.user_id); 387 + assert!(chat.response_id.is_none()); 388 + assert!(chat.context_id.is_none()); 389 + 390 + let saved = get_last_row_counter(&conn, &user.user_id); 391 + assert_eq!(saved.unwrap(), 1); 392 + } 393 + 394 + #[test] 395 + fn test_get_last_row_counter_without_entry() { 396 + let conn = setup_db_schema(); 397 + let user = create_user(); 398 + let saved = get_last_row_counter(&conn, &user.user_id); 399 + assert_eq!(saved.unwrap(), 0) 400 + } 401 + 402 + #[test] 403 + fn test_get_delta_diff() { 404 + let conn = setup_db_schema(); 405 + let user = create_user(); 406 + let input = "2+2"; 407 + let chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 408 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 409 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 410 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 411 + 412 + let rows = get_delta(&conn, &user.user_id, chat_1.row_counter).unwrap(); 413 + assert_eq!(rows.len(), 3); 414 + } 415 + 416 + #[test] 417 + fn test_get_delta_diff_empty_last_entry_id() { 418 + let conn = setup_db_schema(); 419 + let user = create_user(); 420 + let input = "2+2"; 421 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 422 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 423 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 424 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 425 + 426 + let rows = get_delta(&conn, &user.user_id, 0).unwrap(); 427 + assert_eq!(rows.len(), 4); 428 + } 429 + 430 + #[test] 431 + fn test_get_delta_diff_empty_wrong_user_id() { 432 + let conn = setup_db_schema(); 433 + let user = create_user(); 434 + let input = "2+2"; 435 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 436 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 437 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 438 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 439 + 440 + let rows = get_delta(&conn, "", 0).unwrap(); 441 + assert_eq!(rows.len(), 0); 442 + } 443 + 444 + #[test] 445 + fn test_apply_delta() { 446 + let conn = setup_db_schema(); 447 + let mut conn_2 = setup_db_schema(); 448 + let user = create_user(); 449 + let input = "2+2"; 450 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 451 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 452 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 453 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 454 + 455 + let rows = get_delta(&conn, &user.user_id, 0).unwrap(); 456 + assert_eq!(rows.len(), 4); 457 + assert!(apply_delta(&mut conn_2, &rows).is_ok()); 458 + let rows = get_delta(&conn_2, &user.user_id, 0).unwrap(); 459 + assert_eq!(rows.len(), 4); 460 + } 461 + 462 + #[test] 463 + fn test_e2e_delta_roundtrip() { 464 + let conn = setup_db_schema(); 465 + let mut conn_2 = setup_db_schema(); 466 + let user = create_user(); 467 + let input = "2+2"; 468 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 469 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 470 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 471 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 472 + 473 + let rows = get_delta(&conn, &user.user_id, 0).unwrap(); 474 + assert_eq!(rows.len(), 4); 475 + let chat_bytes = encode_delta_to_bytes(&rows); 476 + let decoded_chat = decode_delta_from_bytes(&chat_bytes).unwrap(); 477 + assert!(apply_delta(&mut conn_2, &decoded_chat).is_ok()); 478 + let rows = get_delta(&conn_2, &user.user_id, 0).unwrap(); 479 + assert_eq!(rows.len(), 4); 480 + } 481 + 482 + #[test] 483 + fn test_e2e_delta_roundtrip_w_empty_bytes() { 484 + let conn = setup_db_schema(); 485 + let mut conn_2 = setup_db_schema(); 486 + let user = create_user(); 487 + let input = "2+2"; 488 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 489 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 490 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 491 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 492 + 493 + let rows = get_delta(&conn, &user.user_id, 4).unwrap(); 494 + assert_eq!(rows.len(), 0); 495 + let chat_bytes = encode_delta_to_bytes(&rows); 496 + let decoded_chat = decode_delta_from_bytes(&chat_bytes).unwrap(); 497 + assert!(apply_delta(&mut conn_2, &decoded_chat).is_ok()); 498 + let rows = get_delta(&conn_2, &user.user_id, 0).unwrap(); 499 + assert_eq!(rows.len(), 0); 500 + } 501 + 502 + #[test] 503 + fn test_non_zero_last_counter_delta() { 504 + let conn = setup_db_schema(); 505 + let mut _conn_2 = setup_db_schema(); 506 + let user = create_user(); 507 + let input = "2+2"; 508 + let chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 509 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 510 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 511 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 512 + let rows = get_delta(&conn, &user.user_id, chat_1.row_counter).unwrap(); 513 + assert_eq!(rows.len(), 3); 514 + } 515 + 516 + #[test] 517 + fn test_duplicate_row_apply() { 518 + test_logger(); 519 + let conn = setup_db_schema(); 520 + let mut conn_2 = setup_db_schema(); 521 + let user = create_user(); 522 + let input = "2+2"; 523 + let _chat_1 = save_chat(&conn, &user, input, None).expect("chat should be saved"); 524 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 525 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 526 + let _ = save_chat(&conn, &user, input, None).expect("chat should be saved"); 527 + 528 + let rows = get_delta(&conn, &user.user_id, 0).unwrap(); 529 + assert_eq!(rows.len(), 4); 530 + let chat_bytes = encode_delta_to_bytes(&rows); 531 + let decoded_chat = decode_delta_from_bytes(&chat_bytes).unwrap(); 532 + assert!(apply_delta(&mut conn_2, &decoded_chat).is_ok()); 533 + let rows = get_delta(&conn_2, &user.user_id, 0).unwrap(); 534 + assert_eq!(rows.len(), 4); 535 + assert!(apply_delta(&mut conn_2, &decoded_chat).is_ok()); 536 + let rows = get_delta(&conn_2, &user.user_id, 0).unwrap(); 537 + assert_eq!(rows.len(), 4); 538 + } 539 + 540 + #[test] 541 + fn test_e2e_syncing_both_ways_w_eventual_consistency() { 542 + test_logger(); 543 + let mut conn = setup_db_schema(); 544 + let mut conn_2 = setup_db_schema(); 545 + let user_a = create_user_by_id("user_a"); 546 + let user_b = create_user_by_id("user_b"); 547 + 548 + // Node user A adds stuff 549 + let input = "2+2"; 550 + let _chat_1 = save_chat(&conn, &user_a, input, None).expect("chat should be saved"); 551 + let _ = save_chat(&conn, &user_a, input, None).expect("chat should be saved"); 552 + let _ = save_chat(&conn, &user_a, input, None).expect("chat should be saved"); 553 + let _ = save_chat(&conn, &user_a, input, None).expect("chat should be saved"); 554 + 555 + // Node user B adds stuff 556 + let input = "4+4"; 557 + let _chat_1 = save_chat(&conn_2, &user_b, input, None).expect("chat should be saved"); 558 + let _ = save_chat(&conn_2, &user_b, input, None).expect("chat should be saved"); 559 + let _ = save_chat(&conn_2, &user_b, input, None).expect("chat should be saved"); 560 + let _ = save_chat(&conn_2, &user_b, input, None).expect("chat should be saved"); 561 + 562 + // Node A wants to sync with Node B 563 + 564 + // 1. So its sends last_row_counter of Node B to Node B and hopefully 565 + // it sends the diff since then and last_row_counter of Node A back.. 566 + 567 + let user_b_last_entry_of_user_a = get_last_row_counter(&conn, &user_b.user_id).unwrap(); 568 + 569 + // user_b is extracting the row's of it since the given last_row_counter 570 + let user_bs_diff_rows = 571 + get_delta(&conn_2, &user_b.user_id, user_b_last_entry_of_user_a).unwrap(); 572 + 573 + assert_eq!(user_bs_diff_rows.len(), 4); 574 + 575 + // user_bs diff is encoded 576 + let user_b_chat_bytes = encode_delta_to_bytes(&user_bs_diff_rows); 577 + 578 + // send to user_a and its decoded 579 + let user_b_decoded_chat = decode_delta_from_bytes(&user_b_chat_bytes).unwrap(); 580 + 581 + // Now user_a is gonna apply the user_b diff 582 + assert!(apply_delta(&mut conn, &user_b_decoded_chat).is_ok()); 583 + 584 + // Just checking if we user_a has all 8 rows 585 + 586 + let user_a_rows = conn 587 + .query_row("select count(*) from chats", [], |row| { 588 + row.get::<usize, i64>(0) 589 + }) 590 + .unwrap(); 591 + 592 + assert_eq!(user_a_rows, 8); 593 + 594 + // cool, now lets do the reverse sync, user B syncs user A stuff 595 + 596 + let user_a_last_entry_of_user_b = get_last_row_counter(&conn_2, &user_a.user_id).unwrap(); 597 + 598 + // user_a is extracting the row's of it since the given last_row_counter 599 + let user_as_diff_rows = 600 + get_delta(&conn, &user_a.user_id, user_a_last_entry_of_user_b).unwrap(); 601 + 602 + assert_eq!(user_as_diff_rows.len(), 4); 603 + 604 + // user_as diff is encoded 605 + let user_a_chat_bytes = encode_delta_to_bytes(&user_as_diff_rows); 606 + 607 + // send to user_b and its decoded 608 + let user_a_decoded_chat = decode_delta_from_bytes(&user_a_chat_bytes).unwrap(); 609 + 610 + // Now user_b is gonna apply the user_b diff 611 + assert!(apply_delta(&mut conn_2, &user_a_decoded_chat).is_ok()); 612 + 613 + // Just checking eventual consistency 614 + 615 + let user_a_rows = conn 616 + .query_row("select count(*) from chats", [], |row| { 617 + row.get::<usize, i64>(0) 618 + }) 619 + .unwrap(); 620 + 621 + let user_b_rows = conn_2 622 + .query_row("select count(*) from chats", [], |row| { 623 + row.get::<usize, i64>(0) 624 + }) 625 + .unwrap(); 626 + 627 + assert_eq!(user_a_rows, user_b_rows); 628 + } 629 + 181 630 struct SavedChatRow { 182 631 content: String, 183 632 resp_id: Option<String>, 184 633 role: String, 185 634 user_id: String, 186 - context_id: String, 635 + context_id: Option<String>, 187 636 } 188 637 189 - fn fetch_saved_chat_row(conn: &Connection, chat_id: &Uuid) -> SavedChatRow { 638 + fn fetch_saved_chat_row(conn: &Connection, chat_id: &str) -> SavedChatRow { 190 639 conn.query_row( 191 640 "SELECT content, resp_id, role, user_id, context_id FROM chats WHERE id = ?1", 192 641 [chat_id.to_string()], ··· 205 654 206 655 fn create_user() -> User { 207 656 User { 208 - id: Uuid::now_v7(), 657 + id: Uuid::now_v7().to_string(), 209 658 user_id: String::from("did"), 210 659 username: String::from("nickname"), 211 660 account_type: ACCOUNT::LOCAL, ··· 221 670 .as_secs(), 222 671 } 223 672 } 673 + fn create_user_by_id(user_id: &str) -> User { 674 + User { 675 + id: Uuid::now_v7().to_string(), 676 + user_id: String::from(user_id), 677 + username: String::from("nickname"), 678 + account_type: ACCOUNT::LOCAL, 679 + active_profile: true, 680 + root: true, 681 + created_at: SystemTime::now() 682 + .duration_since(UNIX_EPOCH) 683 + .expect("time went backwards") 684 + .as_secs(), 685 + updated_at: SystemTime::now() 686 + .duration_since(UNIX_EPOCH) 687 + .expect("time went backwards") 688 + .as_secs(), 689 + } 690 + } 224 691 fn setup_db_schema() -> Connection { 225 692 let conn = Connection::open_in_memory().unwrap(); 226 693 conn.execute( ··· 232 699 user_id TEXT NOT NULL, 233 700 context_id TEXT , 234 701 created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), 235 - updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) 702 + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), 703 + row_counter INTEGER, 704 + session_id TEXT 236 705 );", 237 706 [], 238 707 )
+373 -49
tiles/src/core/network/mod.rs
··· 9 9 }; 10 10 11 11 use anyhow::Result; 12 + use axum::body::Bytes; 12 13 use futures_util::{StreamExt, TryStreamExt}; 13 14 use iroh::{ 14 15 Endpoint, EndpointId, NET_REPORT_TIMEOUT, PublicKey, ··· 17 18 endpoint_info::UserData, 18 19 protocol::Router, 19 20 }; 21 + use iroh_blobs::{BlobsProtocol, store::mem::MemStore, ticket::BlobTicket}; 20 22 use iroh_gossip::{ 21 23 Gossip, TopicId, 22 24 api::{Event, GossipReceiver, GossipSender}, 23 25 }; 24 - use iroh_ping::Ping; 25 - use iroh_tickets::endpoint::EndpointTicket; 26 + 27 + use log::info; 26 28 use rusqlite::Connection; 27 - use tilekit::accounts::{get_did_from_public_key, get_random_bytes, get_random_bytes_32}; 28 - use tokio::task::spawn_blocking; 29 + use tilekit::accounts::{ 30 + get_did_from_public_key, get_public_key_from_did, get_random_bytes, get_random_bytes_32, 31 + }; 32 + use tokio::{ 33 + sync::{mpsc::Sender, oneshot}, 34 + task::spawn_blocking, 35 + }; 29 36 use uuid::Uuid; 30 37 31 38 use crate::core::{ 32 39 accounts::{ 33 - self, get_app_secret_key, get_current_user, get_user_by_user_id, save_peer_account_db, 40 + self, create_dummy_user, get_app_secret_key, get_current_user, get_user_info, 41 + save_peer_account_db, 34 42 }, 43 + chats::{SyncOp, create_sync_channel}, 35 44 network::ticket::{EndpointUserData, LinkTicket}, 36 45 storage::db::{DBTYPE, get_db_conn}, 37 46 }; 47 + use owo_colors::OwoColorize; 38 48 use sha2::{Digest, Sha256}; 49 + 50 + // 50 mb 51 + const MAX_DOWNLOADED_BYTES: usize = 50 * 1024 * 1024; 39 52 40 53 const DEVICE_LINK_LOCAL_TOPIC: &str = "com.tilesprivacy.tiles.link"; 41 54 #[derive(serde::Serialize, serde::Deserialize)] ··· 66 79 } 67 80 } 68 81 69 - #[derive(serde::Serialize, serde::Deserialize)] 82 + #[derive(serde::Serialize, serde::Deserialize, Debug)] 70 83 #[allow(clippy::enum_variant_names)] 71 84 enum MessageBody { 72 - LinkRequest { ticket: String }, 85 + LinkRequest { 86 + ticket: String, 87 + }, 73 88 LinkAccepted, 74 - LinkRejected { reason: String }, 75 - } 76 - 77 - // Entrypoint of network connection 78 - pub async fn init(ticket: Option<&str>) -> Result<()> { 79 - if let Some(ticket_addr) = ticket { 80 - let sender_endpoint = Endpoint::bind(presets::N0).await?; 81 - println!("{:?}", sender_endpoint.addr()); 82 - let se_clone = sender_endpoint.clone(); 83 - let send_pinger = Ping::new(); 84 - let rtt = send_pinger 85 - .ping( 86 - &sender_endpoint, 87 - EndpointTicket::from_str(ticket_addr)? 88 - .endpoint_addr() 89 - .clone(), 90 - ) 91 - .await?; 92 - 93 - println!("ping took: {:?} to complete", rtt); 94 - se_clone.close().await; 95 - } else { 96 - let endpoint = Endpoint::bind(presets::N0).await?; 97 - let ep = endpoint.clone(); 98 - let ep2 = endpoint.clone(); 99 - endpoint.online().await; 100 - 101 - let ping = Ping::new(); 102 - 103 - let ticket = EndpointTicket::new(endpoint.addr()); 104 - 105 - println!("ticket\n{:?}", ticket.to_string()); 106 - 107 - let recv_router = Router::builder(ep).accept(iroh_ping::ALPN, ping).spawn(); 108 - ep2.close().await; 109 - recv_router.shutdown().await?; 110 - } 111 - Ok(()) 89 + LinkRejected { 90 + reason: String, 91 + }, 92 + SyncStart { 93 + last_row_counter: Option<i64>, 94 + }, 95 + SyncSendDeltaInfo { 96 + blob_ticket: String, 97 + last_row_counter: Option<i64>, 98 + }, 99 + SyncEnd, 112 100 } 113 101 114 102 pub async fn link(ticket: Option<String>) -> Result<()> { ··· 117 105 let endpoint = create_endpoint(&user).await?; 118 106 let is_online = is_online(&endpoint).await; 119 107 let mut bootstrap_ids: Vec<EndpointId> = vec![]; 120 - // if ticket's there, then this is link enable sender's command, e;se receiver end 108 + // if ticket's there, then this is link enable sender's command, else receiver end 121 109 if let Some(ticket) = ticket { 122 110 let (endpoint_id, mut did, mut nickname, topic_value) = parse_link_ticket(&ticket)?; 123 111 ··· 139 127 did = endpoint_user_data.did; 140 128 nickname = endpoint_user_data.nickname; 141 129 }; 142 - if get_user_by_user_id(&user_db_conn, did.to_owned()).is_ok() { 130 + if get_user_info(&user_db_conn, &did).is_ok() { 143 131 println!("Device {}({}) already linked", nickname, did); 144 132 return Ok(()); 145 133 } ··· 244 232 if cfg!(debug_assertions) { 245 233 println!("In {}:, some event {:?}", user.username, event); 246 234 } 235 + // TODO: Damn refactor the loop, its getting bigger 247 236 if let Event::Received(msg) = event { 248 237 let pub_key = msg.delivered_from; 249 238 let msg = NetworkMessage::from_bytes(&msg.content)?; ··· 335 324 msg.from_nickname, msg.from_did, reason 336 325 ); 337 326 } 327 + msg_body => { 328 + eprintln!("Invalid link message {:?}", msg_body) 329 + } 338 330 } 339 331 } 340 332 } 341 333 Ok(()) 342 334 } 343 335 336 + async fn sync_subscribe_loop( 337 + mut receiver: GossipReceiver, 338 + sender: GossipSender, 339 + user: accounts::User, 340 + store: MemStore, 341 + endpoint: Endpoint, 342 + sync_channel_sender: Sender<SyncOp>, 343 + ) -> Result<()> { 344 + while let Some(event) = receiver.try_next().await? { 345 + info!( 346 + "SYNC_LOOP: Received by {}:, event {:?}", 347 + user.username, event 348 + ); 349 + if let Event::Received(msg) = event { 350 + let pub_key = msg.delivered_from; 351 + let msg = NetworkMessage::from_bytes(&msg.content)?; 352 + if !is_did_valid(&msg.from_did, pub_key)? { 353 + eprintln!( 354 + "Incoming peer DID {} invalid, blocking request", 355 + msg.from_did 356 + ); 357 + continue; 358 + } 359 + match msg.body { 360 + MessageBody::SyncStart { 361 + last_row_counter: _, 362 + } => { 363 + info!("Received sync start event..."); 364 + on_sync_start_event( 365 + &sender, 366 + &store, 367 + &msg, 368 + pub_key, 369 + &user, 370 + &sync_channel_sender, 371 + ) 372 + .await?; 373 + } 374 + MessageBody::SyncSendDeltaInfo { 375 + blob_ticket: _, 376 + last_row_counter: _, 377 + } => { 378 + on_sync_send_delta_info( 379 + &sender, 380 + &store, 381 + &msg, 382 + pub_key, 383 + &user, 384 + &endpoint, 385 + &sync_channel_sender, 386 + ) 387 + .await?; 388 + } 389 + MessageBody::SyncEnd => { 390 + println!("Sync completed..., you can exit now"); 391 + } 392 + msg_body => { 393 + info!("Invalid sync message {:?}", msg_body) 394 + } 395 + } 396 + } 397 + } 398 + Ok(()) 399 + } 344 400 async fn create_endpoint(user: &accounts::User) -> Result<Endpoint> { 345 401 // In release mode, we will build the endpoint using 346 402 // tiles keypair in keychain ··· 362 418 } 363 419 } 364 420 421 + pub async fn sync(did: Option<String>) -> Result<()> { 422 + let user_db_conn = get_db_conn(DBTYPE::COMMON)?; 423 + let user = get_current_user(&user_db_conn)?; 424 + let endpoint = create_endpoint(&user).await?; 425 + let is_online = is_online(&endpoint).await; 426 + if !is_online { 427 + let mdns = address_lookup::mdns::MdnsAddressLookup::builder().build(endpoint.id())?; 428 + endpoint.address_lookup()?.add(mdns.clone()); 429 + } 430 + 431 + let tx = create_sync_channel(); 432 + if let Some(receiver_did) = did { 433 + // INITIATOR BLOCK 434 + // The sync gossip topic is basically derived from the receiver's 435 + // DID, so that initiator's can directly connect w/o any 436 + // initial handshake 437 + let receiver_pub_key = get_public_key_from_did(&receiver_did)?; 438 + let receiver_user = if let Ok(receiver_user) = get_user_info(&user_db_conn, &receiver_did) { 439 + receiver_user 440 + } else { 441 + if cfg!(debug_assertions) == false { 442 + eprintln!("The DID {} is not a linked peer", receiver_did); 443 + return Ok(()); 444 + } 445 + info!("creating a dummy user"); 446 + create_dummy_user() 447 + }; 448 + 449 + let receiver_endpoint_id = PublicKey::from_bytes(&receiver_pub_key)?; 450 + info!("receiver endpoint id {:?}", receiver_endpoint_id); 451 + let sync_topic = format!("sync:{}", receiver_did); 452 + let sync_topic_id = create_topic_id(&sync_topic); 453 + 454 + let (sender, mut receiver, recv_router, store) = 455 + create_sync_network(&endpoint, sync_topic_id, vec![receiver_endpoint_id]).await?; 456 + println!("\nConnecting to {}.....", receiver_did); 457 + receiver.joined().await?; 458 + tokio::spawn(sync_subscribe_loop( 459 + receiver, 460 + sender.clone(), 461 + user.clone(), 462 + store, 463 + endpoint.clone(), 464 + tx.clone(), 465 + )); 466 + 467 + let receiver_last_row_counter = fetch_last_row_counter(&receiver_did, &tx).await?; 468 + let sync_start_msg = NetworkMessage::new( 469 + &user, 470 + is_online, 471 + MessageBody::SyncStart { 472 + last_row_counter: Some(receiver_last_row_counter), 473 + }, 474 + ); 475 + sender.broadcast(sync_start_msg.to_bytes().into()).await?; 476 + info!("Sent SyncStart event"); 477 + 478 + println!( 479 + "\nSyncing in progress with ....{}({})", 480 + receiver_user.username, receiver_did 481 + ); 482 + tokio::signal::ctrl_c().await?; 483 + recv_router.shutdown().await?; 484 + } else { 485 + // RECEIVER BLOCK 486 + // The sync gossip topic is basically derived from the receiver's 487 + // public-key, so that initiator's can directly connect w/o any 488 + // initial handshake 489 + 490 + let did = if cfg!(debug_assertions) { 491 + let pub_key = endpoint.id(); 492 + &get_did_from_public_key(pub_key.as_bytes())? 493 + } else { 494 + &user.user_id 495 + }; 496 + 497 + let sync_topic = format!("sync:{}", did); 498 + let sync_topic_id = create_topic_id(&sync_topic); 499 + let (sender, receiver, recv_router, store) = 500 + create_sync_network(&endpoint, sync_topic_id, vec![]).await?; 501 + info!("sync gossip network created"); 502 + tokio::spawn(sync_subscribe_loop( 503 + receiver, 504 + sender.clone(), 505 + user.clone(), 506 + store, 507 + endpoint.clone(), 508 + tx.clone(), 509 + )); 510 + println!("{}", "Ready to accept sync requests from peers...".blue()); 511 + 512 + // Since in dev, we create endpoints randomly, at the initiator side 513 + // we can use the DID derived from this, instead of actual ones 514 + // for the network to form correctly 515 + if cfg!(debug_assertions) { 516 + println!("Use this DID {} in dev for testing", did); 517 + } 518 + tokio::signal::ctrl_c().await?; 519 + recv_router.shutdown().await?; 520 + } 521 + endpoint.close().await; 522 + Ok(()) 523 + } 524 + 525 + // Router with gossip and blob protocol 526 + async fn create_sync_network( 527 + endpoint: &Endpoint, 528 + topic_id: TopicId, 529 + bootstrap_ids: Vec<iroh::PublicKey>, 530 + ) -> Result<(GossipSender, GossipReceiver, Router, MemStore)> { 531 + let gossip = Gossip::builder().spawn(endpoint.clone()); 532 + let store = MemStore::new(); 533 + let blobs = BlobsProtocol::new(&store, None); 534 + let recv_router = Router::builder(endpoint.clone()) 535 + .accept(iroh_gossip::ALPN, gossip.clone()) 536 + .accept(iroh_blobs::ALPN, blobs.clone()) 537 + .spawn(); 538 + 539 + let (goss_sender, goss_receiver) = gossip.subscribe(topic_id, bootstrap_ids).await?.split(); 540 + 541 + Ok((goss_sender, goss_receiver, recv_router, store)) 542 + } 543 + 365 544 fn create_topic_id(topic_name: &str) -> TopicId { 366 545 let mut hasher = Sha256::new(); 367 546 hasher.update(topic_name.as_bytes()); ··· 463 642 Ok(get_did_from_public_key(&pub_key)? == did) 464 643 } 465 644 } 466 - // fn subsribe_mdns_events(mdns_events) {} 467 - //TODO: Add tests, can we get some from iroh reference? 645 + 646 + async fn fetch_last_row_counter(user_id: &str, sender: &Sender<SyncOp>) -> Result<i64> { 647 + let (sendx, recvx) = oneshot::channel(); 648 + let sync_op_msg = SyncOp::GetLastRowCounter { 649 + user_id: user_id.to_owned(), 650 + resp: sendx, 651 + }; 652 + 653 + sender.send(sync_op_msg).await?; 654 + recvx.await? 655 + } 656 + 657 + async fn fetch_encoded_delta_ticket( 658 + user_id: &str, 659 + sender: &Sender<SyncOp>, 660 + lrc: i64, 661 + store: &MemStore, 662 + delivered_from: PublicKey, 663 + ) -> Result<BlobTicket> { 664 + let (sendx, recvx) = oneshot::channel(); 665 + 666 + let sync_op_msg = SyncOp::GetEncodedData { 667 + user_id: user_id.to_owned(), 668 + last_row_counter: lrc, 669 + resp: sendx, 670 + }; 671 + 672 + sender.send(sync_op_msg).await?; 673 + let encoded_data_result = recvx.await??; 674 + 675 + let tag = store 676 + .blobs() 677 + .add_bytes(Into::<Bytes>::into(encoded_data_result)) 678 + .await?; 679 + 680 + Ok(BlobTicket::new(delivered_from.into(), tag.hash, tag.format)) 681 + } 682 + async fn on_sync_start_event( 683 + sender: &GossipSender, 684 + store: &MemStore, 685 + msg: &NetworkMessage, 686 + delivered_from: PublicKey, 687 + user: &accounts::User, 688 + sync_channel_sender: &Sender<SyncOp>, 689 + ) -> Result<()> { 690 + if let MessageBody::SyncStart { 691 + last_row_counter: lrc, 692 + } = &msg.body 693 + { 694 + let sender_did = get_did_from_public_key(delivered_from.as_bytes())?; 695 + let ticket = fetch_encoded_delta_ticket( 696 + &user.user_id, 697 + sync_channel_sender, 698 + lrc.expect("lrc failed"), 699 + store, 700 + delivered_from, 701 + ) 702 + .await?; 703 + 704 + let receiver_last_row_counter = 705 + fetch_last_row_counter(&sender_did, sync_channel_sender).await?; 706 + 707 + let delta_info = NetworkMessage::new( 708 + user, 709 + msg.is_online, 710 + MessageBody::SyncSendDeltaInfo { 711 + blob_ticket: ticket.to_string(), 712 + last_row_counter: Some(receiver_last_row_counter), 713 + }, 714 + ); 715 + sender.broadcast(delta_info.to_bytes().into()).await?; 716 + info!("Sent blob ticket {} to {}", ticket, sender_did); 717 + } 718 + Ok(()) 719 + } 720 + 721 + async fn on_sync_send_delta_info( 722 + sender: &GossipSender, 723 + store: &MemStore, 724 + msg: &NetworkMessage, 725 + delivered_from: PublicKey, 726 + user: &accounts::User, 727 + endpoint: &Endpoint, 728 + sync_channel_sender: &Sender<SyncOp>, 729 + ) -> Result<()> { 730 + if let MessageBody::SyncSendDeltaInfo { 731 + blob_ticket, 732 + last_row_counter, 733 + } = &msg.body 734 + { 735 + let ticket: BlobTicket = blob_ticket.parse()?; 736 + let downloader = store.downloader(endpoint); 737 + downloader 738 + .download(ticket.hash(), Some(delivered_from)) 739 + .await?; 740 + 741 + let data = store.blobs().get_bytes(ticket.hash()).await?; 742 + info!("Downloaded data diff"); 743 + 744 + if data.len() > MAX_DOWNLOADED_BYTES { 745 + log::error!( 746 + "Downloaded delta is greater than {}, skipping the sync", 747 + MAX_DOWNLOADED_BYTES 748 + ); 749 + return Ok(()); 750 + } 751 + 752 + let (sendx, recvx) = oneshot::channel(); 753 + let sync_op_msg = SyncOp::ApplyDelta { 754 + delta: data.to_vec(), 755 + resp: sendx, 756 + }; 757 + 758 + sync_channel_sender.send(sync_op_msg).await?; 759 + 760 + recvx.await??; 761 + info!("Diff applied successfully"); 762 + 763 + // last_row_counter None means its end of sync relay 764 + if let Some(row_counter) = last_row_counter { 765 + let ticket = fetch_encoded_delta_ticket( 766 + &user.user_id, 767 + sync_channel_sender, 768 + *row_counter, 769 + store, 770 + delivered_from, 771 + ) 772 + .await?; 773 + let delta_info = NetworkMessage::new( 774 + user, 775 + msg.is_online, 776 + MessageBody::SyncSendDeltaInfo { 777 + blob_ticket: ticket.to_string(), 778 + last_row_counter: None, 779 + }, 780 + ); 781 + sender.broadcast(delta_info.to_bytes().into()).await?; 782 + info!("Sent blob ticket {} to {}", ticket, delivered_from); 783 + } else { 784 + let stop_req = NetworkMessage::new(user, msg.is_online, MessageBody::SyncEnd); 785 + sender.broadcast(stop_req.to_bytes().into()).await?; 786 + info!("sync ended"); 787 + println!("\nSync completed..., you can exit now"); 788 + } 789 + } 790 + Ok(()) 791 + }
+18 -3
tiles/src/core/storage/db.rs
··· 37 37 const COMMON_MIGRATIONS: Migrations = Migrations::from_slice(COMMON_MIGRATION_ARRAY); 38 38 39 39 // TODO: add the schema doc 40 - const CHATS_MIGRATION_ARRAY: &[M] = &[M::up( 41 - "CREATE TABLE IF NOT EXISTS chats ( 40 + const CHATS_MIGRATION_ARRAY: &[M] = &[ 41 + M::up( 42 + "CREATE TABLE IF NOT EXISTS chats ( 42 43 id TEXT PRIMARY KEY, 43 44 content TEXT NOT NULL, 44 45 resp_id TEXT, ··· 48 49 created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), 49 50 updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) 50 51 )", 51 - )]; 52 + ), 53 + // After creating row_counter, we backfill the row_counter for existing rows 54 + // which doesnt have any 55 + M::up( 56 + " 57 + ALTER TABLE CHATS ADD COLUMN row_counter INTEGER; 58 + UPDATE chats SET row_counter = ( 59 + SELECT rn FROM ( 60 + SELECT id, ROW_NUMBER() OVER ( PARTITION BY user_id ORDER BY id ) as rn FROM chats 61 + ) t WHERE t.id = chats.id ); 62 + 63 + ALTER TABLE CHATS ADD COLUMN session_id TEXT; 64 + ", 65 + ), 66 + ]; 52 67 53 68 const CHATS_MIGRATIONS: Migrations = Migrations::from_slice(CHATS_MIGRATION_ARRAY); 54 69
+23 -2
tiles/src/main.rs
··· 1 + // #![warn(clippy::pedantic)] 2 + 1 3 use std::error::Error; 2 4 3 5 use clap::{Args, Parser, Subcommand}; 4 6 use tiles::{ 5 - core::{self, network::link}, 7 + core::{ 8 + self, 9 + network::{link, sync}, 10 + }, 6 11 daemon::{start_cmd, start_server, stop_cmd}, 7 12 runtime::{RunArgs, build_runtime}, 8 13 utils::installer, ··· 66 71 67 72 /// Link with other devices p2p 68 73 Link(LinkArgs), 74 + 75 + /// Syncs the chats to peers 76 + Sync { 77 + /// The DID of the peer you want to sync 78 + did: Option<String>, 79 + }, 69 80 } 70 81 71 82 #[derive(Debug, Args)] ··· 174 185 } 175 186 #[tokio::main] 176 187 pub async fn main() -> Result<(), Box<dyn Error>> { 188 + env_logger::try_init()?; 177 189 let cli = Cli::parse(); 178 190 let runtime = build_runtime(); 179 191 match cli.command { ··· 232 244 data, 233 245 model, 234 246 }) => { 235 - let modelfile = commands::optimize(modelfile_path.clone(), data, model).await?; 247 + let modelfile = commands::optimize(&modelfile_path, data, &model).await?; 236 248 std::fs::write(&modelfile_path, modelfile.to_string())?; 237 249 println!("Successfully updated {}", modelfile_path); 238 250 } ··· 263 275 show_peers()?; 264 276 } 265 277 }, 278 + Some(Commands::Sync { did }) => sync(did).await?, 266 279 } 267 280 Ok(()) 268 281 } 282 + 283 + // fn build_logger() -> Result<(), SetLoggerError> { 284 + // let mut env_builder = env_logger::Builder::new(); 285 + // if !cfg!(debug_assertions) { 286 + // env_builder.filter_module("iroh", log::LevelFilter::Off); 287 + // } 288 + // env_builder.try_init() 289 + // }
+1 -2
tiles/src/runtime/mlx.rs
··· 25 25 use tilekit::modelfile::Modelfile; 26 26 use tilekit::modelfile::Role; 27 27 use tokio::time::sleep; 28 - use uuid::Uuid; 29 28 30 29 #[derive(Debug, Deserialize, Serialize, Clone)] 31 30 pub struct BenchmarkMetrics { ··· 56 55 pub reply: String, 57 56 pub code: String, 58 57 pub prev_response_id: String, 59 - pub parent_chat_id: Option<Uuid>, 58 + pub parent_chat_id: Option<String>, 60 59 pub metrics: Option<BenchmarkMetrics>, 61 60 } 62 61
+1 -2
tiles/src/utils/config.rs
··· 96 96 .get("data") 97 97 .expect("Failed to get data") 98 98 .as_table() 99 - .expect("Failed to parse to table (data)") 100 - .clone(); 99 + .expect("Failed to parse to table (data)"); 101 100 102 101 if let Some(path) = data_config 103 102 .get("path")
+4
tiles/src/utils/mod.rs
··· 10 10 .expect("time went backwards") 11 11 .as_secs() 12 12 } 13 + 14 + pub fn test_logger() { 15 + let _ = env_logger::builder().is_test(true).try_init(); 16 + }