Replaced hand-written SQL with diesel

This commit is contained in:
Antoine Gersant 2017-06-25 18:36:54 -07:00
parent 0135538156
commit 378c2015b8
19 changed files with 890 additions and 830 deletions

2
.gitignore vendored
View file

@ -1,7 +1,7 @@
target
release
*.res
*.sqlite
test/*
*.sqlite-journal
*.sqlite-wal
*.sqlite-shm

199
Cargo.lock generated
View file

@ -4,6 +4,8 @@ version = "0.5.1"
dependencies = [
"ape 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"app_dirs 1.1.1 (git+https://github.com/agersant/app-dirs-rs)",
"diesel 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel_codegen 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.11 (registry+https://github.com/rust-lang/crates.io-index)",
@ -18,12 +20,12 @@ dependencies = [
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"router 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rusqlite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"secure-session 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
"shell32-sys 0.1.1 (git+https://github.com/retep998/winapi-rs?branch=0.2)",
"sqlite 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)",
"staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -56,6 +58,14 @@ dependencies = [
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "aho-corasick"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "antidote"
version = "1.0.0"
@ -249,6 +259,45 @@ name = "deque"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "diesel"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "diesel_codegen"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"diesel 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"diesel_infer_schema 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"dotenv 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "diesel_infer_schema"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"diesel 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dotenv"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "dtoa"
version = "0.2.2"
@ -548,11 +597,34 @@ dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libsqlite3-sys"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "linked-hash-map"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lru-cache"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lzw"
version = "0.10.0"
@ -571,6 +643,14 @@ dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "memchr"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "metaflac"
version = "0.1.6"
@ -898,11 +978,28 @@ dependencies = [
"utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "regex-syntax"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "reqwest"
version = "0.6.2"
@ -945,6 +1042,17 @@ dependencies = [
"url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rusqlite"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rust-crypto"
version = "0.2.36"
@ -1151,33 +1259,6 @@ name = "siphasher"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "sqlite"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sqlite3-src"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "sqlite3-sys"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"sqlite3-src 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "staticfile"
version = "0.4.0"
@ -1224,6 +1305,15 @@ dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread-id"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.2.7"
@ -1232,6 +1322,15 @@ dependencies = [
"thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "time"
version = "0.1.37"
@ -1303,6 +1402,14 @@ dependencies = [
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unreachable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unsafe-any"
version = "0.4.2"
@ -1359,16 +1466,31 @@ name = "utf8-ranges"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "utf8-ranges"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "uuid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vcpkg"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
@ -1398,6 +1520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum adler32 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff33fe13a08dbce05bcefa2c68eea4844941437e33d6f808240b54d7157b9cd"
"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a"
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
"checksum ape 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b419c2e36e91776200588f91e24c970d16d34236369136ca819f12dd903c5691"
"checksum app_dirs 1.1.1 (git+https://github.com/agersant/app-dirs-rs)" = "<none>"
@ -1423,6 +1546,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
"checksum deflate 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "64f26d06f2d9b076958a5ab5c9c700d14c88bbd689588df9d58ea314afa24179"
"checksum deque 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a694dae478589798d752c7125542f8a5ae8b6e59476172baf2eed67357bdfa27"
"checksum diesel 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "90edf3024e90c3bf92ff71c6e9e809648b0e482a653dc006d5639fdc40cd78a3"
"checksum diesel_codegen 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb4ee459a5b4a5c7dfd08c573cfa8d922539bcbe0515a8feea0a5d22606f50cb"
"checksum diesel_infer_schema 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c72a50b59e12010dd3cf83d8cd0e6e08cfa60fc2c84d38a70df4e730c8cc1c0c"
"checksum dotenv 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "400b347fe65ccfbd8f545c9d9a75d04b0caf23fec49aaa838a9a05398f94c019"
"checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d"
"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
@ -1459,10 +1586,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lewton 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c1b7b81410f7895d4793bae921cc62317c5500c6ef211c9c24cad778eda77c20"
"checksum libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e7eb6b826bfc1fdea7935d46556250d1799b7fe2d9f7951071f4291710665e3e"
"checksum libflate 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "591fb1342cbc9be24883f22f97bd7ede01dd1f3821833c4ff07435c7270cc1f6"
"checksum libsqlite3-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "370090ad578ba845a3ad4f383ceb3deba7abd51ab1915ad1f2c982cc6035e31c"
"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939"
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21"
"checksum lzw 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084"
"checksum matches 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efd7622e3022e1a6eaa602c4cea8912254e5582c9c692e9167714182244801b1"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
"checksum metaflac 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8486a21ab0211808949ba6910d267bc3c8a61d877940d2acfcbab3814f83f5"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum mime_guess 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76da6df85047af8c0edfa53f48eb1073012ce1cc95c8fedc0a374f659a89dd65"
@ -1498,11 +1629,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum rayon-core 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd1e76f8ee0322fbbeb0c43a07e1757fcf8ff06bb0ff92da017625882ddc04dd"
"checksum redox_syscall 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "3041aeb6000db123d2c9c751433f526e1f404b23213bd733167ab770c3989b4d"
"checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
"checksum reqwest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1d56dbe269dbe19d716b76ec8c3efce8ef84e974f5b7e5527463e8c0507d4e17"
"checksum ring 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)" = "24293de46bac74c9b9c05b40ff8496bbc8b9ae242a9b89f754e1154a43bc7c4c"
"checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256"
"checksum router 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9b1797ff166029cb632237bb5542696e54961b4cf75a324c6f05c9cf0584e4e"
"checksum rusqlite 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28df6ae60019445d95d7a5be417ae16923c156d90f7051727fe0a15eaa70fae0"
"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
"checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
@ -1527,15 +1661,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum shell32-sys 0.1.1 (git+https://github.com/retep998/winapi-rs?branch=0.2)" = "<none>"
"checksum shell32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "72f20b8f3c060374edb8046591ba28f62448c369ccbdc7b02075103fb3a9e38d"
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
"checksum sqlite 0.23.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a3cd27f80d9229b15313403f73d9180f2368f181a870be51f07e8f7f41d7a264"
"checksum sqlite3-src 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52f02ac7a64c3446581ee43758075d9f1cdced596d70ffe28bf02979a719dbed"
"checksum sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71fec807a1534bd13eeaaec396175d67c79bdc68df55e18a452726ec62a8fb08"
"checksum staticfile 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31493480e073d52522a94cdf56269dd8eb05f99549effd1826b0271690608878"
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
"checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773"
"checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
"checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3"
"checksum toml 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd86ad9ebee246fdedd610e0f6d0587b754a3d81438db930a244d0480ed7878f"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
@ -1546,6 +1679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-normalization 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e28fa37426fceeb5cf8f41ee273faa7c82c47dc8fba5853402841e665fcd86ff"
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
"checksum unix-daemonize 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "531faed80732acaa13d1016c66d6a9180b5045c4fcef8daa20bb2baf46b13907"
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
"checksum untrusted 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b65243989ef6aacd9c0d6bd2b822765c3361d8ed352185a6f3a41f3a718c673"
"checksum url 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2ba3456fbe5c0098cb877cf08b92b76c3e18e0be9e47c35b487220d377d24e"
@ -1553,8 +1687,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum user32-sys 0.2.0 (git+https://github.com/retep998/winapi-rs?branch=0.2)" = "<none>"
"checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
"checksum uuid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d0f5103675a280a926ec2f9b7bcc2ef49367df54e8c570c3311fec919f9a8b"
"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b"
"checksum version_check 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2bb3950bf29e36796dea723df1747619dd331881aefef75b7cf1c58fdd738afe"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.2.8 (git+https://github.com/retep998/winapi-rs?branch=0.2)" = "<none>"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi-build 0.1.1 (git+https://github.com/retep998/winapi-rs?branch=0.2)" = "<none>"

View file

@ -9,6 +9,8 @@ ui = []
[dependencies]
ape = "0.1.2"
app_dirs = { git = "https://github.com/agersant/app-dirs-rs" }
diesel = { version = "0.13.0", features = ["sqlite"] }
diesel_codegen = { version = "0.13.0", features = ["sqlite"] }
error-chain = "0.10.0"
getopts = "0.2.14"
hyper = "0.10.0"
@ -26,12 +28,15 @@ secure-session = "0.1.0"
serde = "0.9"
serde_derive = "0.9"
serde_json = "0.9"
sqlite = "0.23.0"
staticfile = "0.4.0"
toml = "=0.3.2"
typemap = "0.3"
url = "1.2.0"
[dependencies.rusqlite]
version = "0.11.0"
features = ["bundled"]
[target.'cfg(windows)'.dependencies]
winapi = { git = "https://github.com/retep998/winapi-rs", branch="0.2" }
kernel32-sys = { git = "https://github.com/retep998/winapi-rs", branch="0.2" }

BIN
dev.sqlite Normal file

Binary file not shown.

View file

@ -5,7 +5,7 @@ use std::sync::Arc;
use config::Config;
use errors::*;
use index::*;
use db::*;
use vfs::*;
@ -49,11 +49,11 @@ impl Collection {
self.index.deref().flatten(virtual_path)
}
pub fn get_random_albums(&self, count: u32) -> Result<Vec<Directory>> {
pub fn get_random_albums(&self, count: i64) -> Result<Vec<Directory>> {
self.index.deref().get_random_albums(count)
}
pub fn get_recent_albums(&self, count: u32) -> Result<Vec<Directory>> {
pub fn get_recent_albums(&self, count: i64) -> Result<Vec<Directory>> {
self.index.deref().get_recent_albums(count)
}

View file

@ -7,7 +7,7 @@ use toml;
use collection::User;
use ddns::DDNSConfig;
use errors::*;
use index::IndexConfig;
use db::IndexConfig;
use utils;
use vfs::VfsConfig;

613
src/db/index.rs Normal file
View file

@ -0,0 +1,613 @@
use core::ops::Deref;
use diesel;
use diesel::expression::sql;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use diesel::types;
use regex::Regex;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time;
use db::models::*;
use db::schema::{directories, songs};
use errors::*;
use metadata;
use vfs::Vfs;
#[allow(dead_code)] const DB_MIGRATIONS_PATH: &'static str = "src/db/migrations";
embed_migrations!("src/db/migrations");
const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 1000; // Insertions in each transaction
pub struct IndexConfig {
pub album_art_pattern: Option<Regex>,
pub sleep_duration: u64, // in seconds
pub path: PathBuf,
}
impl IndexConfig {
pub fn new() -> IndexConfig {
IndexConfig {
sleep_duration: 60 * 30, // 30 minutes
album_art_pattern: None,
path: Path::new(":memory:").to_path_buf(),
}
}
}
pub struct Index {
vfs: Arc<Vfs>,
db: Mutex<SqliteConnection>,
album_art_pattern: Option<Regex>,
sleep_duration: u64,
}
#[derive(Debug, Insertable)]
#[table_name="songs"]
struct NewSong {
path: String,
parent: String,
track_number: Option<i32>,
disc_number: Option<i32>,
title: Option<String>,
artist: Option<String>,
album_artist: Option<String>,
year: Option<i32>,
album: Option<String>,
artwork: Option<String>,
}
#[derive(Debug, Insertable)]
#[table_name="directories"]
struct NewDirectory {
path: String,
parent: Option<String>,
artist: Option<String>,
year: Option<i32>,
album: Option<String>,
artwork: Option<String>,
date_added: i32,
}
struct IndexBuilder<'db> {
new_songs: Vec<NewSong>,
new_directories: Vec<NewDirectory>,
db: &'db Mutex<SqliteConnection>,
}
impl<'db> IndexBuilder<'db> {
fn new(db: &Mutex<SqliteConnection>) -> Result<IndexBuilder> {
let mut new_songs = Vec::new();
let mut new_directories = Vec::new();
new_songs.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
new_directories.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
Ok(IndexBuilder {
new_songs: new_songs,
new_directories: new_directories,
db: db,
})
}
fn flush_songs(&mut self) -> Result<()> {
let db = self.db.lock().unwrap();
let db = db.deref();
db.transaction::<_, Error, _>(|| {
diesel::insert(&self.new_songs).into(songs::table).execute(db)?;
Ok(())
})?;
self.new_songs.clear();
Ok(())
}
fn flush_directories(&mut self) -> Result<()> {
let db = self.db.lock().unwrap();
let db = db.deref();
db.transaction::<_, Error, _>(|| {
diesel::insert(&self.new_directories).into(directories::table).execute(db)?;
Ok(())
})?;
self.new_directories.clear();
Ok(())
}
fn push_song(&mut self, song: NewSong) -> Result<()> {
if self.new_songs.len() >= self.new_songs.capacity() {
self.flush_songs()?;
}
self.new_songs.push(song);
Ok(())
}
fn push_directory(&mut self, directory: NewDirectory) -> Result<()> {
if self.new_directories.len() >= self.new_directories.capacity() {
self.flush_directories()?;
}
self.new_directories.push(directory);
Ok(())
}
}
impl Index {
pub fn new(vfs: Arc<Vfs>, config: &IndexConfig) -> Result<Index> {
let path = &config.path;
println!("Index file path: {}", path.to_string_lossy());
let db = Mutex::new(SqliteConnection::establish(&path.to_string_lossy())?);
let index = Index {
vfs: vfs,
db: db,
album_art_pattern: config.album_art_pattern.clone(),
sleep_duration: config.sleep_duration,
};
index.init()?;
Ok(index)
}
fn init(&self) -> Result<()> {
{
let db = self.db.lock().unwrap();
db.execute("PRAGMA synchronous = NORMAL")?;
}
self.migrate_up()?;
Ok(())
}
#[allow(dead_code)]
fn migrate_down(&self) -> Result<()> {
let db = self.db.lock().unwrap();
let db = db.deref();
loop {
match diesel::migrations::revert_latest_migration_in_directory(db, Path::new(DB_MIGRATIONS_PATH)) {
Ok(_) => (),
Err(diesel::migrations::RunMigrationsError::MigrationError(diesel::migrations::MigrationError::NoMigrationRun)) => break,
Err(e) => bail!(e),
}
}
Ok(())
}
fn migrate_up(&self) -> Result<()> {
let db = self.db.lock().unwrap();
let db = db.deref();
embedded_migrations::run(db)?;
Ok(())
}
fn update_index(&self) -> Result<()> {
let start = time::Instant::now();
println!("Beginning library index update");
self.clean()?;
self.populate()?;
println!("Library index update took {} seconds",
start.elapsed().as_secs());
Ok(())
}
fn clean(&self) -> Result<()> {
{
let all_songs : Vec<String>;
{
let db = self.db.lock().unwrap();
let db = db.deref();
all_songs = songs::table.select(songs::columns::path).load(db)?;
}
let missing_songs = all_songs.into_iter().filter(|ref song_path| {
let path = Path::new(&song_path);
!path.exists() || self.vfs.real_to_virtual(path).is_err()
}).collect::<Vec<_>>();
let db = self.db.lock().unwrap();
let db = db.deref();
diesel::delete(songs::table.filter(songs::columns::path.eq_any(missing_songs))).execute(db)?;
}
{
let all_directories : Vec<String>;
{
let db = self.db.lock().unwrap();
let db = db.deref();
all_directories = directories::table.select(directories::columns::path).load(db)?;
}
let missing_directories = all_directories.into_iter().filter(|ref directory_path| {
let path = Path::new(&directory_path);
!path.exists() || self.vfs.real_to_virtual(path).is_err()
}).collect::<Vec<_>>();
let db = self.db.lock().unwrap();
let db = db.deref();
diesel::delete(directories::table.filter(directories::columns::path.eq_any(missing_directories))).execute(db)?;
}
Ok(())
}
fn populate(&self) -> Result<()> {
let vfs = self.vfs.deref();
let mount_points = vfs.get_mount_points();
let mut builder = IndexBuilder::new(&self.db)?;
for (_, target) in mount_points {
self.populate_directory(&mut builder, None, target.as_path())?;
}
builder.flush_songs()?;
builder.flush_directories()?;
Ok(())
}
fn get_artwork(&self, dir: &Path) -> Option<String> {
let pattern = match self.album_art_pattern {
Some(ref p) => p,
_ => return None,
};
if let Ok(dir_content) = fs::read_dir(dir) {
for file in dir_content {
if let Ok(file) = file {
if let Some(name_string) = file.file_name().to_str() {
if pattern.is_match(name_string) {
return file.path().to_str().map(|p| p.to_owned());
}
}
}
}
}
None
}
fn populate_directory(&self, builder: &mut IndexBuilder, parent: Option<&Path>, path: &Path) -> Result<()> {
// Find artwork
let artwork = self.get_artwork(path);
// Extract path and parent path
let parent_string = parent.and_then(|p| p.to_str()).map(|s| s.to_owned());
let path_string = path.to_str().ok_or("Invalid directory path")?;
// Find date added
let metadata = fs::metadata(path_string)?;
let created = metadata
.created()
.or(metadata.modified())?
.duration_since(time::UNIX_EPOCH)?
.as_secs() as i32;
let mut directory_album = None;
let mut directory_year = None;
let mut directory_artist = None;
let mut inconsistent_directory_album = false;
let mut inconsistent_directory_year = false;
let mut inconsistent_directory_artist = false;
// Insert content
if let Ok(dir_content) = fs::read_dir(path) {
for file in dir_content {
let file_path = match file {
Ok(f) => f.path(),
_ => continue,
};
if file_path.is_dir() {
self.populate_directory(builder, Some(path), file_path.as_path())?;
} else {
if let Some(file_path_string) = file_path.to_str() {
if let Ok(tags) = metadata::read(file_path.as_path()) {
if tags.year.is_some() {
inconsistent_directory_year |= directory_year.is_some() &&
directory_year != tags.year;
directory_year = tags.year;
}
if tags.album.is_some() {
inconsistent_directory_album |= directory_album.is_some() &&
directory_album != tags.album;
directory_album = tags.album.as_ref().map(|a| a.clone());
}
if tags.album_artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist !=
tags.album_artist;
directory_artist = tags.album_artist.as_ref().map(|a| a.clone());
} else if tags.artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist != tags.artist;
directory_artist = tags.artist.as_ref().map(|a| a.clone());
}
let song = NewSong {
path: file_path_string.to_owned(),
parent: path_string.to_owned(),
disc_number: tags.disc_number.map(|n| n as i32),
track_number: tags.track_number.map(|n| n as i32),
title: tags.title,
artist: tags.artist,
album_artist: tags.album_artist,
album: tags.album,
year: tags.year,
artwork: artwork.as_ref().map(|s| s.to_owned()),
};
builder.push_song(song)?;
}
}
}
}
}
// Insert directory
if inconsistent_directory_year {
directory_year = None;
}
if inconsistent_directory_album {
directory_album = None;
}
if inconsistent_directory_artist {
directory_artist = None;
}
let directory = NewDirectory {
path: path_string.to_owned(),
parent: parent_string,
artwork: artwork,
album: directory_album,
artist: directory_artist,
year: directory_year,
date_added: created,
};
builder.push_directory(directory)?;
Ok(())
}
pub fn run(&self) {
loop {
if let Err(e) = self.update_index() {
println!("Error while updating index: {}", e);
}
thread::sleep(time::Duration::from_secs(self.sleep_duration));
}
}
fn virtualize_song(&self, mut song: Song) -> Option<Song> {
song.path = match self.vfs.real_to_virtual(Path::new(&song.path)) {
Ok(p) => p.to_string_lossy().into_owned(),
_ => return None,
};
if let Some(artwork_path) = song.artwork {
song.artwork = match self.vfs.real_to_virtual(Path::new(&artwork_path)) {
Ok(p) => Some(p.to_string_lossy().into_owned()),
_ => None,
};
}
Some(song)
}
fn virtualize_directory(&self, mut directory: Directory) -> Option<Directory> {
directory.path = match self.vfs.real_to_virtual(Path::new(&directory.path)) {
Ok(p) => p.to_string_lossy().into_owned(),
_ => return None,
};
if let Some(artwork_path) = directory.artwork {
directory.artwork = match self.vfs.real_to_virtual(Path::new(&artwork_path)) {
Ok(p) => Some(p.to_string_lossy().into_owned()),
_ => None,
};
}
Some(directory)
}
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>> {
let mut output = Vec::new();
let db = self.db.lock().unwrap();
let db = db.deref();
// Browse top-level
if virtual_path.components().count() == 0 {
let real_directories : Vec<Directory> = directories::table.filter(directories::columns::parent.is_null()).load(db)?;
let virtual_directories = real_directories.into_iter().filter_map(|s| self.virtualize_directory(s));
output.extend(virtual_directories.into_iter().map(|d| CollectionFile::Directory(d)));
// Browse sub-directory
} else {
let real_path = self.vfs.virtual_to_real(virtual_path)?;
let real_path_string = real_path.as_path().to_string_lossy().into_owned();
let real_songs : Vec<Song> = songs::table.filter(songs::columns::parent.eq(&real_path_string)).load(db)?;
let virtual_songs = real_songs.into_iter().filter_map(|s| self.virtualize_song(s));
output.extend(virtual_songs.map(|s| CollectionFile::Song(s)));
let real_directories : Vec<Directory> = directories::table.filter(directories::columns::parent.eq(&real_path_string)).load(db)?;
let virtual_directories = real_directories.into_iter().filter_map(|s| self.virtualize_directory(s));
output.extend(virtual_directories.map(|d| CollectionFile::Directory(d)));
}
Ok(output)
}
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>> {
let db = self.db.lock().unwrap();
let db = db.deref();
let real_path = self.vfs.virtual_to_real(virtual_path)?;
let like_path = real_path.as_path().to_string_lossy().into_owned() + "%";
let real_songs : Vec<Song> = songs::table.filter(songs::columns::path.like(&like_path)).load(db)?;
let virtual_songs = real_songs.into_iter().filter_map(|s| self.virtualize_song(s));
Ok(virtual_songs.collect::<Vec<_>>())
}
pub fn get_random_albums(&self, count: i64) -> Result<Vec<Directory>> {
let db = self.db.lock().unwrap();
let db = db.deref();
let real_directories = directories::table
.filter(directories::columns::album.is_not_null())
.limit(count)
.order(sql::<types::Bool>("RANDOM()"))
.load(db)?;
let virtual_directories = real_directories.into_iter().filter_map(|s| self.virtualize_directory(s));
Ok(virtual_directories.collect::<Vec<_>>())
}
pub fn get_recent_albums(&self, count: i64) -> Result<Vec<Directory>> {
let db = self.db.lock().unwrap();
let db = db.deref();
let real_directories : Vec<Directory> = directories::table.filter(directories::columns::album.is_not_null()).order(directories::columns::date_added.desc()).limit(count).load(db)?;
let virtual_directories = real_directories.into_iter().filter_map(|s| self.virtualize_directory(s));
Ok(virtual_directories.collect::<Vec<_>>())
}
}
fn _get_test_index(name: &str) -> Index {
use vfs::VfsConfig;
use std::collections::HashMap;
let mut collection_path = PathBuf::new();
collection_path.push("test");
collection_path.push("collection");
let mut mount_points = HashMap::new();
mount_points.insert("root".to_owned(), collection_path);
let vfs = Arc::new(Vfs::new(VfsConfig { mount_points: mount_points }));
let mut index_config = IndexConfig::new();
index_config.album_art_pattern = Some(Regex::new(r#"^Folder\.(png|jpg|jpeg)$"#).unwrap());
index_config.path = PathBuf::new();
index_config.path.push("test");
index_config.path.push(name);
if index_config.path.exists() {
fs::remove_file(&index_config.path).unwrap();
}
Index::new(vfs, &index_config).unwrap()
}
#[test]
fn test_migrations_up() {
_get_test_index("migrations_up.sqlite");
}
#[test]
fn test_migrations_down() {
let index = _get_test_index("migrations_down.sqlite");
index.migrate_down().unwrap();
index.migrate_up().unwrap();
}
#[test]
fn test_populate() {
let index = _get_test_index("populate.sqlite");
index.update_index().unwrap();
index.update_index().unwrap(); // Check that subsequent updates don't run into conflicts
let db = index.db.lock().unwrap();
let db = db.deref();
let all_directories : Vec<Directory> = directories::table.load(db).unwrap();
let all_songs : Vec<Song> = songs::table.load(db).unwrap();
assert_eq!(all_directories.len(), 5);
assert_eq!(all_songs.len(), 12);
}
#[test]
fn test_metadata() {
let mut target = PathBuf::new();
target.push("root");
target.push("Tobokegao");
target.push("Picnic");
let mut song_path = target.clone();
song_path.push("05 - シャーベット (Sherbet).mp3");
let mut artwork_path = target.clone();
artwork_path.push("Folder.png");
let index = _get_test_index("metadata.sqlite");
index.update_index().unwrap();
let results = index.flatten(target.as_path()).unwrap();
assert_eq!(results.len(), 7);
let song = &results[4];
assert_eq!(song.path, song_path.to_string_lossy().as_ref());
assert_eq!(song.track_number, Some(5));
assert_eq!(song.disc_number, None);
assert_eq!(song.title, Some("シャーベット (Sherbet)".to_owned()));
assert_eq!(song.artist, Some("Tobokegao".to_owned()));
assert_eq!(song.album_artist, None);
assert_eq!(song.album, Some("Picnic".to_owned()));
assert_eq!(song.year, Some(2016));
assert_eq!(song.artwork, Some(artwork_path.to_string_lossy().into_owned()));
}
#[test]
fn test_browse_top_level() {
let mut root_path = PathBuf::new();
root_path.push("root");
let index = _get_test_index("browse_top_level.sqlite");
index.update_index().unwrap();
let results = index.browse(Path::new("")).unwrap();
assert_eq!(results.len(), 1);
match results[0] {
CollectionFile::Directory(ref d) => assert_eq!(d.path, root_path.to_str().unwrap()),
_ => panic!("Expected directory")
}
}
#[test]
fn test_browse() {
let mut khemmis_path = PathBuf::new();
khemmis_path.push("root");
khemmis_path.push("Khemmis");
let mut tobokegao_path = PathBuf::new();
tobokegao_path.push("root");
tobokegao_path.push("Tobokegao");
let index = _get_test_index("browse.sqlite");
index.update_index().unwrap();
let results = index.browse(Path::new("root")).unwrap();
assert_eq!(results.len(), 2);
match results[0] {
CollectionFile::Directory(ref d) => assert_eq!(d.path, khemmis_path.to_str().unwrap()),
_ => panic!("Expected directory")
}
match results[1] {
CollectionFile::Directory(ref d) => assert_eq!(d.path, tobokegao_path.to_str().unwrap()),
_ => panic!("Expected directory")
}
}
#[test]
fn test_flatten() {
let index = _get_test_index("flatten.sqlite");
index.update_index().unwrap();
let results = index.flatten(Path::new("root")).unwrap();
assert_eq!(results.len(), 12);
}
#[test]
fn test_random() {
let index = _get_test_index("random.sqlite");
index.update_index().unwrap();
let results = index.get_random_albums(1).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_recent() {
let index = _get_test_index("recent.sqlite");
index.update_index().unwrap();
let results = index.get_recent_albums(2).unwrap();
assert_eq!(results.len(), 2);
assert!(results[0].date_added >= results[1].date_added);
}

View file

@ -0,0 +1,2 @@
DROP TABLE directories;
DROP TABLE songs;

View file

@ -0,0 +1,25 @@
CREATE TABLE directories (
id INTEGER PRIMARY KEY NOT NULL,
path TEXT NOT NULL,
parent TEXT,
artist TEXT,
year INTEGER,
album TEXT,
artwork TEXT,
UNIQUE(path) ON CONFLICT REPLACE
);
CREATE TABLE songs (
id INTEGER PRIMARY KEY NOT NULL,
path TEXT NOT NULL,
parent TEXT NOT NULL,
track_number INTEGER,
disc_number INTEGER,
title TEXT,
artist TEXT,
album_artist TEXT,
year INTEGER,
album TEXT,
artwork TEXT,
UNIQUE(path) ON CONFLICT REPLACE
);

View file

@ -0,0 +1,15 @@
CREATE TEMPORARY TABLE directories_backup(id, path, parent, artist, year, album, artwork);
INSERT INTO directories_backup SELECT id, path, parent, artist, year, album, artwork FROM directories;
DROP TABLE directories;
CREATE TABLE directories (
id INTEGER PRIMARY KEY NOT NULL,
path TEXT NOT NULL,
parent TEXT,
artist TEXT,
year INTEGER,
album TEXT,
artwork TEXT,
UNIQUE(path) ON CONFLICT REPLACE
);
INSERT INTO directories SELECT * FROM directories_backup;
DROP TABLE directories_backup;

View file

@ -0,0 +1 @@
ALTER TABLE directories ADD COLUMN date_added INTEGER DEFAULT 0 NOT NULL;

6
src/db/mod.rs Normal file
View file

@ -0,0 +1,6 @@
mod index;
mod models;
mod schema;
pub use self::index::{Index, IndexConfig};
pub use self::models::{CollectionFile, Directory, Song};

32
src/db/models.rs Normal file
View file

@ -0,0 +1,32 @@
#[derive(Debug, Queryable, Serialize)]
pub struct Song {
#[serde(skip_serializing)] id: i32,
pub path: String,
#[serde(skip_serializing)] pub parent: String,
pub track_number: Option<i32>,
pub disc_number: Option<i32>,
pub title: Option<String>,
pub artist: Option<String>,
pub album_artist: Option<String>,
pub year: Option<i32>,
pub album: Option<String>,
pub artwork: Option<String>,
}
#[derive(Debug, Queryable, Serialize)]
pub struct Directory {
#[serde(skip_serializing)] id: i32,
pub path: String,
#[serde(skip_serializing)] pub parent: Option<String>,
pub artist: Option<String>,
pub year: Option<i32>,
pub album: Option<String>,
pub artwork: Option<String>,
pub date_added: i32,
}
#[derive(Debug, Serialize)]
pub enum CollectionFile {
Directory(Directory),
Song(Song),
}

1
src/db/schema.rs Normal file
View file

@ -0,0 +1 @@
infer_schema!("src/db/schema.sqlite");

BIN
src/db/schema.sqlite Normal file

Binary file not shown.

View file

@ -1,5 +1,6 @@
use ape;
use core;
use diesel;
use id3;
use getopts;
use image;
@ -9,13 +10,15 @@ use iron::status::Status;
use lewton;
use metaflac;
use regex;
use sqlite;
use std;
use toml;
error_chain! {
foreign_links {
Ape(ape::Error);
Diesel(diesel::result::Error);
DieselConnection(diesel::ConnectionError);
DieselMigration(diesel::migrations::RunMigrationsError);
Encoding(core::str::Utf8Error);
Flac(metaflac::Error);
GetOpts(getopts::Fail);
@ -26,7 +29,6 @@ error_chain! {
Time(std::time::SystemTimeError);
Toml(toml::de::Error);
Regex(regex::Error);
SQLite(sqlite::Error);
Vorbis(lewton::VorbisError);
}

View file

@ -1,786 +0,0 @@
use core::ops::Deref;
use regex::Regex;
use sqlite;
use sqlite::{Connection, State, Statement, Value};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use std::time;
use errors::*;
use metadata;
use vfs::Vfs;
const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 250; // Insertions in each transaction
const INDEX_LOCK_TIMEOUT: usize = 1000; // In milliseconds
pub struct IndexConfig {
pub album_art_pattern: Option<Regex>,
pub sleep_duration: u64, // in seconds
pub path: PathBuf,
}
impl IndexConfig {
pub fn new() -> IndexConfig {
IndexConfig {
sleep_duration: 60 * 30, // 30 minutes
album_art_pattern: None,
path: Path::new(":memory:").to_path_buf(),
}
}
}
pub struct Index {
path: String,
vfs: Arc<Vfs>,
album_art_pattern: Option<Regex>,
sleep_duration: u64,
}
#[derive(Debug, Serialize, PartialEq)]
pub struct Song {
path: String,
track_number: Option<u32>,
disc_number: Option<u32>,
title: Option<String>,
artist: Option<String>,
album_artist: Option<String>,
album: Option<String>,
year: Option<i32>,
artwork: Option<String>,
}
#[derive(Debug, Serialize, PartialEq)]
pub struct Directory {
path: String,
artist: Option<String>,
album: Option<String>,
year: Option<i32>,
artwork: Option<String>,
}
#[derive(Debug, Serialize, PartialEq)]
pub enum CollectionFile {
Directory(Directory),
Song(Song),
}
fn string_option_to_value(input: Option<String>) -> Value {
match input {
Some(s) => Value::String(s),
None => Value::Null,
}
}
fn i32_option_to_value(input: Option<i32>) -> Value {
match input {
Some(s) => Value::Integer(s as i64),
None => Value::Null,
}
}
fn u32_option_to_value(input: Option<u32>) -> Value {
match input {
Some(s) => Value::Integer(s as i64),
None => Value::Null,
}
}
struct IndexBuilder<'db> {
queue: Vec<CollectionFile>,
db: &'db Connection,
insert_directory: Statement<'db>,
insert_song: Statement<'db>,
}
impl<'db> IndexBuilder<'db> {
fn new(db: &Connection) -> Result<IndexBuilder> {
let mut queue = Vec::new();
queue.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
Ok(IndexBuilder {
queue: queue,
db: db,
insert_directory: db.prepare("INSERT OR REPLACE INTO directories (path, parent, artwork, year, \
artist, album, date_added) VALUES (?, ?, ?, ?, ?, ?, ?)")?,
insert_song: db.prepare("INSERT OR REPLACE INTO songs (path, parent, disc_number, track_number, \
title, year, album_artist, artist, album, artwork) VALUES (?, ?, ?, ?, \
?, ?, ?, ?, ?, ?)")?,
})
}
fn get_parent(path: &str) -> Option<String> {
let parent_path = Path::new(path);
if let Some(parent_dir) = parent_path.parent() {
if let Some(parent_path) = parent_dir.to_str() {
return Some(parent_path.to_owned());
}
}
None
}
fn flush(&mut self) -> Result<()> {
self.db.execute("BEGIN TRANSACTION")?;
while let Some(file) = self.queue.pop() {
match file {
// Insert directory
CollectionFile::Directory(directory) => {
let metadata = fs::metadata(directory.path.as_str())?;
let created = metadata
.created()
.or(metadata.modified())?
.duration_since(time::UNIX_EPOCH)?
.as_secs();
let parent = IndexBuilder::get_parent(directory.path.as_str());
self.insert_directory.reset()?;
self.insert_directory
.bind(1, &Value::String(directory.path))?;
self.insert_directory
.bind(2, &string_option_to_value(parent))?;
self.insert_directory
.bind(3, &string_option_to_value(directory.artwork))?;
self.insert_directory
.bind(4, &i32_option_to_value(directory.year))?;
self.insert_directory
.bind(5, &string_option_to_value(directory.artist))?;
self.insert_directory
.bind(6, &string_option_to_value(directory.album))?;
self.insert_directory
.bind(7, &Value::Integer(created as i64))?;
self.insert_directory.next()?;
}
// Insert song
CollectionFile::Song(song) => {
let parent = IndexBuilder::get_parent(song.path.as_str());
self.insert_song.reset()?;
self.insert_song.bind(1, &Value::String(song.path))?;
self.insert_song
.bind(2, &string_option_to_value(parent))?;
self.insert_song
.bind(3, &u32_option_to_value(song.disc_number))?;
self.insert_song
.bind(4, &u32_option_to_value(song.track_number))?;
self.insert_song
.bind(5, &string_option_to_value(song.title))?;
self.insert_song
.bind(6, &i32_option_to_value(song.year))?;
self.insert_song
.bind(7, &string_option_to_value(song.album_artist))?;
self.insert_song
.bind(8, &string_option_to_value(song.artist))?;
self.insert_song
.bind(9, &string_option_to_value(song.album))?;
self.insert_song
.bind(10, &string_option_to_value(song.artwork))?;
self.insert_song.next()?;
}
}
}
self.db.execute("END TRANSACTION")?;
Ok(())
}
fn push(&mut self, file: CollectionFile) -> Result<()> {
if self.queue.len() == self.queue.capacity() {
self.flush()?;
}
self.queue.push(file);
Ok(())
}
}
impl Index {
pub fn new(vfs: Arc<Vfs>, config: &IndexConfig) -> Result<Index> {
let path = &config.path;
println!("Index file path: {}", path.to_string_lossy());
let index = Index {
path: path.to_string_lossy().deref().to_string(),
vfs: vfs,
album_art_pattern: config.album_art_pattern.clone(),
sleep_duration: config.sleep_duration,
};
if !path.exists() {
index.init()?;
}
index.migrate()?;
Ok(index)
}
fn init(&self) -> Result<()> {
println!("Initializing index database");
let db = self.connect()?;
db.execute("PRAGMA synchronous = NORMAL")?;
db.execute("
CREATE TABLE version
( id INTEGER PRIMARY KEY NOT NULL
, number \
INTEGER NULL
);
INSERT INTO version (number) VALUES(1);
CREATE \
TABLE directories
( id INTEGER PRIMARY KEY NOT NULL
, path TEXT NOT \
NULL
, parent TEXT
, artist TEXT
, year INTEGER
, album TEXT
\
, artwork TEXT
, UNIQUE(path)
);
CREATE TABLE songs
( id \
INTEGER PRIMARY KEY NOT NULL
, path TEXT NOT NULL
, parent TEXT NOT \
NULL
, disc_number INTEGER
, track_number INTEGER
, title TEXT
\
, artist TEXT
, album_artist TEXT
, year INTEGER
, album TEXT
\
, artwork TEXT
, UNIQUE(path)
);
")?;
Ok(())
}
fn migrate(&self) -> Result<()> {
let version = self.read_version()?;
if version < 2 {
println!("Migrating Index from version: {}", version);
self.migrate_to_version2()?
}
Ok(())
}
fn read_version(&self) -> Result<i64> {
let db = self.connect()?;
let mut select = db.prepare("SELECT MAX(number) FROM version")?;
if let Ok(State::Row) = select.next() {
let version = select.read(0)?;
return Ok(version);
}
Err(Error::from(ErrorKind::MissingIndexVersion).into())
}
fn migrate_to_version2(&self) -> Result<()> {
let db = self.connect()?;
db.execute("BEGIN TRANSACTION")?;
db.execute("ALTER TABLE directories ADD COLUMN date_added INTEGER DEFAULT 0 NOT NULL")?;
db.execute("UPDATE version SET number = 2")?;
db.execute("END TRANSACTION")?;
Ok(())
}
fn connect(&self) -> Result<Connection> {
let mut db = sqlite::open(self.path.clone())?;
db.set_busy_timeout(INDEX_LOCK_TIMEOUT)?;
Ok(db)
}
fn update_index(&self) -> Result<()> {
let start = time::Instant::now();
println!("Beginning library index update");
let db = &self.connect()?;
self.clean(db)?;
self.populate(db)?;
println!("Library index update took {} seconds",
start.elapsed().as_secs());
Ok(())
}
fn clean(&self, db: &Connection) -> Result<()> {
{
let mut select = db.prepare("SELECT path FROM songs")?;
let mut delete = db.prepare("DELETE FROM songs WHERE path = ?")?;
while let Ok(State::Row) = select.next() {
let path_string: String = select.read(0)?;
let path = Path::new(path_string.as_str());
if !path.exists() || self.vfs.real_to_virtual(path).is_err() {
delete.reset()?;
delete.bind(1, &Value::String(path_string.to_owned()))?;
delete.next()?;
}
}
}
{
let mut select = db.prepare("SELECT path FROM directories")?;
let mut delete = db.prepare("DELETE FROM directories WHERE path = ?")?;
while let Ok(State::Row) = select.next() {
let path_string: String = select.read(0)?;
let path = Path::new(path_string.as_str());
if !path.exists() || self.vfs.real_to_virtual(path).is_err() {
delete.reset()?;
delete.bind(1, &Value::String(path_string.to_owned()))?;
delete.next()?;
}
}
}
Ok(())
}
fn populate(&self, db: &Connection) -> Result<()> {
let vfs = self.vfs.deref();
let mount_points = vfs.get_mount_points();
let mut builder = IndexBuilder::new(db)?;
for (_, target) in mount_points {
self.populate_directory(&mut builder, target.as_path())?;
}
builder.flush()?;
Ok(())
}
fn get_artwork(&self, dir: &Path) -> Option<String> {
let pattern = match self.album_art_pattern {
Some(ref p) => p,
_ => return None,
};
if let Ok(dir_content) = fs::read_dir(dir) {
for file in dir_content {
if let Ok(file) = file {
if let Some(name_string) = file.file_name().to_str() {
if pattern.is_match(name_string) {
return file.path().to_str().map(|p| p.to_owned());
}
}
}
}
}
None
}
fn populate_directory(&self, builder: &mut IndexBuilder, path: &Path) -> Result<()> {
// Find artwork
let artwork = self.get_artwork(path);
let path_string = path.to_str().ok_or("Invalid directory path")?;
let mut directory_album = None;
let mut directory_year = None;
let mut directory_artist = None;
let mut inconsistent_directory_album = false;
let mut inconsistent_directory_year = false;
let mut inconsistent_directory_artist = false;
// Insert content
if let Ok(dir_content) = fs::read_dir(path) {
for file in dir_content {
let file_path = match file {
Ok(f) => f.path(),
_ => continue,
};
if file_path.is_dir() {
self.populate_directory(builder, file_path.as_path())?;
} else {
if let Some(file_path_string) = file_path.to_str() {
if let Ok(tags) = metadata::read(file_path.as_path()) {
if tags.year.is_some() {
inconsistent_directory_year |= directory_year.is_some() &&
directory_year != tags.year;
directory_year = tags.year;
}
if tags.album.is_some() {
inconsistent_directory_album |= directory_album.is_some() &&
directory_album != tags.album;
directory_album = tags.album.as_ref().map(|a| a.clone());
}
if tags.album_artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist !=
tags.album_artist;
directory_artist = tags.album_artist.as_ref().map(|a| a.clone());
} else if tags.artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist != tags.artist;
directory_artist = tags.artist.as_ref().map(|a| a.clone());
}
let song = Song {
path: file_path_string.to_owned(),
disc_number: tags.disc_number,
track_number: tags.track_number,
title: tags.title,
artist: tags.artist,
album_artist: tags.album_artist,
album: tags.album,
year: tags.year,
artwork: artwork.as_ref().map(|s| s.to_owned()),
};
builder.push(CollectionFile::Song(song))?;
}
}
}
}
}
// Insert directory
if inconsistent_directory_year {
directory_year = None;
}
if inconsistent_directory_album {
directory_album = None;
}
if inconsistent_directory_artist {
directory_artist = None;
}
let directory = Directory {
path: path_string.to_owned(),
artwork: artwork,
album: directory_album,
artist: directory_artist,
year: directory_year,
};
builder.push(CollectionFile::Directory(directory))?;
Ok(())
}
pub fn run(&self) {
loop {
if let Err(e) = self.update_index() {
println!("Error while updating index: {}", e);
}
thread::sleep(time::Duration::from_secs(self.sleep_duration));
}
}
fn select_songs(&self, select: &mut Statement) -> Result<Vec<Song>> {
let mut output = Vec::new();
while let State::Row = select.next()? {
let song_path: String = select.read(0)?;
let disc_number: Value = select.read(1)?;
let track_number: Value = select.read(2)?;
let title: Value = select.read(3)?;
let year: Value = select.read(4)?;
let album_artist: Value = select.read(5)?;
let artist: Value = select.read(6)?;
let album: Value = select.read(7)?;
let artwork: Value = select.read(8)?;
let song_path = Path::new(song_path.as_str());
let song_path = match self.vfs.real_to_virtual(song_path) {
Ok(p) => p.to_str().ok_or("Invalid song path")?.to_owned(),
_ => continue,
};
let artwork_path = artwork.as_string().map(|p| Path::new(p));
let mut artwork = None;
if let Some(artwork_path) = artwork_path {
artwork = match self.vfs.real_to_virtual(artwork_path) {
Ok(p) => {
Some(p.to_str()
.ok_or("Invalid song artwork path")?
.to_owned())
}
_ => None,
};
}
let song = Song {
path: song_path,
disc_number: disc_number.as_integer().map(|n| n as u32),
track_number: track_number.as_integer().map(|n| n as u32),
title: title.as_string().map(|s| s.to_owned()),
year: year.as_integer().map(|n| n as i32),
album_artist: album_artist.as_string().map(|s| s.to_owned()),
artist: artist.as_string().map(|s| s.to_owned()),
album: album.as_string().map(|s| s.to_owned()),
artwork: artwork,
};
output.push(song);
}
Ok(output)
}
fn select_directories(&self, select: &mut Statement) -> Result<Vec<Directory>> {
let mut output = Vec::new();
while let State::Row = select.next()? {
let directory_value: String = select.read(0)?;
let artwork_path: Value = select.read(1)?;
let year: Value = select.read(2)?;
let artist: Value = select.read(3)?;
let album: Value = select.read(4)?;
let directory_path = Path::new(directory_value.as_str());
let directory_path = match self.vfs.real_to_virtual(directory_path) {
Ok(p) => p.to_str().ok_or("Invalid directory path")?.to_owned(),
_ => continue,
};
let artwork_path = artwork_path.as_string().map(|p| Path::new(p));
let mut artwork = None;
if let Some(artwork_path) = artwork_path {
artwork = match self.vfs.real_to_virtual(artwork_path) {
Ok(p) => {
Some(p.to_str()
.ok_or("Invalid directory artwork path")?
.to_owned())
}
_ => None,
};
}
let directory = Directory {
path: directory_path,
artwork: artwork,
year: year.as_integer().map(|n| n as i32),
artist: artist.as_string().map(|s| s.to_owned()),
album: album.as_string().map(|s| s.to_owned()),
};
output.push(directory);
}
Ok(output)
}
// List sub-directories within a directory
fn browse_directories(&self, real_path: &Path) -> Result<Vec<CollectionFile>> {
let db = self.connect()?;
let path_string = real_path.to_string_lossy();
let mut select = db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE \
parent = ? ORDER BY path COLLATE NOCASE ASC")?;
select
.bind(1, &Value::String(path_string.deref().to_owned()))?;
let output = self.select_directories(&mut select)?;
let output = output
.into_iter()
.map(|d| CollectionFile::Directory(d))
.collect();
Ok(output)
}
// List songs within a directory
fn browse_songs(&self, real_path: &Path) -> Result<Vec<CollectionFile>> {
let db = self.connect()?;
let path_string = real_path.to_string_lossy();
let mut select = db.prepare("SELECT path, disc_number, track_number, title, year, album_artist, \
artist, album, artwork FROM songs WHERE parent = ? ORDER BY track_number, path \
COLLATE NOCASE ASC")?;
select
.bind(1, &Value::String(path_string.deref().to_owned()))?;
Ok(self.select_songs(&mut select)?
.into_iter()
.map(|s| CollectionFile::Song(s))
.collect())
}
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>> {
let mut output = Vec::new();
// Browse top-level
if virtual_path.components().count() == 0 {
for (n, _) in self.vfs.get_mount_points() {
let directory = Directory {
path: n.to_owned(),
artwork: None,
year: None,
artist: None,
album: None,
};
output.push(CollectionFile::Directory(directory));
}
// Browse sub-directory
} else {
let real_path = self.vfs.virtual_to_real(virtual_path)?;
let directories = self.browse_directories(real_path.as_path())?;
let songs = self.browse_songs(real_path.as_path())?;
output.extend(directories);
output.extend(songs);
}
Ok(output)
}
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>> {
let db = self.connect()?;
let real_path = self.vfs.virtual_to_real(virtual_path)?;
let path_string = real_path.to_string_lossy().into_owned() + "%";
let mut select = db.prepare("SELECT path, disc_number, track_number, title, year, album_artist, \
artist, album, artwork FROM songs WHERE path LIKE ? ORDER BY path \
COLLATE NOCASE ASC")?;
select
.bind(1, &Value::String(path_string.deref().to_owned()))?;
self.select_songs(&mut select)
}
pub fn get_random_albums(&self, count: u32) -> Result<Vec<Directory>> {
let db = self.connect()?;
let mut select = db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE album \
IS NOT NULL ORDER BY RANDOM() LIMIT ?")?;
select.bind(1, &Value::Integer(count as i64))?;
self.select_directories(&mut select)
}
pub fn get_recent_albums(&self, count: u32) -> Result<Vec<Directory>> {
let db = self.connect()?;
let mut select = db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE album \
IS NOT NULL ORDER BY date_added DESC LIMIT ?")?;
select.bind(1, &Value::Integer(count as i64))?;
self.select_directories(&mut select)
}
}
fn _get_test_index(name: &str) -> Index {
use vfs::VfsConfig;
use std::collections::HashMap;
let mut collection_path = PathBuf::new();
collection_path.push("test");
collection_path.push("collection");
let mut mount_points = HashMap::new();
mount_points.insert("root".to_owned(), collection_path);
let vfs = Arc::new(Vfs::new(VfsConfig { mount_points: mount_points }));
let mut index_config = IndexConfig::new();
index_config.album_art_pattern = Some(Regex::new(r#"^Folder\.(png|jpg|jpeg)$"#).unwrap());
index_config.path = PathBuf::new();
index_config.path.push("test");
index_config.path.push(name);
if index_config.path.exists() {
fs::remove_file(&index_config.path).unwrap();
}
Index::new(vfs, &index_config).unwrap()
}
#[test]
fn test_populate() {
let index = _get_test_index("populate.sqlite");
index.update_index().unwrap();
}
#[test]
fn test_metadata() {
let mut target = PathBuf::new();
target.push("root");
target.push("Tobokegao");
target.push("Picnic");
let mut song_path = target.clone();
song_path.push("05 - シャーベット (Sherbet).mp3");
let mut artwork_path = target.clone();
artwork_path.push("Folder.png");
let index = _get_test_index("metadata.sqlite");
index.update_index().unwrap();
let results = index.browse(target.as_path()).unwrap();
assert_eq!(results.len(), 7);
assert_eq!(results[4],
CollectionFile::Song(Song {
path: song_path.to_str().unwrap().to_string(),
track_number: Some(5),
disc_number: None,
title: Some("シャーベット (Sherbet)".to_owned()),
artist: Some("Tobokegao".to_owned()),
album_artist: None,
album: Some("Picnic".to_owned()),
year: Some(2016),
artwork: Some(artwork_path.to_str().unwrap().to_string()),
}));
}
#[test]
fn test_browse() {
let mut khemmis_path = PathBuf::new();
khemmis_path.push("root");
khemmis_path.push("Khemmis");
let khemmis =
CollectionFile::Directory(Directory {
path: khemmis_path.to_string_lossy().deref().to_string(),
artist: None,
album: None,
year: None,
artwork: None,
});
let mut tobokegao_path = PathBuf::new();
tobokegao_path.push("root");
tobokegao_path.push("Tobokegao");
let tobokegao =
CollectionFile::Directory(Directory {
path: tobokegao_path.to_string_lossy().deref().to_string(),
artist: None,
album: None,
year: None,
artwork: None,
});
let index = _get_test_index("browse.sqlite");
index.update_index().unwrap();
let results = index.browse(Path::new("root")).unwrap();
assert_eq!(results, vec![khemmis, tobokegao]);
}
#[test]
fn test_flatten() {
let index = _get_test_index("flatten.sqlite");
index.update_index().unwrap();
let results = index.flatten(Path::new("root")).unwrap();
assert_eq!(results.len(), 12);
}
#[test]
fn test_random() {
let index = _get_test_index("random.sqlite");
index.update_index().unwrap();
let results = index.get_random_albums(1).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_recent() {
let index = _get_test_index("recent.sqlite");
index.update_index().unwrap();
let results = index.get_recent_albums(2).unwrap();
assert_eq!(results.len(), 2);
}

View file

@ -4,6 +4,10 @@ extern crate ape;
extern crate app_dirs;
extern crate core;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_codegen;
#[macro_use]
extern crate error_chain;
extern crate getopts;
extern crate hyper;
@ -22,7 +26,6 @@ extern crate serde;
extern crate serde_derive;
extern crate serde_json;
extern crate staticfile;
extern crate sqlite;
extern crate toml;
extern crate typemap;
extern crate url;
@ -55,9 +58,9 @@ use std::sync::Arc;
mod api;
mod collection;
mod config;
mod db;
mod ddns;
mod errors;
mod index;
mod metadata;
mod ui;
mod utils;
@ -114,7 +117,7 @@ fn run() -> Result<()> {
let index_file_name = matches.opt_str("d");
let index_file_path = index_file_name.map(|n| Path::new(n.as_str()).to_path_buf());
config.index.path = index_file_path.unwrap_or( config.index.path );
let index = Arc::new(index::Index::new(vfs.clone(), &config.index)?);
let index = Arc::new(db::Index::new(vfs.clone(), &config.index)?);
let index_ref = index.clone();
std::thread::spawn(move || index_ref.run());

View file

@ -29,7 +29,11 @@ impl Vfs {
match real_path.strip_prefix(target) {
Ok(p) => {
let mount_path = Path::new(&name);
return Ok(mount_path.join(p));
return if p.components().count() == 0 {
Ok(mount_path.to_path_buf())
} else {
Ok(mount_path.join(p))
};
}
Err(_) => (),
}
@ -46,7 +50,7 @@ impl Vfs {
Ok(target.clone())
} else {
Ok(target.join(p))
}
};
}
Err(_) => (),
}