diff --git a/Cargo.lock b/Cargo.lock index a596718df6a796d97bb50679976d7372d0219069..95d8175e20044dae16458ad848fed16fbb86d772 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project 1.0.8", - "rand", + "rand 0.7.3", "regex", "serde", "serde_json", @@ -289,6 +289,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.3", + "once_cell", + "version_check 0.9.3", +] + [[package]] name = "aho-corasick" version = "0.7.18" @@ -385,6 +396,35 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-native-tls" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" +dependencies = [ + "async-std", + "native-tls", + "thiserror", + "url", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + [[package]] name = "async-std" version = "1.10.0" @@ -395,6 +435,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", + "async-process", "crossbeam-utils", "futures-channel", "futures-core", @@ -429,6 +470,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-waker" version = "1.0.0" @@ -470,7 +520,7 @@ dependencies = [ "log", "mime", "percent-encoding", - "rand", + "rand 0.7.3", "serde", "serde_json", "serde_urlencoded", @@ -670,6 +720,22 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536" +[[package]] +name = "core-foundation" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -679,6 +745,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc32fast" version = "1.3.0" @@ -688,6 +769,26 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -736,6 +837,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "either" version = "1.6.1" @@ -796,6 +903,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -852,6 +974,17 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.17" @@ -998,6 +1131,18 @@ name = "hashbrown" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] [[package]] name = "heck" @@ -1017,6 +1162,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hostname" version = "0.3.1" @@ -1096,6 +1247,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -1154,6 +1314,17 @@ version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +[[package]] +name = "libsqlite3-sys" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1222,6 +1393,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.4.4" @@ -1274,6 +1451,24 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -1295,6 +1490,17 @@ dependencies = [ "version_check 0.1.5", ] +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check 0.9.3", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -1336,6 +1542,39 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[package]] +name = "openssl-probe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" + +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking" version = "2.0.0" @@ -1431,6 +1670,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + [[package]] name = "polling" version = "2.2.0" @@ -1518,9 +1763,21 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", - "rand_hc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", + "rand_hc 0.3.1", ] [[package]] @@ -1530,7 +1787,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", ] [[package]] @@ -1542,13 +1809,31 @@ dependencies = [ "getrandom 0.1.16", ] +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.3", +] + [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core 0.6.3", ] [[package]] @@ -1583,6 +1868,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -1617,12 +1911,45 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi 0.3.9", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "0.9.0" @@ -1706,6 +2033,29 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +[[package]] +name = "sha2" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "signal-hook" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1759,6 +2109,98 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools", + "nom 7.1.0", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7911b0031a0247af40095838002999c7a52fba29d9739e93326e71a5a1bc9d43" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec89bfaca8f7737439bad16d52b07f1ccd0730520d3bf6ae9d069fe4b641fb1" +dependencies = [ + "ahash", + "atoi", + "bitflags", + "byteorder", + "bytes 1.1.0", + "crc", + "crossbeam-channel", + "crossbeam-queue", + "crossbeam-utils", + "either", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "indexmap", + "itoa 0.4.8", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "once_cell", + "parking_lot", + "percent-encoding", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "url", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "584866c833511b1a152e87a7ee20dee2739746f60c858b3c5209150bc4b466f5" +dependencies = [ + "dotenv", + "either", + "heck", + "once_cell", + "proc-macro2", + "quote", + "sha2", + "sqlx-core", + "sqlx-rt", + "syn", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d1bd069de53442e7a320f525a6d4deb8bb0621ac7a55f7eccbc2b58b57f43d0" +dependencies = [ + "async-native-tls", + "async-std", + "native-tls", +] + [[package]] name = "standback" version = "0.2.17" @@ -1817,6 +2259,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.8.0" @@ -1858,6 +2310,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "rand 0.8.4", + "redox_syscall 0.2.10", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -2049,7 +2515,7 @@ dependencies = [ "idna", "lazy_static", "log", - "rand", + "rand 0.7.3", "smallvec", "thiserror", "tokio", @@ -2089,6 +2555,7 @@ dependencies = [ "serde", "serde_json", "simple-logging", + "sqlx", "structopt", "thiserror", "url", @@ -2143,6 +2610,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "url" version = "2.2.2" @@ -2180,7 +2653,7 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29769400af8b264944b851c961a4a6930e76604f59b1fcd51246bab6a296c8c" dependencies = [ - "nom", + "nom 4.2.3", "proc-macro2", "quote", "syn", @@ -2206,6 +2679,12 @@ dependencies = [ "version_check 0.9.3", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -2327,6 +2806,16 @@ dependencies = [ "cc", ] +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "widestring" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index c538a529c2d89118b89e6e2702b8cd1b203f4e83..5f908aab2c50accf79b6613abd0dc33e956289b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ chrono = { version = "^0.4.19", features = ["serde"] } serde_json = "1" log = "^0.4.14" url = "2.2.2" -simple-logging = { version = "^2.0.2" } \ No newline at end of file +simple-logging = { version = "^2.0.2" } +sqlx = { version = "0.5", features = [ "runtime-async-std-native-tls", "sqlite" ] } \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 8e626c8e5cd52df214b5139f9c90792c340033c8..810ab5b74703cb37c17b32a52d0d9e49246482d0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,44 +5,73 @@ use structopt::StructOpt; use crate::errors::TuserError; use crate::storages::AvailableStores; +#[derive(StructOpt, Debug, Clone)] +pub struct StorageOptions { + /// Tuser storage type. + #[structopt(long, short, default_value = "file_storage", env = "TUSER_STORAGE")] + pub storage: AvailableStores, + + /// Tuser data directory + /// + /// This directory is used to store files + /// for all *file_storage storages. + #[structopt( + long, + default_value = "./data", + required_if("storage", "file_storage"), + required_if("storage", "sqlite_file_storage") + )] + pub data: PathBuf, + + /// Path to SQLite file. + /// + /// This file is used to + /// store information about uploaded files. + #[structopt( + long, + default_value = "data/info.sqlite3", + required_if("storage", "sqlite_file_storage") + )] + pub sqlite_dsn: PathBuf, +} + #[derive(Debug, StructOpt, Clone)] #[structopt(name = "tuser", about = "Tus server implementation in Rust.")] pub struct TuserConf { /// Tuser host - #[structopt(short, long, default_value = "0.0.0.0")] + #[structopt(short, long, default_value = "0.0.0.0", env = "TUSER_HOST")] pub host: String, /// Tuser server port - #[structopt(short, long, default_value = "1081")] + #[structopt(short, long, default_value = "1081", env = "TUSER_PORT")] pub port: u16, /// Tuser base API url - #[structopt(long, default_value = "/files")] + #[structopt(long, default_value = "/files", env = "TUSER_URL")] pub url: String, - /// Tuser data directory - #[structopt(long, default_value = "./data")] - pub data: PathBuf, - /// Enabled hooks for http events - #[structopt(long, default_value = "pre-create,post-finish")] + #[structopt(long, default_value = "pre-create,post-finish", env = "TUSER_HOOKS")] pub enabled_hooks: String, /// Tuser maximum log level - #[structopt(long, default_value = "INFO")] + #[structopt(long, default_value = "INFO", env = "TUSER_LOG_LEVEL")] pub log_level: log::LevelFilter, - /// Storage type - #[structopt(long, short, default_value = "file_storage")] - pub storage: AvailableStores, - /// Number of actix workers default value = number of cpu cores. - #[structopt(long, short)] + #[structopt(long, short, env = "TUSER_WORKERS")] pub workers: Option<usize>, /// Enabled extensions for TUS protocol. - #[structopt(long, default_value = "creation,creation-with-upload")] + #[structopt( + long, + default_value = "creation,creation-with-upload", + env = "TUSER_EXTENSIONS" + )] pub extensions: String, + + #[structopt(flatten)] + pub storage_opts: StorageOptions, } /// Enum of available Protocol Extensions diff --git a/src/errors.rs b/src/errors.rs index 3a70a22f99e631bea61689b3e3d4a82669b93724..60851b687f79b284261d3ae3d2a88bd5a621bc21 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -3,11 +3,10 @@ use std::io::{Error, ErrorKind}; use actix_web::dev::HttpResponseBuilder; use actix_web::http::StatusCode; use actix_web::{HttpResponse, ResponseError}; -use thiserror::Error; pub type TuserResult<T> = Result<T, TuserError>; -#[derive(Error, Debug)] +#[derive(thiserror::Error, Debug)] pub enum TuserError { #[error("File with id {0} was not found")] FileNotFound(String), @@ -19,6 +18,8 @@ pub enum TuserError { Unknown, #[error("Unable to serialize object")] UnableToSerialize(#[from] serde_json::Error), + #[error("Database error: {0}")] + DatabaseError(#[from] sqlx::Error), #[error("Unable to get file information")] UnableToReadInfo, #[error("Unable to write file {0}")] diff --git a/src/main.rs b/src/main.rs index bbfd9b5a2868870d0b7e6c45e7a2ed15016fe026..1ead5e97e8e8a2712d42360ec567c318ac115d31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::sync::Arc; use actix_web::http::Method; use actix_web::{ @@ -30,19 +31,21 @@ mod storages; /// This function may throw an error /// if the server can't be bound to the /// given address. -pub fn create_server<S: Storage + 'static + Send>( - storage: S, +pub fn create_server( + storage: Box<dyn Storage + Send + Sync>, app_conf: TuserConf, ) -> Result<Server, std::io::Error> { let host = app_conf.host.clone(); let port = app_conf.port; let workers = app_conf.workers; + let storage_data: web::Data<Box<dyn Storage + Send + Sync>> = + web::Data::from(Arc::new(storage)); let mut server = HttpServer::new(move || { App::new() .data(app_conf.clone()) - .data(storage.clone()) + .app_data(storage_data.clone()) // Adds all routes. - .configure(protocol::setup::<S>(app_conf.clone())) + .configure(protocol::setup(app_conf.clone())) // Main middleware that appends TUS headers. .wrap( middleware::DefaultHeaders::new() @@ -81,7 +84,7 @@ async fn main() -> std::io::Result<()> { let app_conf = TuserConf::from_args(); simple_logging::log_to_stderr(app_conf.log_level); - let storage = app_conf.storage.get_storage(&app_conf); + let mut storage = app_conf.storage_opts.storage.get(&app_conf); if let Err(err) = storage.prepare().await { error!("{}", err); return Err(err.into()); diff --git a/src/protocol/core/mod.rs b/src/protocol/core/mod.rs index b2248f46f17bfd92e83814d8efb0e0947a462697..c76a19dd10582c87d9f213d1f7e34f97aa166b9e 100644 --- a/src/protocol/core/mod.rs +++ b/src/protocol/core/mod.rs @@ -1,6 +1,6 @@ use actix_web::{guard, middleware, web}; -use crate::{Storage, TuserConf}; +use crate::TuserConf; mod routes; @@ -12,10 +12,7 @@ mod routes; /// OPTIONS /api - to get info about the app. /// HEAD /api/file - to get info about the file. /// PATCH /api/file - to add bytes to file. -pub fn add_extension<S: Storage + 'static + Send>( - web_app: &mut web::ServiceConfig, - app_conf: &TuserConf, -) { +pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &TuserConf) { web_app .service( // PATCH /base/{file_id} @@ -31,7 +28,7 @@ pub fn add_extension<S: Storage + 'static + Send>( web::resource(app_conf.file_url().as_str()) .name("core:write_bytes") .guard(guard::Patch()) - .to(routes::write_bytes::<S>), + .to(routes::write_bytes), ) .service( // HEAD /base/{file_id} @@ -41,6 +38,6 @@ pub fn add_extension<S: Storage + 'static + Send>( .guard(guard::Head()) // Header to prevent the client and/or proxies from caching the response. .wrap(middleware::DefaultHeaders::new().header("Cache-Control", "no-store")) - .to(routes::get_file_info::<S>), + .to(routes::get_file_info), ); } diff --git a/src/protocol/core/routes.rs b/src/protocol/core/routes.rs index b43a63b967f6d101179857736d1d8d3f9b7ef218..87e769bbb080bdef5a3678114233595e0f61ad47 100644 --- a/src/protocol/core/routes.rs +++ b/src/protocol/core/routes.rs @@ -21,8 +21,8 @@ pub fn server_info(app_conf: web::Data<TuserConf>) -> HttpResponse { .body("") } -pub async fn get_file_info<T: Storage>( - storage: web::Data<T>, +pub async fn get_file_info( + storage: web::Data<Box<dyn Storage + Send + Sync>>, request: HttpRequest, ) -> actix_web::Result<HttpResponse> { let resp = if let Some(file_id) = request.match_info().get("file_id") { @@ -37,10 +37,10 @@ pub async fn get_file_info<T: Storage>( Ok(resp) } -pub async fn write_bytes<T: Storage>( +pub async fn write_bytes( request: HttpRequest, bytes: Bytes, - storage: web::Data<T>, + storage: web::Data<Box<dyn Storage + Send + Sync>>, ) -> actix_web::Result<HttpResponse> { let content_type = request diff --git a/src/protocol/creation/mod.rs b/src/protocol/creation/mod.rs index 69214dc27555183200aae0bab7ff92c82302a40d..0b0245429b3672832b6a7f2d24cc66b05366ade0 100644 --- a/src/protocol/creation/mod.rs +++ b/src/protocol/creation/mod.rs @@ -1,6 +1,6 @@ use actix_web::{guard, web}; -use crate::{Storage, TuserConf}; +use crate::TuserConf; mod routes; @@ -8,16 +8,13 @@ mod routes; /// /// This extension allows you /// to create file before sending data. -pub fn add_extension<S: Storage + 'static + Send>( - web_app: &mut web::ServiceConfig, - app_conf: &TuserConf, -) { +pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &TuserConf) { web_app.service( // Post /base // URL for creating files. web::resource(app_conf.base_url().as_str()) .name("creation:create_file") .guard(guard::Post()) - .to(routes::create_file::<S>), + .to(routes::create_file), ); } diff --git a/src/protocol/creation/routes.rs b/src/protocol/creation/routes.rs index d1177196b60e2c4c793282adcbf8e15c431b72f6..beb19b733cb364e557cb24d1d06ea53975a07379 100644 --- a/src/protocol/creation/routes.rs +++ b/src/protocol/creation/routes.rs @@ -4,8 +4,8 @@ use actix_web::{web, HttpRequest, HttpResponse}; use crate::Storage; -pub async fn create_file<T: Storage>( - storage: web::Data<T>, +pub async fn create_file( + storage: web::Data<Box<dyn Storage + Send + Sync>>, request: HttpRequest, ) -> actix_web::Result<HttpResponse> { let length = request diff --git a/src/protocol/creation_with_upload/mod.rs b/src/protocol/creation_with_upload/mod.rs index a0b3707c169b7d6556bf283c548ee6050747f3a2..b02b0cf235280e2a2fb46b88a0c6e1f5b0cfb0e9 100644 --- a/src/protocol/creation_with_upload/mod.rs +++ b/src/protocol/creation_with_upload/mod.rs @@ -1,19 +1,16 @@ use actix_web::{guard, web}; -use crate::{Storage, TuserConf}; +use crate::TuserConf; mod routes; -pub fn add_extension<S: Storage + 'static + Send>( - web_app: &mut web::ServiceConfig, - app_conf: &TuserConf, -) { +pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &TuserConf) { web_app.service( // Post /base // URL for creating files. web::resource(app_conf.base_url().as_str()) .name("creation-with-upload:create_file") .guard(guard::Post()) - .to(routes::create_file::<S>), + .to(routes::create_file), ); } diff --git a/src/protocol/creation_with_upload/routes.rs b/src/protocol/creation_with_upload/routes.rs index c3b38a00a7e04879ad439a20804187dee3581c8b..7b6cb95e36852b39b2f9b17ce2e1d7ebec2d31da 100644 --- a/src/protocol/creation_with_upload/routes.rs +++ b/src/protocol/creation_with_upload/routes.rs @@ -8,11 +8,11 @@ use crate::Storage; /// Creates files with initial bytes. /// /// This function is similar to -/// creation:create_file, +/// `creation:create_file`, /// except that it can write bytes /// right after it created a data file. -pub async fn create_file<T: Storage>( - storage: web::Data<T>, +pub async fn create_file( + storage: web::Data<Box<dyn Storage + Send + Sync>>, request: HttpRequest, bytes: Bytes, ) -> actix_web::Result<HttpResponse> { diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index b41c1475af3f43fa1e5f8ec852b3971b04ab31cf..ae3cd98d6729c610683c89e462c3fa675e4a45c3 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -1,7 +1,7 @@ use actix_web::web; use crate::config::ProtocolExtensions; -use crate::{Storage, TuserConf}; +use crate::TuserConf; mod core; mod creation; @@ -12,21 +12,19 @@ mod termination; /// /// This function resolves all protocol extensions /// provided by CLI into services and adds it to the application. -pub fn setup<S: Storage + 'static + Send>( - app_conf: TuserConf, -) -> Box<dyn Fn(&mut web::ServiceConfig)> { +pub fn setup(app_conf: TuserConf) -> Box<dyn Fn(&mut web::ServiceConfig)> { Box::new(move |web_app| { for extension in app_conf.extensions_vec() { match extension { - ProtocolExtensions::Creation => creation::add_extension::<S>(web_app, &app_conf), + ProtocolExtensions::Creation => creation::add_extension(web_app, &app_conf), ProtocolExtensions::CreationWithUpload => { - creation_with_upload::add_extension::<S>(web_app, &app_conf); + creation_with_upload::add_extension(web_app, &app_conf); } ProtocolExtensions::Termination => { - termination::add_extension::<S>(web_app, &app_conf); + termination::add_extension(web_app, &app_conf); } } } - core::add_extension::<S>(web_app, &app_conf); + core::add_extension(web_app, &app_conf); }) } diff --git a/src/protocol/termination/mod.rs b/src/protocol/termination/mod.rs index 7ed341d05ad5a48c9b28866d81199ae171a42545..ae8c62cf57540cd4faf9cfc82761486246800b2f 100644 --- a/src/protocol/termination/mod.rs +++ b/src/protocol/termination/mod.rs @@ -1,6 +1,6 @@ use actix_web::{guard, web}; -use crate::{Storage, TuserConf}; +use crate::TuserConf; mod routes; @@ -8,15 +8,12 @@ mod routes; /// /// This extension allows you /// to terminate file upload. -pub fn add_extension<S: Storage + 'static + Send>( - web_app: &mut web::ServiceConfig, - app_conf: &TuserConf, -) { +pub fn add_extension(web_app: &mut web::ServiceConfig, app_conf: &TuserConf) { web_app.service( // DELETE /base/file web::resource(app_conf.file_url().as_str()) .name("termination:terminate") .guard(guard::Delete()) - .to(routes::terminate::<S>), + .to(routes::terminate), ); } diff --git a/src/protocol/termination/routes.rs b/src/protocol/termination/routes.rs index 0aa971ce962e985810e74013579067879d74b2b7..4125a20cbf88f23297b11e927d45cdb28b979257 100644 --- a/src/protocol/termination/routes.rs +++ b/src/protocol/termination/routes.rs @@ -9,8 +9,8 @@ use crate::Storage; /// /// This method will remove all /// files by id. -pub async fn terminate<S: Storage>( - storage: web::Data<S>, +pub async fn terminate( + storage: web::Data<Box<dyn Storage + Send + Sync>>, request: HttpRequest, ) -> TuserResult<HttpResponse> { let file_id_opt = request.match_info().get("file_id").map(String::from); diff --git a/src/storages/file_storage.rs b/src/storages/file_storage.rs index 1f6abeb63b2a8c7626e77c4c6ce4e303079a81ab..433033f687ca5bdf72e5c7cd3c8eaeac45ff0894 100644 --- a/src/storages/file_storage.rs +++ b/src/storages/file_storage.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::PathBuf; use actix_files::NamedFile; -use async_std::fs::{DirBuilder, OpenOptions, read_to_string, remove_file}; +use async_std::fs::{read_to_string, remove_file, DirBuilder, OpenOptions}; use async_std::prelude::*; use async_trait::async_trait; use log::error; @@ -23,20 +23,23 @@ impl FileStorage { } pub fn info_file_path(&self, file_id: &str) -> PathBuf { - self.app_conf.data.join(format!("{}.info", file_id)) + self.app_conf + .storage_opts + .data + .join(format!("{}.info", file_id)) } pub fn data_file_path(&self, file_id: &str) -> PathBuf { - self.app_conf.data.join(file_id.to_string()) + self.app_conf.storage_opts.data.join(file_id.to_string()) } } #[async_trait] impl Storage for FileStorage { - async fn prepare(&self) -> TuserResult<()> { - if !self.app_conf.data.exists() { + async fn prepare(&mut self) -> TuserResult<()> { + if !self.app_conf.storage_opts.data.exists() { DirBuilder::new() - .create(self.app_conf.data.as_path()) + .create(self.app_conf.storage_opts.data.as_path()) .await .map_err(|err| TuserError::UnableToPrepareStorage(err.to_string()))?; } diff --git a/src/storages/mod.rs b/src/storages/mod.rs index 613d77f47a5b07d21f11729cd7d609ff317fbbc9..afeb42ace6b409b1d047a9b271c836a7cd019e09 100644 --- a/src/storages/mod.rs +++ b/src/storages/mod.rs @@ -3,21 +3,25 @@ use std::str::FromStr; use actix_files::NamedFile; use async_trait::async_trait; -use chrono::serde::ts_seconds; use chrono::{DateTime, Utc}; +use chrono::serde::ts_seconds; use derive_more::{Display, From}; use serde::{Deserialize, Serialize}; +use sqlx::FromRow; use crate::errors::TuserResult; use crate::TuserConf; pub mod file_storage; +pub mod sqlite_file_storage; /// Enum of available Storage implementations. #[derive(PartialEq, From, Display, Clone, Debug)] pub enum AvailableStores { #[display(fmt = "FileStorage")] FileStorage, + #[display(fmt = "SqliteFileStorage")] + SqliteFileStorage, } impl FromStr for AvailableStores { @@ -31,6 +35,7 @@ impl FromStr for AvailableStores { fn from_str(input: &str) -> Result<AvailableStores, Self::Err> { match input { "file_storage" => Ok(AvailableStores::FileStorage), + "sqlite_file_storage" => Ok(AvailableStores::SqliteFileStorage), _ => Err(String::from("Unknown storage type")), } } @@ -42,15 +47,19 @@ impl AvailableStores { /// # Params /// `config` - Tuser configuration. /// - #[allow(clippy::unused_self)] - pub fn get_storage(&self, config: &TuserConf) -> impl Storage { - file_storage::FileStorage::new(config.clone()) + pub fn get(&self, config: &TuserConf) -> Box<dyn Storage + Send + Sync> { + match self { + Self::FileStorage => Box::new(file_storage::FileStorage::new(config.clone())), + Self::SqliteFileStorage => { + Box::new(sqlite_file_storage::SQLiteFileStorage::new(config.clone())) + } + } } } /// Information about file. /// It has everything about stored file. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, FromRow)] pub struct FileInfo { pub id: String, pub offset: usize, @@ -103,13 +112,13 @@ impl FileInfo { } #[async_trait] -pub trait Storage: Clone { +pub trait Storage { /// Prepare storage before starting up server. /// /// Function to check if configuration is correct /// and prepare storage E.G. create connection pool, /// or directory for files. - async fn prepare(&self) -> TuserResult<()>; + async fn prepare(&mut self) -> TuserResult<()>; /// Get file information. /// diff --git a/src/storages/sqlite_file_storage.rs b/src/storages/sqlite_file_storage.rs new file mode 100644 index 0000000000000000000000000000000000000000..d0039dc514067fa1ab609b216d054409f534b9be --- /dev/null +++ b/src/storages/sqlite_file_storage.rs @@ -0,0 +1,107 @@ +use std::collections::HashMap; + +use actix_files::NamedFile; +use async_std::fs::{DirBuilder, File}; +use async_trait::async_trait; +use log::error; +use sqlx::sqlite::SqlitePoolOptions; +use sqlx::SqlitePool; +use thiserror::private::PathAsDisplay; + +use crate::errors::{TuserError, TuserResult}; +use crate::storages::{FileInfo, Storage}; +use crate::TuserConf; + +#[derive(Clone)] +pub struct SQLiteFileStorage { + app_conf: TuserConf, + pool: Option<SqlitePool>, +} + +impl SQLiteFileStorage { + pub fn new(app_conf: TuserConf) -> SQLiteFileStorage { + SQLiteFileStorage { + app_conf, + pool: None, + } + } + + #[allow(dead_code)] + pub fn get_pool(&self) -> TuserResult<&SqlitePool> { + if let Some(pool) = &self.pool { + Ok(pool) + } else { + error!("Pool doesn't exist."); + Err(TuserError::Unknown) + } + } +} + +#[async_trait] +impl Storage for SQLiteFileStorage { + async fn prepare(&mut self) -> TuserResult<()> { + if !self.app_conf.storage_opts.data.exists() { + DirBuilder::new() + .create(self.app_conf.storage_opts.data.as_path()) + .await + .map_err(|err| TuserError::UnableToPrepareStorage(err.to_string()))?; + } + if !self.app_conf.storage_opts.sqlite_dsn.exists() { + File::create(self.app_conf.storage_opts.sqlite_dsn.clone()).await.map_err(|err| { + TuserError::UnableToPrepareStorage(err.to_string()) + })?; + } + let pool = SqlitePoolOptions::new() + .max_connections(10) + .connect(format!("sqlite://{}", self.app_conf.storage_opts.sqlite_dsn.as_display().to_string()).as_str()) + .await + .map_err(TuserError::from)?; + sqlx::query( + "CREATE TABLE IF NOT EXISTS \ + fileinfo(\ + id VARCHAR(40) PRIMARY KEY, \ + offset UNSIGNED BIG INT NOT NULL DEFAULT 0, \ + length UNSIGNED BIG INT, \ + path TEXT, \ + created_at DATETIME, \ + deferred_size BOOLEAN, \ + metadata TEXT\ + );", + ).execute(&pool).await?; + self.pool = Some(pool); + Ok(()) + } + + async fn get_file_info(&self, _file_id: &str) -> TuserResult<FileInfo> { + todo!() + } + + async fn set_file_info(&self, _file_info: &FileInfo) -> TuserResult<()> { + todo!() + } + + async fn get_contents(&self, _file_id: &str) -> TuserResult<NamedFile> { + todo!() + } + + async fn add_bytes( + &self, + _file_id: &str, + _request_offset: usize, + _bytes: &[u8], + ) -> TuserResult<usize> { + todo!() + } + + async fn create_file( + &self, + _file_size: Option<usize>, + _metadata: Option<HashMap<String, String>>, + ) -> TuserResult<String> { + todo!() + } + + async fn remove_file(&self, _file_id: &str) -> TuserResult<()> { + todo!() + } +}