SQLite-backed Key / Value Store
1
fork

Configure Feed

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

Merge pull request #7 from Tyrannican/memcache

Memcache

authored by

Graham Keenan and committed by
GitHub
2967275c b076d124

+781 -48
+15
CHANGELOG.md
··· 2 2 3 3 Documenting changes between versions beginning from v0.3.0 4 4 5 + ## v0.5.0 6 + 7 + Added the ability to operate Safir as a Memcache server (storing contents in-memory instead of on disk). 8 + Requires the [Rubin CLI](https://crates.io/crates/rubin-cli) to be installed. 9 + 10 + The Memcache service operates using TCP sockets to communicate with the Rubin server to access and update storage. 11 + It operates on `localhost` or `127.0.0.1` and on port `9876`. 12 + 13 + It works in the same fashion as Safir and does not update the store on disk when activated. 14 + 15 + Initialisation and cleanup is handles with the `start` and `stop` commands. 16 + 17 + * New memcache option available for using Safir as an in-memory store 18 + * Minor edits and bugfixes 19 + 5 20 ## v0.4.0 6 21 7 22 **BREAKING CHANGE**: A newer version of `Rubin` is now used which is incompatible with older versions of Safir.
+354 -7
Cargo.lock
··· 125 125 "heck", 126 126 "proc-macro2", 127 127 "quote", 128 - "syn", 128 + "syn 2.0.18", 129 129 ] 130 130 131 131 [[package]] ··· 152 152 ] 153 153 154 154 [[package]] 155 + name = "core-foundation-sys" 156 + version = "0.8.4" 157 + source = "registry+https://github.com/rust-lang/crates.io-index" 158 + checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" 159 + 160 + [[package]] 161 + name = "crossbeam-channel" 162 + version = "0.5.8" 163 + source = "registry+https://github.com/rust-lang/crates.io-index" 164 + checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" 165 + dependencies = [ 166 + "cfg-if", 167 + "crossbeam-utils", 168 + ] 169 + 170 + [[package]] 171 + name = "crossbeam-deque" 172 + version = "0.8.3" 173 + source = "registry+https://github.com/rust-lang/crates.io-index" 174 + checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 175 + dependencies = [ 176 + "cfg-if", 177 + "crossbeam-epoch", 178 + "crossbeam-utils", 179 + ] 180 + 181 + [[package]] 182 + name = "crossbeam-epoch" 183 + version = "0.9.15" 184 + source = "registry+https://github.com/rust-lang/crates.io-index" 185 + checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" 186 + dependencies = [ 187 + "autocfg", 188 + "cfg-if", 189 + "crossbeam-utils", 190 + "memoffset 0.9.0", 191 + "scopeguard", 192 + ] 193 + 194 + [[package]] 195 + name = "crossbeam-utils" 196 + version = "0.8.16" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" 199 + dependencies = [ 200 + "cfg-if", 201 + ] 202 + 203 + [[package]] 204 + name = "darwin-libproc" 205 + version = "0.1.2" 206 + source = "registry+https://github.com/rust-lang/crates.io-index" 207 + checksum = "9fb90051930c9a0f09e585762152048e23ac74d20c10590ef7cf01c0343c3046" 208 + dependencies = [ 209 + "darwin-libproc-sys", 210 + "libc", 211 + "memchr", 212 + ] 213 + 214 + [[package]] 215 + name = "darwin-libproc-sys" 216 + version = "0.1.2" 217 + source = "registry+https://github.com/rust-lang/crates.io-index" 218 + checksum = "57cebb5bde66eecdd30ddc4b9cd208238b15db4982ccc72db59d699ea10867c1" 219 + dependencies = [ 220 + "libc", 221 + ] 222 + 223 + [[package]] 224 + name = "derive_more" 225 + version = "0.99.17" 226 + source = "registry+https://github.com/rust-lang/crates.io-index" 227 + checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 228 + dependencies = [ 229 + "proc-macro2", 230 + "quote", 231 + "syn 1.0.109", 232 + ] 233 + 234 + [[package]] 155 235 name = "dirs" 156 236 version = "5.0.1" 157 237 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 171 251 "redox_users", 172 252 "windows-sys 0.48.0", 173 253 ] 254 + 255 + [[package]] 256 + name = "either" 257 + version = "1.8.1" 258 + source = "registry+https://github.com/rust-lang/crates.io-index" 259 + checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 174 260 175 261 [[package]] 176 262 name = "errno" ··· 205 291 ] 206 292 207 293 [[package]] 294 + name = "glob" 295 + version = "0.3.1" 296 + source = "registry+https://github.com/rust-lang/crates.io-index" 297 + checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 298 + 299 + [[package]] 208 300 name = "heck" 209 301 version = "0.4.1" 210 302 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 292 384 ] 293 385 294 386 [[package]] 387 + name = "log" 388 + version = "0.4.19" 389 + source = "registry+https://github.com/rust-lang/crates.io-index" 390 + checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" 391 + 392 + [[package]] 393 + name = "mach" 394 + version = "0.3.2" 395 + source = "registry+https://github.com/rust-lang/crates.io-index" 396 + checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 397 + dependencies = [ 398 + "libc", 399 + ] 400 + 401 + [[package]] 402 + name = "memchr" 403 + version = "2.5.0" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 406 + 407 + [[package]] 408 + name = "memoffset" 409 + version = "0.6.5" 410 + source = "registry+https://github.com/rust-lang/crates.io-index" 411 + checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 412 + dependencies = [ 413 + "autocfg", 414 + ] 415 + 416 + [[package]] 417 + name = "memoffset" 418 + version = "0.9.0" 419 + source = "registry+https://github.com/rust-lang/crates.io-index" 420 + checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" 421 + dependencies = [ 422 + "autocfg", 423 + ] 424 + 425 + [[package]] 295 426 name = "mio" 296 427 version = "0.8.8" 297 428 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 303 434 ] 304 435 305 436 [[package]] 437 + name = "nix" 438 + version = "0.23.2" 439 + source = "registry+https://github.com/rust-lang/crates.io-index" 440 + checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" 441 + dependencies = [ 442 + "bitflags", 443 + "cc", 444 + "cfg-if", 445 + "libc", 446 + "memoffset 0.6.5", 447 + ] 448 + 449 + [[package]] 450 + name = "ntapi" 451 + version = "0.4.1" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" 454 + dependencies = [ 455 + "winapi", 456 + ] 457 + 458 + [[package]] 459 + name = "nu-ansi-term" 460 + version = "0.46.0" 461 + source = "registry+https://github.com/rust-lang/crates.io-index" 462 + checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 463 + dependencies = [ 464 + "overload", 465 + "winapi", 466 + ] 467 + 468 + [[package]] 306 469 name = "num_cpus" 307 470 version = "1.15.0" 308 471 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 325 488 checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 326 489 327 490 [[package]] 491 + name = "overload" 492 + version = "0.1.1" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 495 + 496 + [[package]] 328 497 name = "parking_lot" 329 498 version = "0.12.1" 330 499 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 354 523 checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 355 524 356 525 [[package]] 526 + name = "platforms" 527 + version = "2.0.0" 528 + source = "registry+https://github.com/rust-lang/crates.io-index" 529 + checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" 530 + 531 + [[package]] 357 532 name = "proc-macro2" 358 533 version = "1.0.59" 359 534 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 363 538 ] 364 539 365 540 [[package]] 541 + name = "psutil" 542 + version = "3.2.2" 543 + source = "registry+https://github.com/rust-lang/crates.io-index" 544 + checksum = "f866af2b0f8e4b0d2d00aad8a9c5fc48fad33466cd99a64cbb3a4c1505f1a62d" 545 + dependencies = [ 546 + "cfg-if", 547 + "darwin-libproc", 548 + "derive_more", 549 + "glob", 550 + "mach", 551 + "nix", 552 + "num_cpus", 553 + "once_cell", 554 + "platforms", 555 + "thiserror", 556 + "unescape", 557 + ] 558 + 559 + [[package]] 366 560 name = "quote" 367 561 version = "1.0.28" 368 562 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 372 566 ] 373 567 374 568 [[package]] 569 + name = "rayon" 570 + version = "1.7.0" 571 + source = "registry+https://github.com/rust-lang/crates.io-index" 572 + checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 573 + dependencies = [ 574 + "either", 575 + "rayon-core", 576 + ] 577 + 578 + [[package]] 579 + name = "rayon-core" 580 + version = "1.11.0" 581 + source = "registry+https://github.com/rust-lang/crates.io-index" 582 + checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 583 + dependencies = [ 584 + "crossbeam-channel", 585 + "crossbeam-deque", 586 + "crossbeam-utils", 587 + "num_cpus", 588 + ] 589 + 590 + [[package]] 375 591 name = "redox_syscall" 376 592 version = "0.2.16" 377 593 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 393 609 394 610 [[package]] 395 611 name = "rubin" 396 - version = "0.3.0" 612 + version = "0.3.1" 397 613 source = "registry+https://github.com/rust-lang/crates.io-index" 398 - checksum = "8b8b3a06f6cc5e4ad2110061d4d38ba800d9393e3fc4df2d02460693d0f18b21" 614 + checksum = "42c54f8f7b8a32f0b361d6dab1933f931ea13e533f19cb33c09a6d1e7141d55b" 399 615 dependencies = [ 400 616 "serde", 401 617 "serde_json", 402 618 "tokio", 619 + "tracing", 620 + "tracing-subscriber", 403 621 ] 404 622 405 623 [[package]] ··· 424 642 425 643 [[package]] 426 644 name = "safir" 427 - version = "0.4.0" 645 + version = "0.5.0" 428 646 dependencies = [ 429 647 "clap", 430 648 "colored", 431 649 "dirs", 650 + "psutil", 432 651 "rubin", 433 652 "serde", 434 653 "serde_json", 654 + "sysinfo", 435 655 "tokio", 656 + "which", 436 657 ] 437 658 438 659 [[package]] ··· 458 679 dependencies = [ 459 680 "proc-macro2", 460 681 "quote", 461 - "syn", 682 + "syn 2.0.18", 462 683 ] 463 684 464 685 [[package]] ··· 473 694 ] 474 695 475 696 [[package]] 697 + name = "sharded-slab" 698 + version = "0.1.4" 699 + source = "registry+https://github.com/rust-lang/crates.io-index" 700 + checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 701 + dependencies = [ 702 + "lazy_static", 703 + ] 704 + 705 + [[package]] 476 706 name = "signal-hook-registry" 477 707 version = "1.4.1" 478 708 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 505 735 506 736 [[package]] 507 737 name = "syn" 738 + version = "1.0.109" 739 + source = "registry+https://github.com/rust-lang/crates.io-index" 740 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 741 + dependencies = [ 742 + "proc-macro2", 743 + "quote", 744 + "unicode-ident", 745 + ] 746 + 747 + [[package]] 748 + name = "syn" 508 749 version = "2.0.18" 509 750 source = "registry+https://github.com/rust-lang/crates.io-index" 510 751 checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" ··· 515 756 ] 516 757 517 758 [[package]] 759 + name = "sysinfo" 760 + version = "0.29.3" 761 + source = "registry+https://github.com/rust-lang/crates.io-index" 762 + checksum = "5bcd0346f90b6bc83526c7b180039a8acd26a5c848cc556d457f6472eb148122" 763 + dependencies = [ 764 + "cfg-if", 765 + "core-foundation-sys", 766 + "libc", 767 + "ntapi", 768 + "once_cell", 769 + "rayon", 770 + "winapi", 771 + ] 772 + 773 + [[package]] 518 774 name = "thiserror" 519 775 version = "1.0.40" 520 776 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 531 787 dependencies = [ 532 788 "proc-macro2", 533 789 "quote", 534 - "syn", 790 + "syn 2.0.18", 791 + ] 792 + 793 + [[package]] 794 + name = "thread_local" 795 + version = "1.1.7" 796 + source = "registry+https://github.com/rust-lang/crates.io-index" 797 + checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" 798 + dependencies = [ 799 + "cfg-if", 800 + "once_cell", 535 801 ] 536 802 537 803 [[package]] ··· 561 827 dependencies = [ 562 828 "proc-macro2", 563 829 "quote", 564 - "syn", 830 + "syn 2.0.18", 831 + ] 832 + 833 + [[package]] 834 + name = "tracing" 835 + version = "0.1.37" 836 + source = "registry+https://github.com/rust-lang/crates.io-index" 837 + checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 838 + dependencies = [ 839 + "cfg-if", 840 + "pin-project-lite", 841 + "tracing-attributes", 842 + "tracing-core", 843 + ] 844 + 845 + [[package]] 846 + name = "tracing-attributes" 847 + version = "0.1.26" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" 850 + dependencies = [ 851 + "proc-macro2", 852 + "quote", 853 + "syn 2.0.18", 565 854 ] 566 855 567 856 [[package]] 857 + name = "tracing-core" 858 + version = "0.1.31" 859 + source = "registry+https://github.com/rust-lang/crates.io-index" 860 + checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" 861 + dependencies = [ 862 + "once_cell", 863 + "valuable", 864 + ] 865 + 866 + [[package]] 867 + name = "tracing-log" 868 + version = "0.1.3" 869 + source = "registry+https://github.com/rust-lang/crates.io-index" 870 + checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 871 + dependencies = [ 872 + "lazy_static", 873 + "log", 874 + "tracing-core", 875 + ] 876 + 877 + [[package]] 878 + name = "tracing-subscriber" 879 + version = "0.3.17" 880 + source = "registry+https://github.com/rust-lang/crates.io-index" 881 + checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" 882 + dependencies = [ 883 + "nu-ansi-term", 884 + "sharded-slab", 885 + "smallvec", 886 + "thread_local", 887 + "tracing-core", 888 + "tracing-log", 889 + ] 890 + 891 + [[package]] 892 + name = "unescape" 893 + version = "0.1.0" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" 896 + 897 + [[package]] 568 898 name = "unicode-ident" 569 899 version = "1.0.9" 570 900 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 575 905 version = "0.2.1" 576 906 source = "registry+https://github.com/rust-lang/crates.io-index" 577 907 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 908 + 909 + [[package]] 910 + name = "valuable" 911 + version = "0.1.0" 912 + source = "registry+https://github.com/rust-lang/crates.io-index" 913 + checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 578 914 579 915 [[package]] 580 916 name = "wasi" 581 917 version = "0.11.0+wasi-snapshot-preview1" 582 918 source = "registry+https://github.com/rust-lang/crates.io-index" 583 919 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 920 + 921 + [[package]] 922 + name = "which" 923 + version = "4.4.0" 924 + source = "registry+https://github.com/rust-lang/crates.io-index" 925 + checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" 926 + dependencies = [ 927 + "either", 928 + "libc", 929 + "once_cell", 930 + ] 584 931 585 932 [[package]] 586 933 name = "winapi"
+8 -2
Cargo.toml
··· 1 1 [package] 2 2 name = "safir" 3 - version = "0.4.0" 3 + version = "0.5.0" 4 4 edition = "2021" 5 5 authors = ["Graham Keenan graham.keenan@outlook.com"] 6 6 license = "MIT OR Apache-2.0" ··· 19 19 serde_json = "1" 20 20 dirs = "5" 21 21 colored = "2" 22 - rubin = "0.3.0" 22 + rubin = "^0.3.1" 23 23 tokio = { version = "1.28.2", features = ["full"] } 24 + psutil = { version = "3.2.2", optional = true } 25 + which = "4.4.0" 26 + sysinfo = "0.29.3" 27 + 28 + [target.'cfg(unix)'.dependencies] 29 + psutil = "3.2.2"
+38
README.md
··· 35 35 export Output the export command for a key / value pair to be entered into a shell session 36 36 clear Clear all keys/values from the store 37 37 purge Purges the .safirstore directory, removing it and its contents 38 + mem Start or stop the Memcache (in-memory store) service 38 39 help Print this message or the help of the given subcommand(s) 39 40 40 41 Options: 41 42 -h, --help Print help 42 43 -V, --version Print version 43 44 ``` 45 + 46 + ## Memcache service 47 + 48 + Safir offers the ability to run the store as a dedicate memcache service using in-memory storage. 49 + 50 + The service runs on `localhost` or `127.0.0.1` on dedicated port `9876`. 51 + Once activated, Safir will continue to act as before expect that all new values added are given to the memcache instead of being saved on disk. 52 + 53 + This can be enabled / diabled with the `start` and `stop` commands respectively. 54 + Note that when the memcache service is disabled, ALL data contained within it is lost so use wisely. 55 + 56 + In cases where you want to save the contents of the memcache, the `dump` command will allow for the contents to be saved out to disk in JSON format. 57 + This behaves as a snapshot as the contents of the cache persist after usage. 58 + 59 + ### Requirements 60 + 61 + Using this requires that the [Rubin CLI](https://crates.io/crates/rubin-cli) be installed. 62 + 63 + ```bash 64 + cargo install rubin-cli 65 + ``` 66 + 67 + ### Usage 68 + 69 + Start or stop the Memcache (in-memory store) service 70 + 71 + Usage: safir mem <COMMAND> 72 + 73 + Commands: 74 + start Start the Safir Memcache server 75 + stop Stop the Safir Memcache server 76 + dump Dump the Safir Memcache server to disk 77 + help Print this message or the help of the given subcommand(s) 78 + 79 + Options: 80 + -h, --help Print help 81 + -V, --version Print version 44 82 45 83 ## v0.3.0 -> v0.4.0 46 84
+38
src/cfg.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use std::{io::Result, path::Path}; 3 + use tokio::{fs, io::AsyncWriteExt}; 4 + 5 + #[derive(Default, Serialize, Deserialize, Debug)] 6 + pub struct SafirConfig { 7 + pub memcache_pid: Option<u32>, 8 + pub memcache_flag: bool, 9 + } 10 + 11 + impl SafirConfig { 12 + pub fn new() -> Self { 13 + Self::default() 14 + } 15 + 16 + pub async fn load(cfg_path: impl AsRef<Path>) -> Result<Self> { 17 + let cfg = fs::read_to_string(cfg_path).await?; 18 + Ok(serde_json::from_str(&cfg).expect("unable to deserialize safir config")) 19 + } 20 + 21 + pub fn pid(mut self, pid: Option<u32>) -> Self { 22 + self.memcache_pid = pid; 23 + self 24 + } 25 + 26 + pub fn set_memcache(mut self, state: bool) -> Self { 27 + self.memcache_flag = state; 28 + self 29 + } 30 + 31 + pub async fn write(&self, cfg_path: impl AsRef<Path>) -> Result<()> { 32 + let data = serde_json::to_string_pretty(&self).expect("unable to serialize safir config"); 33 + let mut file = fs::File::create(cfg_path).await?; 34 + file.write_all(data.as_bytes()).await?; 35 + 36 + Ok(()) 37 + } 38 + }
+23
src/cli.rs
··· 35 35 36 36 /// Purges the .safirstore directory, removing it and its contents 37 37 Purge, 38 + 39 + /// Start or stop the Memcache (in-memory store) service 40 + #[clap(subcommand)] 41 + Mem(MemArgs), 38 42 } 39 43 40 44 /// Arguments for adding a value to the store with a given key ··· 71 75 /// Name of the keys to display (e.g. alias / export) 72 76 pub keys: Vec<String>, 73 77 } 78 + 79 + /// Arguments for the Mem sub command 80 + #[derive(Subcommand, Debug)] 81 + pub enum MemArgs { 82 + /// Start the Safir Memcache server 83 + Start, 84 + 85 + /// Stop the Safir Memcache server 86 + Stop, 87 + 88 + /// Dump the Safir Memcache server to disk 89 + Dump(DumpArgs), 90 + } 91 + 92 + #[derive(Args, Debug)] 93 + pub struct DumpArgs { 94 + /// Path to save the store to 95 + pub path: String, 96 + }
+124 -10
src/main.rs
··· 1 + mod cfg; 1 2 mod cli; 2 3 mod safir; 4 + mod utils; 3 5 4 6 use cli::*; 5 7 8 + use std::process::{Command, Stdio}; 9 + 6 10 #[tokio::main] 7 11 async fn main() -> std::io::Result<()> { 8 12 let cli = Cli::parse(); 9 - let mut safir = safir::Safir::init().await?; 13 + let store_dir = utils::create_safir_directory().await?; 14 + let safir_cfg = &store_dir.join("safir.cfg"); 15 + 16 + let mut cfg = utils::load_safir_config(&safir_cfg).await?; 17 + 18 + // Should probably only initialise when not using memcache but meh... 19 + // Easier this way 20 + let mut safir = safir::Safir::init(&store_dir).await?; 21 + let safir_mem = safir::SafirMemcache::new(); 10 22 11 23 match &cli.command { 12 24 Commands::Add(args) => { 13 - safir 14 - .add_entry(args.key.clone(), args.value.clone()) 15 - .await?; 25 + if cfg.memcache_flag { 26 + safir_mem.add_entry(&args.key, &args.value).await? 27 + } else { 28 + safir 29 + .add_entry(args.key.clone(), args.value.clone()) 30 + .await?; 31 + } 16 32 } 17 33 Commands::Get(args) => { 18 - if let Some(key) = &args.key { 34 + if cfg.memcache_flag { 35 + if let Some(key) = &args.key { 36 + safir_mem.get_string(key).await?; 37 + } else { 38 + utils::print_header(); 39 + utils::print_output("A key is required for memcache GET command!"); 40 + } 41 + } else if let Some(key) = &args.key { 19 42 safir.get_entry(key.clone())?; 20 43 } else { 21 44 safir.display_all(); 22 45 } 23 46 } 24 47 Commands::Rm(args) => { 25 - safir.remove_entry(args.key.clone()).await?; 48 + if cfg.memcache_flag { 49 + safir_mem.remove_entry(args.key.clone()).await?; 50 + } else { 51 + safir.remove_entry(args.key.clone()).await?; 52 + } 26 53 } 27 54 Commands::Alias(args) => { 28 - safir.set_commands("alias", &args.keys); 55 + if cfg.memcache_flag { 56 + safir_mem.set_commands("alias", &args.keys).await; 57 + } else { 58 + safir.set_commands("alias", &args.keys); 59 + } 29 60 } 30 61 Commands::Export(args) => { 31 - safir.set_commands("export", &args.keys); 62 + if cfg.memcache_flag { 63 + safir_mem.set_commands("export", &args.keys).await; 64 + } else { 65 + safir.set_commands("export", &args.keys); 66 + } 32 67 } 33 68 Commands::Clear => { 34 - safir.clear_entries().await?; 69 + if cfg.memcache_flag { 70 + safir_mem.clear_entries().await?; 71 + } else { 72 + safir.clear_entries().await?; 73 + } 35 74 } 36 - Commands::Purge => safir.purge(), 75 + Commands::Purge => { 76 + if !cfg.memcache_flag { 77 + safir.purge(); 78 + } 79 + } 80 + Commands::Mem(args) => match args { 81 + MemArgs::Start => { 82 + if !utils::check_rubin_installed() { 83 + eprintln!( 84 + "The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`" 85 + ); 86 + return Ok(()); 87 + } 88 + 89 + if let Some(pid) = cfg.memcache_pid { 90 + println!( 91 + "Safir memcache service is already running on 127.0.0.1:9876 - PID {}", 92 + pid 93 + ); 94 + 95 + return Ok(()); 96 + } 97 + 98 + let child = Command::new("rubin") 99 + .args(["server"]) 100 + .stdout(Stdio::null()) 101 + .stderr(Stdio::null()) 102 + .stdin(Stdio::null()) 103 + .spawn() 104 + .expect("unable to spawn child process"); 105 + 106 + let pid = child.id(); 107 + cfg = cfg.pid(Some(pid)).set_memcache(true); 108 + cfg.write(&safir_cfg).await?; 109 + println!( 110 + "Safir memcache service started at 127.0.0.1:9876 - PID {}", 111 + pid 112 + ); 113 + } 114 + MemArgs::Stop => { 115 + if !utils::check_rubin_installed() { 116 + eprintln!("The Rubin binary must be installed to use this feature, please install it via cargo using `cargo install rubin-cli`"); 117 + return Ok(()); 118 + } 119 + 120 + let pid = match cfg.memcache_pid { 121 + Some(pid) => pid, 122 + None => { 123 + println!("Safir memcache service does not seem to be running."); 124 + return Ok(()); 125 + } 126 + }; 127 + 128 + if let Err(err) = utils::kill_process(pid).await { 129 + eprintln!( 130 + "Safir memcache service failed to stop, manual removal may be necessary - {}", 131 + err 132 + ); 133 + } else { 134 + cfg = cfg.pid(None).set_memcache(false); 135 + cfg.write(&safir_cfg).await?; 136 + println!("Stopping Safir memcache service!"); 137 + } 138 + } 139 + MemArgs::Dump(args) => { 140 + if cfg.memcache_pid.is_none() { 141 + println!("Safir memcache service does not seem to be running"); 142 + return Ok(()); 143 + } 144 + 145 + if let Err(e) = safir_mem.dump_store(&args.path).await { 146 + eprintln!("unable to dump Safir memcache service: {}", e); 147 + } 148 + } 149 + }, 37 150 } 151 + 38 152 Ok(()) 39 153 }
+69 -29
src/safir.rs
··· 8 8 //! 9 9 //! Safir gives you the option to add / get / remove items from the store 10 10 //! and to clear / purge when you're finished with them. 11 - use std::io::{Result, Write}; 11 + use std::{io::Result, path::Path}; 12 12 13 13 use colored::*; 14 14 15 - use rubin::store::persistence::PersistentStore; 16 - 17 - /// Formats and prints the message to stdout 18 - fn print_output(msg: &str) { 19 - println!("{}", format!("{}\n", msg)); 20 - } 21 - 22 - /// Prints the Safirstore header 23 - fn print_header() { 24 - println!("{}", "--=Safirstore=--\n".bold()); 25 - } 15 + use crate::utils::{confirm_entry, print_header, print_output}; 16 + use rubin::{net::client::RubinClient, store::persistence::PersistentStore}; 26 17 27 18 /// Safir Store (fancy wrapper around reading and writing to a JSON file) 28 19 pub struct Safir { ··· 31 22 32 23 impl Safir { 33 24 /// Initialises the Safirstore if not already initialised 34 - pub async fn init() -> Result<Self> { 35 - let home_dir = dirs::home_dir().unwrap(); 36 - let store_path = home_dir.join(".safirstore/safirstore.json"); 25 + pub async fn init(store_loc: &Path) -> Result<Self> { 26 + let store_path = store_loc.join("safirstore.json"); 37 27 let mut ps = if store_path.exists() { 38 28 PersistentStore::from_existing(store_path).await? 39 29 } else { ··· 104 94 105 95 /// Clear the the contents of the store and update onto disk 106 96 pub async fn clear_entries(&mut self) -> Result<()> { 107 - if self.confirm_entry("Are you sure you want to clear the store?") { 97 + if confirm_entry("Are you sure you want to clear the store?") { 108 98 self.store.clear_strings().await?; 109 99 } 110 100 ··· 113 103 114 104 /// Remove the store directory and all contents 115 105 pub fn purge(&mut self) { 116 - if self.confirm_entry("Are you sure you want to purge Safirstore?") { 106 + if confirm_entry("Are you sure you want to purge Safirstore?") { 117 107 std::fs::remove_dir_all(&self.store.path) 118 108 .expect("unable to remove safirstore directory"); 119 109 } 120 110 } 111 + } 121 112 122 - /// Confirmation dialog for important calls 123 - fn confirm_entry(&self, msg: &str) -> bool { 124 - let mut answer = String::new(); 125 - print!("{} (y/n) ", msg); 126 - std::io::stdout().flush().expect("failed to flush buffer"); 113 + pub struct SafirMemcache { 114 + client: RubinClient, 115 + } 127 116 128 - let _ = std::io::stdin() 129 - .read_line(&mut answer) 130 - .expect("unable to get input from user"); 117 + impl SafirMemcache { 118 + pub fn new() -> Self { 119 + Self { 120 + client: RubinClient::new("127.0.0.1", 9876), 121 + } 122 + } 131 123 132 - let answer = answer.trim(); 133 - if answer == "y" || answer == "Y" { 134 - return true; 124 + pub async fn add_entry(&self, key: &str, value: &str) -> Result<()> { 125 + self.client.insert_string(key, value).await?; 126 + Ok(()) 127 + } 128 + 129 + pub async fn get_string(&self, key: &str) -> Result<()> { 130 + print_header(); 131 + let output = if let Ok(val) = self.client.get_string(key).await { 132 + format!("{}: \"{}\"", key.bold().yellow(), val) 133 + } else { 134 + format!("{}: ", key.bold().yellow()) 135 + }; 136 + 137 + print_output(&output); 138 + 139 + Ok(()) 140 + } 141 + 142 + pub async fn remove_entry(&self, keys: Vec<String>) -> Result<()> { 143 + for key in &keys { 144 + self.client.remove_string(key).await?; 135 145 } 136 146 137 - false 147 + Ok(()) 148 + } 149 + 150 + pub async fn set_commands(&self, prefix: &str, keys: &Vec<String>) { 151 + print_header(); 152 + let prefix = match prefix { 153 + "alias" => "alias".bold().green(), 154 + "export" => "export".bold().magenta(), 155 + _ => prefix.bold(), 156 + }; 157 + 158 + for key in keys { 159 + if let Ok(value) = self.client.get_string(key).await { 160 + println!("{} {}=\"{}\"\n", prefix, key.bold().yellow(), value); 161 + } 162 + } 163 + } 164 + 165 + pub async fn clear_entries(&self) -> Result<()> { 166 + if confirm_entry("Are you sure you want to clear the store?") { 167 + self.client.clear_strings().await?; 168 + } 169 + 170 + Ok(()) 171 + } 172 + 173 + pub async fn dump_store(&self, path: &str) -> Result<()> { 174 + self.client.dump_store(path).await?; 175 + println!("Safir memcache dumped to {}", path); 176 + 177 + Ok(()) 138 178 } 139 179 }
+112
src/utils.rs
··· 1 + use std::io::{self, Write}; 2 + use std::path::{Path, PathBuf}; 3 + 4 + use crate::cfg::SafirConfig; 5 + 6 + use colored::*; 7 + use sysinfo::{Pid, System, SystemExt}; 8 + use tokio::fs; 9 + 10 + pub fn check_rubin_installed() -> bool { 11 + if which::which("rubin").is_ok() { 12 + return true; 13 + } 14 + 15 + false 16 + } 17 + 18 + pub fn check_process_running(pid: u32) -> bool { 19 + let mut system = System::new_all(); 20 + system.refresh_all(); 21 + if system.process(Pid::from(pid as usize)).is_some() { 22 + return true; 23 + } 24 + 25 + false 26 + } 27 + 28 + pub async fn path_exists(path: impl AsRef<Path>) -> bool { 29 + path.as_ref().exists() 30 + } 31 + 32 + pub async fn create_safir_directory() -> io::Result<PathBuf> { 33 + let home_dir = dirs::home_dir().unwrap(); 34 + let store_path = home_dir.join(".safirstore"); 35 + fs::create_dir_all(&store_path).await?; 36 + 37 + Ok(store_path) 38 + } 39 + 40 + #[cfg(target_family = "unix")] 41 + pub async fn kill_process(pid: u32) -> io::Result<()> { 42 + if let Ok(process) = psutil::process::Process::new(pid) { 43 + if let Err(err) = process.kill() { 44 + eprintln!("failed to kill process: {}", err); 45 + } 46 + } else { 47 + eprintln!("failed to get process information"); 48 + } 49 + 50 + Ok(()) 51 + } 52 + 53 + #[cfg(target_os = "windows")] 54 + pub async fn kill_process(pid: u32) { 55 + println!("*** Windows -- This is experimental and may not work as intended! ***"); 56 + let output = Command::new("taskkill") 57 + .arg("/F") 58 + .arg("/PID") 59 + .arg(pid.to_string()) 60 + .output() 61 + .expect("failed to call taskkill"); 62 + 63 + if !output.status.success() { 64 + eprintln!("failed to terminate process"); 65 + } 66 + } 67 + 68 + /// Formats and prints the message to stdout 69 + pub fn print_output(msg: &str) { 70 + println!("{}\n", msg); 71 + } 72 + 73 + /// Prints the Safirstore header 74 + pub fn print_header() { 75 + println!("{}", "--=Safirstore=--\n".bold()); 76 + } 77 + 78 + /// Confirmation dialog for important calls 79 + pub fn confirm_entry(msg: &str) -> bool { 80 + let mut answer = String::new(); 81 + print!("{} (y/n) ", msg); 82 + std::io::stdout().flush().expect("failed to flush buffer"); 83 + 84 + let _ = std::io::stdin() 85 + .read_line(&mut answer) 86 + .expect("unable to get input from user"); 87 + 88 + let answer = answer.trim().to_lowercase(); 89 + if answer == "y" { 90 + return true; 91 + } 92 + 93 + false 94 + } 95 + 96 + pub async fn load_safir_config(safir_cfg: impl AsRef<Path>) -> io::Result<SafirConfig> { 97 + let mut cfg = if path_exists(&safir_cfg).await { 98 + SafirConfig::load(&safir_cfg).await? 99 + } else { 100 + SafirConfig::new() 101 + }; 102 + 103 + // Used in cases where the process has ended ungracefully and the config hasnt been updated 104 + if let Some(pid) = cfg.memcache_pid { 105 + if !check_process_running(pid) { 106 + cfg = SafirConfig::new(); 107 + cfg.write(&safir_cfg).await?; 108 + } 109 + } 110 + 111 + Ok(cfg) 112 + }