Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
.idea
|
||||
897
Cargo.lock
generated
Normal file
897
Cargo.lock
generated
Normal file
@@ -0,0 +1,897 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "envman"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "197b85007754a50a296eb8e8c18f5fdf8c042c4476d718094dcc462aab6aaa69"
|
||||
dependencies = [
|
||||
"envman_derive",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "envman_derive"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ad04bc2ce6745a02566c866bb3a37e43b2d6331cdbe6e3b6d6eae9f8b7aa026"
|
||||
dependencies = [
|
||||
"envman_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "envman_derive_internals"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c591cb2983452ece83360a86b7bf46ad5e9230678231fa30c0e9f348e462c337"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.3+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"potential_utf",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"stable_deref_trait",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-core",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "opds"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"envman",
|
||||
"inotify",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.38.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a232e7487fc2ef313d96dde7948e7a3c05101870d8985e4fd8d26aedd27b89"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"rand",
|
||||
"sha1_smol",
|
||||
"uuid-macro-internal",
|
||||
"wasm-bindgen",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid-macro-internal"
|
||||
version = "1.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9384a660318abfbd7f8932c34d67e4d1ec511095f95972ddc01e19d7ba8413f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.3+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "opds"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
url = "2"
|
||||
inotify = "0.11.0"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
envman = "2.0.0"
|
||||
uuid = { version = "1.18.1", features = [
|
||||
"v4",
|
||||
"v7",
|
||||
"v5",
|
||||
"fast-rng",
|
||||
"macro-diagnostics",
|
||||
"zerocopy",
|
||||
"rng",
|
||||
] }
|
||||
quick-xml = { version = "0.38.3", features = ["serialize", "serde"] }
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
36
src/application/application.rs
Normal file
36
src/application/application.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::application::services::books::Books;
|
||||
use crate::application::config::Config;
|
||||
use crate::domain::book::Book;
|
||||
use crate::domain::repository::{BookFilter, Repository};
|
||||
|
||||
pub struct Application<R: Repository<Book, BookFilter>> {
|
||||
pub books: Books<R>,
|
||||
cfg: Config,
|
||||
}
|
||||
|
||||
impl<R: Repository<Book, BookFilter> +'static> Application<R> {
|
||||
pub fn new(repo: R) -> Self {
|
||||
let cfg = Config::new();
|
||||
|
||||
Application {
|
||||
books: Books::new(repo, (&cfg.books_dir).into(), "https://foo.bar/".to_string()),
|
||||
|
||||
cfg
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) -> Result<(), String>{
|
||||
self.books.add_books_from_path();
|
||||
|
||||
if self.cfg.watcher {
|
||||
return match self.books.watch_dir() {
|
||||
Ok(_) => {Ok(())}
|
||||
Err(e) => {
|
||||
Err(format!("Error start watching books: {}", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
15
src/application/config.rs
Normal file
15
src/application/config.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use envman::EnvMan;
|
||||
|
||||
#[derive(EnvMan)]
|
||||
pub struct Config {
|
||||
#[envman(default = "./")]
|
||||
pub books_dir: String,
|
||||
#[envman(default = "true")]
|
||||
pub watcher: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
Config::load_from_env().expect("Failed to load configuration")
|
||||
}
|
||||
}
|
||||
75
src/application/loaders/fs.rs
Normal file
75
src/application/loaders/fs.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::application::parsers;
|
||||
use crate::domain::book::Book;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct Loader {
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
pub struct LoaderIter {
|
||||
queue: VecDeque<PathBuf>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
pub fn new(root: PathBuf) -> Self {
|
||||
Loader { root }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Loader {
|
||||
type Item = Book;
|
||||
type IntoIter = LoaderIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(self.root);
|
||||
LoaderIter { queue }
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for LoaderIter {
|
||||
type Item = Book;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(path) = self.queue.pop_front() {
|
||||
match path.is_dir() {
|
||||
true => {
|
||||
if let Ok(entries) = fs::read_dir(&path) {
|
||||
for entry in entries.flatten() {
|
||||
self.queue.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
false => {
|
||||
let book = Self::parse_path(&path);
|
||||
if book.is_some() {
|
||||
return book;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl LoaderIter {
|
||||
fn parse_path(path: &PathBuf) -> Option<Book> {
|
||||
match parsers::parse(&path) {
|
||||
Ok(book) => return Some(book),
|
||||
Err(err) => {
|
||||
match err {
|
||||
parsers::Error::ParseError(err) => {
|
||||
println!("Failed to load book at {}: {:?}", path.display(), err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/application/loaders/inotify.rs
Normal file
81
src/application/loaders/inotify.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use crate::application::parsers;
|
||||
use crate::domain::book::Book;
|
||||
use inotify::{Event, Inotify, WatchMask};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
|
||||
pub struct Loader {
|
||||
inotify: Inotify,
|
||||
}
|
||||
|
||||
pub struct LoaderIter<'a> {
|
||||
inotify: &'a mut Inotify,
|
||||
buf: Vec<u8>,
|
||||
queue: VecDeque<Book>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
pub fn new(root: PathBuf) -> io::Result<Self> {
|
||||
let mask = WatchMask::MODIFY | WatchMask::CREATE;
|
||||
let inotify = Inotify::init()?;
|
||||
|
||||
inotify.watches().add(&root, mask)?;
|
||||
Ok(Loader { inotify })
|
||||
}
|
||||
|
||||
pub fn iter(&mut self) -> LoaderIter<'_> {
|
||||
LoaderIter {
|
||||
inotify: &mut self.inotify,
|
||||
buf: vec![0u8; BUFFER_SIZE],
|
||||
queue: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LoaderIter<'a> {
|
||||
type Item = Book;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(book) = self.queue.pop_front() {
|
||||
return Some(book);
|
||||
}
|
||||
|
||||
match self.inotify.read_events_blocking(&mut self.buf) {
|
||||
Ok(events) => {
|
||||
for ev in events {
|
||||
println!("{:?}", ev);
|
||||
if let Some(book) = Self::process_event(ev) {
|
||||
println!("{}", book);
|
||||
self.queue.push_back(book);
|
||||
}
|
||||
}
|
||||
self.queue.pop_front()
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("inotify error: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LoaderIter<'a> {
|
||||
fn process_event(event: Event<&OsStr>) -> Option<Book> {
|
||||
let path = PathBuf::from(event.name?);
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match parsers::parse(&path) {
|
||||
Ok(book) => Some(book),
|
||||
Err(err) => {
|
||||
eprintln!("Failed to parse book from {:?}: {:?}", path, err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/application/loaders/mod.rs
Normal file
2
src/application/loaders/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod fs;
|
||||
pub mod inotify;
|
||||
5
src/application/mod.rs
Normal file
5
src/application/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod services;
|
||||
mod loaders;
|
||||
mod parsers;
|
||||
mod config;
|
||||
pub mod application;
|
||||
16
src/application/parsers/mod.rs
Normal file
16
src/application/parsers/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use crate::domain::book::Book;
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod rs;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NotSupported,
|
||||
ParseError(String),
|
||||
}
|
||||
pub fn parse(path: &PathBuf) -> Result<Book, Error> {
|
||||
match path.extension().and_then(|s| s.to_str()) {
|
||||
Some("rs") => rs::parse(path).map_err(Error::ParseError),
|
||||
Some(_) | None => Err(Error::NotSupported),
|
||||
}
|
||||
}
|
||||
15
src/application/parsers/rs.rs
Normal file
15
src/application/parsers/rs.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use crate::domain::author::Author;
|
||||
use crate::domain::book::Book;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn parse(path: &PathBuf) -> Result<Book, String> {
|
||||
let mut book = Book::new();
|
||||
|
||||
book.title = path.to_string_lossy().to_string();
|
||||
|
||||
let mut author = Author::new();
|
||||
author.first_name = path.extension().unwrap().to_string_lossy().to_string();
|
||||
book.author.push(author);
|
||||
|
||||
return Ok(book);
|
||||
}
|
||||
87
src/application/services/books.rs
Normal file
87
src/application/services/books.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use crate::application::loaders::fs;
|
||||
use crate::application::loaders::inotify;
|
||||
use crate::domain::book::Book;
|
||||
use crate::domain::feed::{BooksFeed, Entry};
|
||||
use crate::domain::repository::{BookFilter, Repository};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{io, thread};
|
||||
use url::Url;
|
||||
|
||||
const AUTHOR_URL_PREFIX: &str = "author";
|
||||
|
||||
pub struct Books<R: Repository<Book, BookFilter>> {
|
||||
pub repo: Arc<Mutex<R>>,
|
||||
root: PathBuf,
|
||||
base_url: Url,
|
||||
}
|
||||
|
||||
impl<R: Repository<Book, BookFilter>> Books<R>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
pub fn new(repo: R, root: PathBuf, base_url: String) -> Self {
|
||||
Books {
|
||||
repo: Arc::new(Mutex::new(repo)),
|
||||
root,
|
||||
base_url: Url::parse(&base_url).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn book_entries(&self, filter: BookFilter) -> Vec<Entry> {
|
||||
let mut res = self
|
||||
.repo
|
||||
.lock()
|
||||
.unwrap()
|
||||
.filter(filter)
|
||||
.map(|book| Entry::from(&book))
|
||||
.collect::<Vec<Entry>>();
|
||||
|
||||
for entry in &mut res {
|
||||
for author in &mut entry.author {
|
||||
author.url = self.build_author_url(author.url.as_str()).to_string();
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn build_author_url(&self, author_url: &str) -> Url {
|
||||
let mut url = self.base_url.clone();
|
||||
|
||||
match url.join([AUTHOR_URL_PREFIX, author_url].join("/").as_str()) {
|
||||
Ok(u) => url = u,
|
||||
Err(err) => {
|
||||
println!("{}", err);
|
||||
}
|
||||
}
|
||||
url
|
||||
}
|
||||
|
||||
pub fn books_feed(&self, filter: BookFilter) -> BooksFeed {
|
||||
let mut feed: BooksFeed = Default::default();
|
||||
|
||||
feed.entry = self.book_entries(filter);
|
||||
|
||||
feed
|
||||
}
|
||||
|
||||
pub fn add_books_from_path(&mut self) {
|
||||
let iter = fs::Loader::new(PathBuf::from(&self.root));
|
||||
self.repo.lock().unwrap().bulk_add(iter);
|
||||
}
|
||||
|
||||
pub fn watch_dir(&mut self) -> Result<(), io::Error> {
|
||||
let root = self.root.clone();
|
||||
let repo = Arc::clone(&self.repo);
|
||||
let mut loader = inotify::Loader::new(root.clone())?;
|
||||
|
||||
thread::spawn(move || loop {
|
||||
for book in loader.iter() {
|
||||
repo.lock().unwrap().add(book);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
src/application/services/mod.rs
Normal file
1
src/application/services/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod books;
|
||||
85
src/domain/author.rs
Normal file
85
src/domain/author.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Author {
|
||||
pub id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: Option<String>,
|
||||
pub middle_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Author {
|
||||
pub fn new() -> Author {
|
||||
Author{
|
||||
id: Uuid::new_v4(),
|
||||
first_name: "".to_string(),
|
||||
last_name: None,
|
||||
middle_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name_contains(&self, name: &str) -> bool {
|
||||
let name = name.to_lowercase();
|
||||
let first_name = self.first_name.to_lowercase();
|
||||
let last_name = self.last_name.as_ref().map(|s| s.to_lowercase());
|
||||
let middle_name = self.middle_name.as_ref().map(|s| s.to_lowercase());
|
||||
|
||||
name.contains(&first_name) ||
|
||||
last_name.map_or(false, |s| s.contains(&name)) ||
|
||||
middle_name.map_or(false, |s| s.contains(&name))
|
||||
}
|
||||
|
||||
pub fn uniq_id(&self) -> Uuid {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
if let Some(ref last) = self.last_name {
|
||||
if !last.is_empty() {
|
||||
parts.push(last.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if !self.first_name.is_empty() {
|
||||
parts.push(self.first_name.as_str());
|
||||
}
|
||||
|
||||
if let Some(ref middle) = self.middle_name {
|
||||
if !middle.is_empty() {
|
||||
parts.push(middle.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
Uuid::new_v5(&Uuid::NAMESPACE_URL, parts.join(" ").as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Author {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Author {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
if let Some(ref last) = self.last_name {
|
||||
if !last.is_empty() {
|
||||
parts.push(last.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
if !self.first_name.is_empty() {
|
||||
parts.push(self.first_name.as_str());
|
||||
}
|
||||
|
||||
if let Some(ref middle) = self.middle_name {
|
||||
if !middle.is_empty() {
|
||||
parts.push(middle.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, "{}", parts.join(" "))
|
||||
}
|
||||
}
|
||||
|
||||
65
src/domain/book.rs
Normal file
65
src/domain/book.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use crate::domain::author;
|
||||
use std::fmt;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct Book {
|
||||
pub id: Uuid,
|
||||
pub title: String,
|
||||
pub author: Vec<author::Author>,
|
||||
pub language: String,
|
||||
pub description: String,
|
||||
pub tags: Vec<String>,
|
||||
pub published_at: String,
|
||||
pub publisher: String,
|
||||
pub updated: String,
|
||||
}
|
||||
|
||||
impl Book {
|
||||
pub fn new() -> Book {
|
||||
Book {
|
||||
id: Uuid::new_v4(),
|
||||
title: "".to_string(),
|
||||
author: Default::default(),
|
||||
language: "".to_string(),
|
||||
description: "".to_string(),
|
||||
tags: vec![],
|
||||
published_at: "".to_string(),
|
||||
publisher: "".to_string(),
|
||||
updated: "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uniq_id(&self) -> Uuid {
|
||||
let mut parts = Vec::new();
|
||||
parts.push(self.title.as_str());
|
||||
|
||||
let authors = self.author.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(";");
|
||||
parts.push(authors.as_str());
|
||||
|
||||
parts.push(self.language.as_str());
|
||||
parts.push(self.publisher.as_str());
|
||||
parts.push(self.published_at.as_str());
|
||||
|
||||
Uuid::new_v5(&Uuid::NAMESPACE_OID, parts.join("-").as_bytes())
|
||||
}
|
||||
|
||||
pub fn same(&self, other: &Book) -> bool {
|
||||
self.uniq_id() == other.uniq_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Book {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
|
||||
let authors = self.author.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(";");
|
||||
|
||||
write!(f, "{} by {}", self.title, authors)
|
||||
}
|
||||
}
|
||||
|
||||
101
src/domain/feed.rs
Normal file
101
src/domain/feed.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::domain::author;
|
||||
use crate::domain::book::Book;
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename = "feed")]
|
||||
pub struct BooksFeed {
|
||||
#[serde(rename = "@xmlns", default = "default_atom_ns")]
|
||||
pub xmlns: String,
|
||||
|
||||
#[serde(rename = "@xmlns:dc", default = "default_dc_ns")]
|
||||
pub xmlns_dc: String,
|
||||
|
||||
pub id: String,
|
||||
pub title: String,
|
||||
pub updated: String,
|
||||
#[serde(default)]
|
||||
pub author: Option<Author>,
|
||||
#[serde(default)]
|
||||
pub entry: Vec<Entry>,
|
||||
#[serde(default)]
|
||||
pub link: Vec<Link>,
|
||||
}
|
||||
|
||||
impl Default for BooksFeed {
|
||||
fn default() -> Self {
|
||||
BooksFeed {
|
||||
xmlns: default_atom_ns(),
|
||||
xmlns_dc: default_dc_ns(),
|
||||
id: Default::default(),
|
||||
title: Default::default(),
|
||||
updated: Default::default(),
|
||||
author: Default::default(),
|
||||
entry: Default::default(),
|
||||
link: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_atom_ns() -> String {
|
||||
"http://www.w3.org/2005/Atom".to_string()
|
||||
}
|
||||
fn default_dc_ns() -> String {
|
||||
"http://purl.org/dc/terms/".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Author {
|
||||
pub name: String,
|
||||
pub url: String
|
||||
}
|
||||
|
||||
impl From<author::Author> for Author {
|
||||
fn from(value: author::Author) -> Self {
|
||||
Author {
|
||||
name: value.to_string(),
|
||||
url: value.id.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Link {
|
||||
#[serde(rename = "rel", default)]
|
||||
pub rel: Option<String>,
|
||||
#[serde(rename = "href")]
|
||||
pub href: String,
|
||||
#[serde(rename = "type", default)]
|
||||
pub media_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename = "entry")]
|
||||
pub struct Entry {
|
||||
pub title: String,
|
||||
pub id: String,
|
||||
pub updated: String,
|
||||
pub author: Vec<Author>,
|
||||
#[serde(rename = "dc:language", default)]
|
||||
pub language: Option<String>,
|
||||
#[serde(rename = "dc:issued", default)]
|
||||
pub issued: Option<String>,
|
||||
#[serde(default)]
|
||||
pub summary: Option<String>,
|
||||
#[serde(default)]
|
||||
pub link: Vec<Link>, // acquisition, cover, etc.
|
||||
}
|
||||
|
||||
impl From<&Book> for Entry {
|
||||
fn from(book: &Book) -> Self {
|
||||
Entry{
|
||||
title: book.title.clone(),
|
||||
id: book.id.to_string().clone(),
|
||||
updated: book.updated.clone(),
|
||||
author: book.author.clone().into_iter().map(|a| a.into()).collect(),
|
||||
language: (!book.language.is_empty()).then(|| book.language.clone()),
|
||||
issued: (!book.published_at.is_empty()).then(|| book.published_at.clone()),
|
||||
summary: (!book.description.is_empty()).then(|| book.description.clone()),
|
||||
link: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/domain/mod.rs
Normal file
4
src/domain/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod author;
|
||||
pub mod book;
|
||||
pub mod feed;
|
||||
pub mod repository;
|
||||
24
src/domain/repository.rs
Normal file
24
src/domain/repository.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
pub trait Repository<T, F>: Send + Sync + Sized{
|
||||
fn add(&mut self, item: T);
|
||||
fn bulk_add<I>(&mut self, items: I) where I: IntoIterator<Item = T>;
|
||||
fn remove(&mut self, item: T);
|
||||
fn get(&self, id: String) -> Option<T>;
|
||||
fn update(&mut self, item: T);
|
||||
fn filter(&self, filter: F) -> Box<dyn Iterator<Item = T>>;
|
||||
}
|
||||
|
||||
pub struct BookFilter {
|
||||
pub author: Option<AuthorFilter>,
|
||||
pub title: Option<String>,
|
||||
pub language: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub published_at: Option<String>,
|
||||
pub publisher: Option<String>,
|
||||
pub updated: Option<String>,
|
||||
}
|
||||
|
||||
pub struct AuthorFilter {
|
||||
pub id: Option<String>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
1
src/infrastructure/mod.rs
Normal file
1
src/infrastructure/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod repository;
|
||||
251
src/infrastructure/repository/inmem/books.rs
Normal file
251
src/infrastructure/repository/inmem/books.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
use crate::domain::author::Author;
|
||||
use crate::domain::repository::{BookFilter, Repository};
|
||||
use crate::domain::{author, book};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Book {
|
||||
id: Uuid,
|
||||
title: String,
|
||||
author: Vec<String>,
|
||||
language: String,
|
||||
description: String,
|
||||
tags: Vec<String>,
|
||||
published_at: String,
|
||||
publisher: String,
|
||||
updated: String,
|
||||
}
|
||||
|
||||
impl From<book::Book> for Book {
|
||||
fn from(book: book::Book) -> Self {
|
||||
Book {
|
||||
id: book.id,
|
||||
title: book.title,
|
||||
author: book.author.iter().map(|a| a.id.to_string()).collect(),
|
||||
language: book.language,
|
||||
description: book.description,
|
||||
tags: book.tags,
|
||||
published_at: book.published_at,
|
||||
publisher: book.publisher,
|
||||
updated: book.updated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<book::Book> for Book {
|
||||
fn into(self) -> book::Book {
|
||||
book::Book {
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
author: self
|
||||
.author
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let mut na: author::Author = Default::default();
|
||||
if let Ok(id) = Uuid::parse_str(a) {
|
||||
na.id = id;
|
||||
}
|
||||
na
|
||||
})
|
||||
.collect(),
|
||||
language: self.language,
|
||||
description: self.description,
|
||||
tags: self.tags,
|
||||
published_at: self.published_at,
|
||||
publisher: self.publisher,
|
||||
updated: self.updated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BookRepository {
|
||||
books: Vec<Book>,
|
||||
authors: HashMap<Uuid, Author>,
|
||||
author_uniques: HashMap<String, Uuid>,
|
||||
}
|
||||
|
||||
impl BookRepository {
|
||||
pub fn new() -> Self {
|
||||
BookRepository {
|
||||
books: vec![],
|
||||
authors: HashMap::new(),
|
||||
author_uniques: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn populate_authors(&self, book: &mut book::Book) {
|
||||
for a in &mut book.author {
|
||||
if let Some(stored) = self.authors.get(&a.id) {
|
||||
a.first_name = stored.first_name.clone();
|
||||
a.last_name = stored.last_name.clone();
|
||||
a.middle_name = stored.middle_name.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_authors(&mut self, item: &mut book::Book) {
|
||||
for a in item.author.iter_mut() {
|
||||
let uniq = a.uniq_id().to_string();
|
||||
|
||||
if let Some(&id) = self.author_uniques.get(&uniq) {
|
||||
a.id = id;
|
||||
} else {
|
||||
self.author_uniques.insert(uniq, a.id);
|
||||
}
|
||||
|
||||
self.authors.insert(a.id, a.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Repository<book::Book, BookFilter> for BookRepository {
|
||||
fn add(&mut self, mut item: book::Book) {
|
||||
if self.get(item.id.to_string()).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.extract_authors(&mut item);
|
||||
|
||||
self.books.push(item.into());
|
||||
}
|
||||
|
||||
fn bulk_add<I>(&mut self, items: I)
|
||||
where
|
||||
I: IntoIterator<Item = book::Book>,
|
||||
{
|
||||
items.into_iter().for_each(|item| {
|
||||
if self.get(item.id.to_string()).is_none() {
|
||||
self.add(item)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn remove(&mut self, item: book::Book) {
|
||||
self.books.retain(|book| book.id != item.id);
|
||||
}
|
||||
|
||||
fn get(&self, id: String) -> Option<book::Book> {
|
||||
let id_uuid: Uuid = id.parse().unwrap();
|
||||
|
||||
let mut book: Option<book::Book> = self
|
||||
.books
|
||||
.iter()
|
||||
.cloned()
|
||||
.find(|x| x.id.eq(&id_uuid))
|
||||
.and_then(|x| Some(x.into()));
|
||||
|
||||
if book.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(book) = book.as_mut() {
|
||||
self.populate_authors(book);
|
||||
}
|
||||
|
||||
book
|
||||
}
|
||||
|
||||
fn update(&mut self, mut item: book::Book) {
|
||||
self.extract_authors(&mut item);
|
||||
|
||||
for res in &mut self.books {
|
||||
if res.id == item.id {
|
||||
*res = item.into();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn filter(&self, f: BookFilter) -> Box<dyn Iterator<Item = book::Book>> {
|
||||
let mut author_ids: Vec<String> = vec![];
|
||||
|
||||
if let Some(author) = f.author {
|
||||
if let Some(id) = author.id {
|
||||
author_ids.push(id);
|
||||
}
|
||||
|
||||
if let Some(name) = author.name {
|
||||
for (id, author) in self.authors.iter() {
|
||||
if author.first_name.contains(&name)
|
||||
|| (author.last_name.is_some()
|
||||
&& author.clone().last_name.unwrap().contains(&name))
|
||||
|| (author.middle_name.is_some()
|
||||
&& author.clone().middle_name.unwrap().contains(&name))
|
||||
{
|
||||
author_ids.push(id.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if author_ids.is_empty() {
|
||||
return Box::new(std::iter::empty::<book::Book>())
|
||||
}
|
||||
|
||||
let mut res = self
|
||||
.books
|
||||
.iter()
|
||||
.filter(move |book| {
|
||||
// Фильтр по названию
|
||||
if let Some(ref search_title) = f.title {
|
||||
if !book.title.contains(search_title) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Фильтр по описанию
|
||||
if let Some(ref descr) = f.description {
|
||||
if !book.description.contains(descr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Фильтр по языку
|
||||
if let Some(ref lang) = f.language {
|
||||
if !book.language.eq(lang) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Фильтр по тегам
|
||||
if let Some(ref tags) = f.tags {
|
||||
if !tags.iter().all(|tag| book.tags.contains(tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Фильтр по издателю
|
||||
if let Some(ref publisher) = f.publisher {
|
||||
if !book.publisher.eq(publisher) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Фильтр по датам (пример, можно доработать)
|
||||
if let Some(ref published_at) = f.published_at {
|
||||
if book.published_at != *published_at {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if let Some(ref updated) = f.updated {
|
||||
if book.updated != *updated {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if !author_ids.is_empty() {
|
||||
if !book.author.iter().all(|x| author_ids.contains(x)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<book::Book>>();
|
||||
|
||||
for book in &mut res {
|
||||
self.populate_authors(book);
|
||||
}
|
||||
|
||||
Box::new(res.into_iter())
|
||||
}
|
||||
}
|
||||
1
src/infrastructure/repository/inmem/mod.rs
Normal file
1
src/infrastructure/repository/inmem/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod books;
|
||||
1
src/infrastructure/repository/mod.rs
Normal file
1
src/infrastructure/repository/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod inmem;
|
||||
14
src/lib.rs
Normal file
14
src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::application::application::Application;
|
||||
use crate::infrastructure::repository::inmem::books::BookRepository;
|
||||
|
||||
pub mod domain;
|
||||
|
||||
mod application;
|
||||
pub mod infrastructure;
|
||||
|
||||
pub fn demo() -> Application<BookRepository> {
|
||||
let mut app = Application::new(BookRepository::new());
|
||||
app.start().expect("Application initialization failed");
|
||||
|
||||
app
|
||||
}
|
||||
46
src/main.rs
Normal file
46
src/main.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use opds::demo;
|
||||
use opds::domain::repository::{AuthorFilter, Repository};
|
||||
use opds::domain::repository::BookFilter;
|
||||
use quick_xml::se::to_string as to_xml_string;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let app = demo();
|
||||
|
||||
let filter = BookFilter {
|
||||
author: Some(AuthorFilter{
|
||||
id: None,
|
||||
name: Some("rs".to_string()),
|
||||
}),
|
||||
title: Some("service".to_string()),
|
||||
language: None,
|
||||
description: None,
|
||||
tags: None,
|
||||
published_at: None,
|
||||
publisher: None,
|
||||
updated: None,
|
||||
};
|
||||
|
||||
let res = app.books.books_feed(filter);
|
||||
println!("{}", to_xml_string(&res).unwrap());
|
||||
|
||||
if let Some(book) = res.entry.iter().next() {
|
||||
let book = app.books.repo.lock().unwrap().get(book.id.to_string().clone());
|
||||
println!("{:?}", book.unwrap().author);
|
||||
}
|
||||
|
||||
sleep(Duration::new(10, 0));
|
||||
|
||||
let filter = BookFilter {
|
||||
author: None,
|
||||
title: Some("foo".to_string()),
|
||||
language: None,
|
||||
description: None,
|
||||
tags: None,
|
||||
published_at: None,
|
||||
publisher: None,
|
||||
updated: None,
|
||||
};
|
||||
println!("{}", to_xml_string(&app.books.books_feed(filter)).unwrap());
|
||||
}
|
||||
Reference in New Issue
Block a user