Compare commits

...

12 Commits

Author SHA1 Message Date
sigoden
3b3ea718d9 chore: improve readme 2022-09-09 21:43:40 +08:00
sigoden
3debf88da1 chore: improve readme 2022-09-09 21:37:07 +08:00
sigoden
7eaa6f2484 chore: undo hidden arg changes 2022-09-09 21:30:27 +08:00
sigoden
68def1c1d9 chore: update screenshot.png in readme 2022-09-09 21:22:03 +08:00
sigoden
868f4158f5 chore(release): version 0.30.0 2022-09-09 21:04:05 +08:00
sigoden
3063dca0a6 chore: update readme 2022-09-05 10:34:18 +08:00
sigoden
a74e40aee5 feat: add --assets options to override assets (#134)
* feat: add --assets options to override assets

* update readme
2022-09-05 10:30:45 +08:00
sigoden
bde06fef94 chore: refactor clap multiple_occurrences and multiple_values (#130) 2022-08-27 10:30:08 +08:00
sigoden
31c832a742 feat: support sort by name, mtime, size (#128) 2022-08-23 14:24:42 +08:00
Daniel Flannery
9f8171a22f chore: Corrected type in README (#127) 2022-08-17 07:41:02 +08:00
sigoden
0fb9f3b2c8 chore: update readme 2022-08-06 08:30:19 +08:00
sigoden
3ae75d3558 fix: hide path by ext name (#126) 2022-08-06 07:48:34 +08:00
15 changed files with 569 additions and 270 deletions

View File

@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.30.0] - 2022-09-05
### Bug Fixes
- Hide path by ext name ([#126](https://github.com/sigoden/dufs/issues/126))
### Features
- Support sort by name, mtime, size ([#128](https://github.com/sigoden/dufs/issues/128))
- Add --assets options to override assets ([#134](https://github.com/sigoden/dufs/issues/134))
## [0.29.0] - 2022-08-03 ## [0.29.0] - 2022-08-03
### Bug Fixes ### Bug Fixes

243
Cargo.lock generated
View File

@@ -10,13 +10,28 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alphanumeric-sort"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e9c9abb82613923ec78d7a461595d52491ba7240f3c64c0bbe0e6d98e0fce0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "assert_cmd" name = "assert_cmd"
version = "2.0.4" version = "2.0.4"
@@ -142,9 +157,9 @@ dependencies = [
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [ dependencies = [
"generic-array", "generic-array",
] ]
@@ -162,9 +177,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.10.0" version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]] [[package]]
name = "bytes" name = "bytes"
@@ -186,22 +201,24 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [ dependencies = [
"libc", "iana-time-zone",
"js-sys",
"num-integer", "num-integer",
"num-traits", "num-traits",
"time", "time",
"wasm-bindgen",
"winapi", "winapi",
] ]
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.16" version = "3.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"clap_lex", "clap_lex",
@@ -212,9 +229,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "3.2.3" version = "3.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" checksum = "e4179da71abd56c26b54dd0c248cc081c1f43b0a1a7e8448e28e57a29baa993d"
dependencies = [ dependencies = [
"clap", "clap",
] ]
@@ -246,9 +263,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.2" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -303,7 +320,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [ dependencies = [
"block-buffer 0.10.2", "block-buffer 0.10.3",
"crypto-common", "crypto-common",
] ]
@@ -322,9 +339,9 @@ dependencies = [
[[package]] [[package]]
name = "diqwest" name = "diqwest"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3cebabb6a61cb79191ab4cfbe1ebed9d2357f20bfd224855ec5d02bd1076c52" checksum = "70822b55cc8af405e65915d22ee0031433414f4464fa276cd6cdb9fff203bb40"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"digest_auth", "digest_auth",
@@ -339,8 +356,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dufs" name = "dufs"
version = "0.29.0" version = "0.30.0"
dependencies = [ dependencies = [
"alphanumeric-sort",
"assert_cmd", "assert_cmd",
"assert_fs", "assert_fs",
"async-stream", "async-stream",
@@ -350,10 +368,12 @@ dependencies = [
"clap", "clap",
"clap_complete", "clap_complete",
"diqwest", "diqwest",
"form_urlencoded",
"futures", "futures",
"headers", "headers",
"hyper", "hyper",
"if-addrs", "if-addrs",
"indexmap",
"lazy_static", "lazy_static",
"log", "log",
"md5", "md5",
@@ -381,9 +401,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@@ -445,19 +465,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [ dependencies = [
"matches",
"percent-encoding", "percent-encoding",
] ]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -470,9 +489,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@@ -480,15 +499,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@@ -497,15 +516,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -514,15 +533,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
[[package]] [[package]]
name = "futures-timer" name = "futures-timer"
@@ -532,9 +551,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@@ -595,9 +614,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.13" version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -620,9 +639,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "headers" name = "headers"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags", "bitflags",
@@ -631,7 +650,7 @@ dependencies = [
"http", "http",
"httpdate", "httpdate",
"mime", "mime",
"sha-1", "sha1",
] ]
[[package]] [[package]]
@@ -682,9 +701,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.7.1" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@@ -743,12 +762,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "idna" name = "iana-time-zone"
version = "0.2.3" version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"once_cell",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [ dependencies = [
"matches",
"unicode-bidi", "unicode-bidi",
"unicode-normalization", "unicode-normalization",
] ]
@@ -817,9 +849,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
@@ -838,9 +870,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.126" version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]] [[package]]
name = "log" name = "log"
@@ -851,12 +883,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.9.1" version = "0.9.1"
@@ -898,9 +924,9 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@@ -972,9 +998,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@@ -1029,15 +1055,15 @@ dependencies = [
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.2.0" version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
@@ -1101,18 +1127,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.42" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.20" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -1303,9 +1329,9 @@ dependencies = [
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.10" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]] [[package]]
name = "same-file" name = "same-file"
@@ -1338,9 +1364,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.6.1" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
@@ -1361,24 +1387,24 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.12" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.141" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500" checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.141" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f" checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1387,9 +1413,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.82" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@@ -1409,10 +1435,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "sha-1" name = "sha1"
version = "0.10.0" version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"cpufeatures", "cpufeatures",
@@ -1452,9 +1478,9 @@ dependencies = [
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.4" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [ dependencies = [
"libc", "libc",
"winapi", "winapi",
@@ -1468,9 +1494,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1518,18 +1544,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1573,9 +1599,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.20.1" version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@@ -1625,9 +1651,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -1692,9 +1718,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@@ -1713,21 +1739,20 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.2" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
"matches",
"percent-encoding", "percent-encoding",
] ]
[[package]] [[package]]
name = "urlencoding" name = "urlencoding"
version = "2.1.0" version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821" checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
[[package]] [[package]]
name = "uuid" name = "uuid"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "dufs" name = "dufs"
version = "0.29.0" version = "0.30.0"
edition = "2021" edition = "2021"
authors = ["sigoden <sigoden@gmail.com>"] authors = ["sigoden <sigoden@gmail.com>"]
description = "Dufs is a distinctive utility file server" description = "Dufs is a distinctive utility file server"
@@ -38,6 +38,8 @@ log = "0.4"
socket2 = "0.4" socket2 = "0.4"
async-stream = "0.3" async-stream = "0.3"
walkdir = "2.3" walkdir = "2.3"
form_urlencoded = "1.0"
alphanumeric-sort = "1.4"
[features] [features]
default = ["tls"] default = ["tls"]
@@ -53,6 +55,7 @@ regex = "1"
url = "2" url = "2"
diqwest = { version = "1", features = ["blocking"] } diqwest = { version = "1", features = ["blocking"] }
predicates = "2" predicates = "2"
indexmap = "1.9"
[profile.release] [profile.release]
lto = true lto = true

108
README.md
View File

@@ -5,7 +5,7 @@
Dufs is a distinctive utility file server that supports static serving, uploading, searching, accessing control, webdav... Dufs is a distinctive utility file server that supports static serving, uploading, searching, accessing control, webdav...
![demo](https://user-images.githubusercontent.com/4012553/177549931-130383ef-0480-4911-b9c2-0d9534a624b7.png) ![demo](https://user-images.githubusercontent.com/4012553/189362357-b2f7aa6b-9df0-4438-a57c-c8f92850fc4f.png)
## Features ## Features
@@ -43,10 +43,10 @@ Download from [Github Releases](https://github.com/sigoden/dufs/releases), unzip
Dufs is a distinctive utility file server - https://github.com/sigoden/dufs Dufs is a distinctive utility file server - https://github.com/sigoden/dufs
USAGE: USAGE:
dufs [OPTIONS] [--] [path] dufs [OPTIONS] [--] [root]
ARGS: ARGS:
<path> Specific path to serve [default: .] <root> Specific path to serve [default: .]
OPTIONS: OPTIONS:
-b, --bind <addr>... Specify bind address -b, --bind <addr>... Specify bind address
@@ -64,6 +64,7 @@ OPTIONS:
--render-index Serve index.html when requesting a directory, returns 404 if not found index.html --render-index Serve index.html when requesting a directory, returns 404 if not found index.html
--render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html --render-try-index Serve index.html when requesting a directory, returns directory listing if not found index.html
--render-spa Serve SPA(Single Page Application) --render-spa Serve SPA(Single Page Application)
--assets <path> Use custom assets to override builtin assets
--tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS --tls-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
--tls-key <path> Path to the SSL/TLS certificate's private key --tls-key <path> Path to the SSL/TLS certificate's private key
--log-format <format> Customize http log format --log-format <format> Customize http log format
@@ -160,57 +161,72 @@ curl -X DELETE http://127.0.0.1:5000/path-to-file-or-folder
``` ```
<details> <details>
<summary><h2>Advance topics</h2></summary> <summary><h2>Advanced topics</h2></summary>
### Access Control ### Access Control
Dufs supports path level access control. You can control who can do what on which path with `--auth`/`-a`. Dufs supports path level access control. You can control who can do what on which path with `--auth`/`-a`.
``` ```
dufs -a <path>@<readwrite>[@<readonly>|@*] dufs -a <path>@<readwrite>
dufs -a <path>@<readwrite>@<readonly>
dufs -a <path>@<readwrite>@*
``` ```
- `<path>`: Protected url path - `<path>`: Protected url path
- `<readwrite>`: Account with upload/delete/view/download permission, required - `<readwrite>`: Account with readwrite permissions. If dufs is run with `dufs --allow-all`, the permissions are upload/delete/search/view/download. If dufs is run with `dufs --allow-upload`, the permissions are upload/view/download.
- `<readonly>`: Account with view/download permission, optional - `<readonly>`: Account with readonly permissions. The permissions are search/view/download if dufs allow search, otherwise view/download..
> `*` means `<path>` is public, everyone can view/download it.
For example:
``` ```
dufs -a /@admin:pass1@* -a /ui@designer:pass2 -A dufs -A -a /@admin:admin
``` ```
- All files/folders are public to view/download. `admin` has all permissions for all paths.
- Account `admin:pass1` can upload/delete/view/download any files/folders.
- Account `designer:pass2` can upload/delete/view/download any files/folders in the `ui` folder.
```
dufs -A -a /@admin:admin@guest:guest
```
`guest` has readonly permissions for all paths.
### Hidden Paths ```
dufs -A -a /@admin:admin@*
```
All paths is public, everyone can view/download it.
```
dufs -A -a /@admin:admin -a /user1@user1:pass1 -a /user2@pass2:user2
```
`user1` has all permissions for `/user1*` path.
`user2` has all permissions for `/user2*` path.
```
dufs -a /@admin:admin
```
Since dufs only allows viewing/downloading, `admin` can only view/download files.
### Hide Paths
Dufs supports hiding paths from directory listings via option `--hidden`. Dufs supports hiding paths from directory listings via option `--hidden`.
``` ```
dufs --hidden .git,.DS_Store dufs --hidden .git,.DS_Store,tmp
``` ```
`--hidden` supports a variant glob: `--hidden` also supports a variant glob:
- `?` matches any single character - `?` matches any single character
- `*` matches any (possibly empty) sequence of characters - `*` matches any (possibly empty) sequence of characters
- no `**`, `[..]`, `[!..]` - `**`, `[..]`, `[!..]` is not supported
Hide all hidden directories/files ```sh
```
dufs --hidden '.*' dufs --hidden '.*'
dufs --hidden '*.log,*.lock'
``` ```
### Log Format ### Log Format
Dufs supports customize http log format via option `--log-format`. Dufs supports customize http log format with option `--log-format`.
The default format is `'$remote_addr "$request" $status'`. The log format can use following variables.
| variable | description | | variable | description |
| ------------ | ------------------------------------------------------------------------- | | ------------ | ------------------------------------------------------------------------- |
@@ -220,7 +236,47 @@ The default format is `'$remote_addr "$request" $status'`.
| $status | response status | | $status | response status |
| $http_ | arbitrary request header field. examples: $http_user_agent, $http_referer | | $http_ | arbitrary request header field. examples: $http_user_agent, $http_referer |
> use `dufs --log-format=''` to disable http log
The default log format is `'$remote_addr "$request" $status'`.
```
2022-08-06T06:59:31+08:00 INFO - 127.0.0.1 "GET /" 200
```
Disable http log
```
dufs --log-format=''
```
Log user-agent
```
dufs --log-format '$remote_addr "$request" $status $http_user_agent'
```
```
2022-08-06T06:53:55+08:00 INFO - 127.0.0.1 "GET /" 200 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
```
Log remote-user
```
dufs --log-format '$remote_addr $remote_user "$request" $status' -a /@admin:admin -a /folder1@user1:pass1
```
```
2022-08-06T07:04:37+08:00 INFO - 127.0.0.1 admin "GET /" 200
```
### Customize UI
Dufs allows users to customize the UI with your own assets.
```
dufs --assets my-assets-dir/
```
Your assets folder must contains a `index.html` file.
`index.html` can use the following placeholder variables to retrieve internal data.
- `__INDEX_DATA__`: directory listing data
- `__ASSERTS_PREFIX__`: assets url prefix
</details> </details>
@@ -230,4 +286,4 @@ Copyright (c) 2022 dufs-developers.
dufs is made available under the terms of either the MIT License or the Apache License 2.0, at your option. dufs is made available under the terms of either the MIT License or the Apache License 2.0, at your option.
See the LICENSE-APACHE and LICENSE-MIT files for license details. See the LICENSE-APACHE and LICENSE-MIT files for license details.

View File

@@ -131,7 +131,16 @@ body {
padding-left: 0.6em; padding-left: 0.6em;
} }
.paths-table tr:hover { .paths-table thead a {
color: unset;
text-decoration: none;
}
.paths-table thead a > span {
padding-left: 2px;
}
.paths-table tbody tr:hover {
background-color: #fafafa; background-color: #fafafa;
} }
@@ -232,7 +241,7 @@ body {
color: #3191ff; color: #3191ff;
} }
.paths-table tr:hover { .paths-table tbody tr:hover {
background-color: #1a1a1a; background-color: #1a1a1a;
} }
} }

View File

@@ -4,7 +4,12 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
__SLOT__ <link rel="icon" type="image/x-icon" href="__ASSERTS_PREFIX__favicon.ico">
<link rel="stylesheet" href="__ASSERTS_PREFIX__index.css">
<script>
DATA = __INDEX_DATA__
</script>
<script src="__ASSERTS_PREFIX__index.js"></script>
</head> </head>
<body> <body>
<div class="head"> <div class="head">
@@ -48,12 +53,6 @@
</table> </table>
<table class="paths-table hidden"> <table class="paths-table hidden">
<thead> <thead>
<tr>
<th class="cell-name" colspan="2">Name</th>
<th class="cell-mtime">Last modified</th>
<th class="cell-size">Size</th>
<th class="cell-actions">Actions</th>
</tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>

View File

@@ -6,17 +6,41 @@
* @property {number} size * @property {number} size
*/ */
// https://stackoverflow.com/a/901144/3642588 /**
const params = new Proxy(new URLSearchParams(window.location.search), { * @typedef {object} DATA
get: (searchParams, prop) => searchParams.get(prop), * @property {string} href
}); * @property {string} uri_prefix
* @property {PathItem[]} paths
* @property {boolean} allow_upload
* @property {boolean} allow_delete
* @property {boolean} allow_search
* @property {boolean} dir_exists
*/
const dirEmptyNote = params.q ? 'No results' : DATA.dir_exists ? 'Empty folder' : 'Folder will be created when a file is uploaded'; /**
* @type {DATA} DATA
*/
var DATA;
/**
* @type {PARAMS}
* @typedef {object} PARAMS
* @property {string} q
* @property {string} sort
* @property {string} order
*/
const PARAMS = Object.fromEntries(new URLSearchParams(window.location.search).entries());
const dirEmptyNote = PARAMS.q ? 'No results' : DATA.dir_exists ? 'Empty folder' : 'Folder will be created when a file is uploaded';
/** /**
* @type Element * @type Element
*/ */
let $pathsTable; let $pathsTable;
/**
* @type Element
*/
let $pathsTableHead;
/** /**
* @type Element * @type Element
*/ */
@@ -175,6 +199,67 @@ function addBreadcrumb(href, uri_prefix) {
} }
} }
/**
* Render path table thead
*/
function renderPathsTableHead() {
const headerItems = [
{
name: "name",
props: `colspan="2"`,
text: "Name",
},
{
name: "mtime",
props: ``,
text: "Last Modified",
},
{
name: "size",
props: ``,
text: "Size",
}
];
$pathsTableHead.insertAdjacentHTML("beforeend", `
<tr>
${headerItems.map(item => {
let svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M11.5 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L11 2.707V14.5a.5.5 0 0 0 .5.5zm-7-14a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V1.5a.5.5 0 0 1 .5-.5z"/></svg>`;
let order = "asc";
if (PARAMS.sort === item.name) {
if (PARAMS.order === "asc") {
order = "desc";
svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5z"/></svg>`
} else {
svg = `<svg width="12" height="12" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8 1a.5.5 0 0 1 .5.5v11.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L7.5 13.293V1.5A.5.5 0 0 1 8 1z"/></svg>`
}
}
const qs = new URLSearchParams({...PARAMS, order, sort: item.name }).toString();
const icon = `<span>${svg}</span>`
return `<th class="cell-${item.name}" ${item.props}><a href="?${qs}">${item.text}${icon}</a></th>`
}).join("\n")}
<th class="cell-actions">Actions</th>
</tr>
`);
}
/**
* Render path table tbody
*/
function renderPathsTableBody() {
if (DATA.paths && DATA.paths.length > 0) {
const len = DATA.paths.length;
if (len > 0) {
$pathsTable.classList.remove("hidden");
}
for (let i = 0; i < len; i++) {
addPath(DATA.paths[i], i);
}
} else {
$emptyFolder.textContent = dirEmptyNote;
$emptyFolder.classList.remove("hidden");
}
}
/** /**
* Add pathitem * Add pathitem
* @param {PathItem} file * @param {PathItem} file
@@ -430,6 +515,7 @@ function encodedStr(rawStr) {
function ready() { function ready() {
document.title = `Index of ${DATA.href} - Dufs`; document.title = `Index of ${DATA.href} - Dufs`;
$pathsTable = document.querySelector(".paths-table") $pathsTable = document.querySelector(".paths-table")
$pathsTableHead = document.querySelector(".paths-table thead");
$pathsTableBody = document.querySelector(".paths-table tbody"); $pathsTableBody = document.querySelector(".paths-table tbody");
$uploadersTable = document.querySelector(".uploaders-table"); $uploadersTable = document.querySelector(".uploaders-table");
$emptyFolder = document.querySelector(".empty-folder"); $emptyFolder = document.querySelector(".empty-folder");
@@ -437,26 +523,15 @@ function ready() {
if (DATA.allow_search) { if (DATA.allow_search) {
document.querySelector(".searchbar").classList.remove("hidden"); document.querySelector(".searchbar").classList.remove("hidden");
if (params.q) { if (PARAMS.q) {
document.getElementById('search').value = params.q; document.getElementById('search').value = PARAMS.q;
} }
} }
addBreadcrumb(DATA.href, DATA.uri_prefix); addBreadcrumb(DATA.href, DATA.uri_prefix);
if (Array.isArray(DATA.paths)) { renderPathsTableHead();
const len = DATA.paths.length; renderPathsTableBody();
if (len > 0) {
$pathsTable.classList.remove("hidden");
}
for (let i = 0; i < len; i++) {
addPath(DATA.paths[i], i);
}
if (len == 0) {
$emptyFolder.textContent = dirEmptyNote;
$emptyFolder.classList.remove("hidden");
}
}
if (DATA.allow_upload) { if (DATA.allow_upload) {
dropzone(); dropzone();
if (DATA.allow_delete) { if (DATA.allow_delete) {

View File

@@ -1,4 +1,4 @@
use clap::{value_parser, AppSettings, Arg, ArgMatches, Command}; use clap::{value_parser, AppSettings, Arg, ArgAction, ArgMatches, Command};
use clap_complete::{generate, Generator, Shell}; use clap_complete::{generate, Generator, Shell};
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
use rustls::{Certificate, PrivateKey}; use rustls::{Certificate, PrivateKey};
@@ -30,7 +30,8 @@ pub fn build_cli() -> Command<'static> {
.long("bind") .long("bind")
.help("Specify bind address") .help("Specify bind address")
.multiple_values(true) .multiple_values(true)
.multiple_occurrences(true) .value_delimiter(',')
.action(ArgAction::Append)
.value_name("addr"), .value_name("addr"),
) )
.arg( .arg(
@@ -42,7 +43,7 @@ pub fn build_cli() -> Command<'static> {
.value_name("port"), .value_name("port"),
) )
.arg( .arg(
Arg::new("path") Arg::new("root")
.default_value(".") .default_value(".")
.allow_invalid_utf8(true) .allow_invalid_utf8(true)
.help("Specific path to serve"), .help("Specific path to serve"),
@@ -64,8 +65,9 @@ pub fn build_cli() -> Command<'static> {
.short('a') .short('a')
.long("auth") .long("auth")
.help("Add auth for path") .help("Add auth for path")
.action(ArgAction::Append)
.multiple_values(true) .multiple_values(true)
.multiple_occurrences(true) .value_delimiter(',')
.value_name("rule"), .value_name("rule"),
) )
.arg( .arg(
@@ -121,6 +123,13 @@ pub fn build_cli() -> Command<'static> {
Arg::new("render-spa") Arg::new("render-spa")
.long("render-spa") .long("render-spa")
.help("Serve SPA(Single Page Application)"), .help("Serve SPA(Single Page Application)"),
)
.arg(
Arg::new("assets")
.long("assets")
.help("Use custom assets to override builtin assets")
.allow_invalid_utf8(true)
.value_name("path")
); );
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
@@ -176,6 +185,7 @@ pub struct Args {
pub render_spa: bool, pub render_spa: bool,
pub render_try_index: bool, pub render_try_index: bool,
pub enable_cors: bool, pub enable_cors: bool,
pub assets_path: Option<PathBuf>,
pub log_http: LogHttp, pub log_http: LogHttp,
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
pub tls: Option<(Vec<Certificate>, PrivateKey)>, pub tls: Option<(Vec<Certificate>, PrivateKey)>,
@@ -195,7 +205,7 @@ impl Args {
.map(|v| v.collect()) .map(|v| v.collect())
.unwrap_or_else(|| vec!["0.0.0.0", "::"]); .unwrap_or_else(|| vec!["0.0.0.0", "::"]);
let addrs: Vec<IpAddr> = Args::parse_addrs(&addrs)?; let addrs: Vec<IpAddr> = Args::parse_addrs(&addrs)?;
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?; let path = Args::parse_path(matches.value_of_os("root").unwrap_or_default())?;
let path_is_file = path.metadata()?.is_file(); let path_is_file = path.metadata()?.is_file();
let path_prefix = matches let path_prefix = matches
.value_of("path-prefix") .value_of("path-prefix")
@@ -242,6 +252,10 @@ impl Args {
.value_of("log-format") .value_of("log-format")
.unwrap_or(DEFAULT_LOG_FORMAT) .unwrap_or(DEFAULT_LOG_FORMAT)
.parse()?; .parse()?;
let assets_path = match matches.value_of_os("assets") {
Some(v) => Some(Args::parse_assets_path(v)?),
None => None,
};
Ok(Args { Ok(Args {
addrs, addrs,
@@ -263,6 +277,7 @@ impl Args {
render_spa, render_spa,
tls, tls,
log_http, log_http,
assets_path,
}) })
} }
@@ -298,4 +313,12 @@ impl Args {
}) })
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into()) .map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
} }
fn parse_assets_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
let path = Self::parse_path(path)?;
if !path.join("index.html").exists() {
return Err(format!("Path `{}` doesn't contains index.html", path.display()).into());
}
Ok(path)
}
} }

View File

@@ -19,6 +19,8 @@ use hyper::header::{
}; };
use hyper::{Body, Method, StatusCode, Uri}; use hyper::{Body, Method, StatusCode, Uri};
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::Metadata; use std::fs::Metadata;
use std::io::SeekFrom; use std::io::SeekFrom;
use std::net::SocketAddr; use std::net::SocketAddr;
@@ -45,6 +47,7 @@ const BUF_SIZE: usize = 65536;
pub struct Server { pub struct Server {
args: Arc<Args>, args: Arc<Args>,
assets_prefix: String, assets_prefix: String,
html: Cow<'static, str>,
single_file_req_paths: Vec<String>, single_file_req_paths: Vec<String>,
running: Arc<AtomicBool>, running: Arc<AtomicBool>,
} }
@@ -65,11 +68,16 @@ impl Server {
} else { } else {
vec![] vec![]
}; };
let html = match args.assets_path.as_ref() {
Some(path) => Cow::Owned(std::fs::read_to_string(path.join("index.html")).unwrap()),
None => Cow::Borrowed(INDEX_HTML),
};
Self { Self {
args, args,
running, running,
single_file_req_paths, single_file_req_paths,
assets_prefix, assets_prefix,
html,
} }
} }
@@ -117,7 +125,7 @@ impl Server {
let headers = req.headers(); let headers = req.headers();
let method = req.method().clone(); let method = req.method().clone();
if method == Method::GET && self.handle_embed_assets(req_path, &mut res).await? { if method == Method::GET && self.handle_assets(req_path, headers, &mut res).await? {
return Ok(res); return Ok(res);
} }
@@ -160,6 +168,9 @@ impl Server {
let path = path.as_path(); let path = path.as_path();
let query = req.uri().query().unwrap_or_default(); let query = req.uri().query().unwrap_or_default();
let query_params: HashMap<String, String> = form_urlencoded::parse(query.as_bytes())
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let (is_miss, is_dir, is_file, size) = match fs::metadata(path).await.ok() { let (is_miss, is_dir, is_file, size) = match fs::metadata(path).await.ok() {
Some(meta) => (false, meta.is_dir(), meta.is_file(), meta.len()), Some(meta) => (false, meta.is_dir(), meta.is_file(), meta.len()),
@@ -182,27 +193,32 @@ impl Server {
Method::GET | Method::HEAD => { Method::GET | Method::HEAD => {
if is_dir { if is_dir {
if render_try_index { if render_try_index {
if query == "zip" { if query_params.contains_key("zip") {
self.handle_zip_dir(path, head_only, &mut res).await?; self.handle_zip_dir(path, head_only, &mut res).await?;
} else if allow_search && query.starts_with("q=") { } else if allow_search && query_params.contains_key("q") {
let q = decode_uri(&query[2..]).unwrap_or_default(); self.handle_search_dir(path, &query_params, head_only, &mut res)
self.handle_search_dir(path, &q, head_only, &mut res)
.await?; .await?;
} else { } else {
self.handle_render_index(path, headers, head_only, &mut res) self.handle_render_index(
.await?; path,
&query_params,
headers,
head_only,
&mut res,
)
.await?;
} }
} else if render_index || render_spa { } else if render_index || render_spa {
self.handle_render_index(path, headers, head_only, &mut res) self.handle_render_index(path, &query_params, headers, head_only, &mut res)
.await?; .await?;
} else if query == "zip" { } else if query_params.contains_key("zip") {
self.handle_zip_dir(path, head_only, &mut res).await?; self.handle_zip_dir(path, head_only, &mut res).await?;
} else if allow_search && query.starts_with("q=") { } else if allow_search && query_params.contains_key("q") {
let q = decode_uri(&query[2..]).unwrap_or_default(); self.handle_search_dir(path, &query_params, head_only, &mut res)
self.handle_search_dir(path, &q, head_only, &mut res)
.await?; .await?;
} else { } else {
self.handle_ls_dir(path, true, head_only, &mut res).await?; self.handle_ls_dir(path, true, &query_params, head_only, &mut res)
.await?;
} }
} else if is_file { } else if is_file {
self.handle_send_file(path, headers, head_only, &mut res) self.handle_send_file(path, headers, head_only, &mut res)
@@ -211,7 +227,8 @@ impl Server {
self.handle_render_spa(path, headers, head_only, &mut res) self.handle_render_spa(path, headers, head_only, &mut res)
.await?; .await?;
} else if allow_upload && req_path.ends_with('/') { } else if allow_upload && req_path.ends_with('/') {
self.handle_ls_dir(path, false, head_only, &mut res).await?; self.handle_ls_dir(path, false, &query_params, head_only, &mut res)
.await?;
} else { } else {
status_not_found(&mut res); status_not_found(&mut res);
} }
@@ -344,6 +361,7 @@ impl Server {
&self, &self,
path: &Path, path: &Path,
exist: bool, exist: bool,
query_params: &HashMap<String, String>,
head_only: bool, head_only: bool,
res: &mut Response, res: &mut Response,
) -> BoxResult<()> { ) -> BoxResult<()> {
@@ -357,13 +375,13 @@ impl Server {
} }
} }
}; };
self.send_index(path, paths, exist, head_only, res) self.send_index(path, paths, exist, query_params, head_only, res)
} }
async fn handle_search_dir( async fn handle_search_dir(
&self, &self,
path: &Path, path: &Path,
search: &str, query_params: &HashMap<String, String>,
head_only: bool, head_only: bool,
res: &mut Response, res: &mut Response,
) -> BoxResult<()> { ) -> BoxResult<()> {
@@ -372,7 +390,7 @@ impl Server {
let hidden = Arc::new(self.args.hidden.to_vec()); let hidden = Arc::new(self.args.hidden.to_vec());
let hidden = hidden.clone(); let hidden = hidden.clone();
let running = self.running.clone(); let running = self.running.clone();
let search = search.to_lowercase(); let search = query_params.get("q").unwrap().to_lowercase();
let search_paths = tokio::task::spawn_blocking(move || { let search_paths = tokio::task::spawn_blocking(move || {
let mut it = WalkDir::new(&path_buf).into_iter(); let mut it = WalkDir::new(&path_buf).into_iter();
let mut paths: Vec<PathBuf> = vec![]; let mut paths: Vec<PathBuf> = vec![];
@@ -405,7 +423,7 @@ impl Server {
paths.push(item); paths.push(item);
} }
} }
self.send_index(path, paths, true, head_only, res) self.send_index(path, paths, true, query_params, head_only, res)
} }
async fn handle_zip_dir( async fn handle_zip_dir(
@@ -445,6 +463,7 @@ impl Server {
async fn handle_render_index( async fn handle_render_index(
&self, &self,
path: &Path, path: &Path,
query_params: &HashMap<String, String>,
headers: &HeaderMap<HeaderValue>, headers: &HeaderMap<HeaderValue>,
head_only: bool, head_only: bool,
res: &mut Response, res: &mut Response,
@@ -459,7 +478,8 @@ impl Server {
self.handle_send_file(&index_path, headers, head_only, res) self.handle_send_file(&index_path, headers, head_only, res)
.await?; .await?;
} else if self.args.render_try_index { } else if self.args.render_try_index {
self.handle_ls_dir(path, true, head_only, res).await?; self.handle_ls_dir(path, true, query_params, head_only, res)
.await?;
} else { } else {
status_not_found(res) status_not_found(res)
} }
@@ -483,29 +503,40 @@ impl Server {
Ok(()) Ok(())
} }
async fn handle_embed_assets(&self, req_path: &str, res: &mut Response) -> BoxResult<bool> { async fn handle_assets(
&self,
req_path: &str,
headers: &HeaderMap<HeaderValue>,
res: &mut Response,
) -> BoxResult<bool> {
if let Some(name) = req_path.strip_prefix(&self.assets_prefix) { if let Some(name) = req_path.strip_prefix(&self.assets_prefix) {
match name { match self.args.assets_path.as_ref() {
"index.js" => { Some(assets_path) => {
*res.body_mut() = Body::from(INDEX_JS); let path = assets_path.join(name);
res.headers_mut().insert( self.handle_send_file(&path, headers, false, res).await?;
"content-type",
HeaderValue::from_static("application/javascript"),
);
}
"index.css" => {
*res.body_mut() = Body::from(INDEX_CSS);
res.headers_mut()
.insert("content-type", HeaderValue::from_static("text/css"));
}
"favicon.ico" => {
*res.body_mut() = Body::from(FAVICON_ICO);
res.headers_mut()
.insert("content-type", HeaderValue::from_static("image/x-icon"));
}
_ => {
return Ok(false);
} }
None => match name {
"index.js" => {
*res.body_mut() = Body::from(INDEX_JS);
res.headers_mut().insert(
"content-type",
HeaderValue::from_static("application/javascript"),
);
}
"index.css" => {
*res.body_mut() = Body::from(INDEX_CSS);
res.headers_mut()
.insert("content-type", HeaderValue::from_static("text/css"));
}
"favicon.ico" => {
*res.body_mut() = Body::from(FAVICON_ICO);
res.headers_mut()
.insert("content-type", HeaderValue::from_static("image/x-icon"));
}
_ => {
status_not_found(res);
}
},
} }
res.headers_mut().insert( res.headers_mut().insert(
"cache-control", "cache-control",
@@ -754,10 +785,30 @@ impl Server {
path: &Path, path: &Path,
mut paths: Vec<PathItem>, mut paths: Vec<PathItem>,
exist: bool, exist: bool,
query_params: &HashMap<String, String>,
head_only: bool, head_only: bool,
res: &mut Response, res: &mut Response,
) -> BoxResult<()> { ) -> BoxResult<()> {
paths.sort_unstable(); if let Some(sort) = query_params.get("sort") {
if sort == "name" {
paths.sort_by(|v1, v2| {
alphanumeric_sort::compare_str(v1.name.to_lowercase(), v2.name.to_lowercase())
})
} else if sort == "mtime" {
paths.sort_by(|v1, v2| v1.mtime.cmp(&v2.mtime))
} else if sort == "size" {
paths.sort_by(|v1, v2| v1.size.unwrap_or(0).cmp(&v2.size.unwrap_or(0)))
}
if query_params
.get("order")
.map(|v| v == "desc")
.unwrap_or_default()
{
paths.reverse()
}
} else {
paths.sort_unstable();
}
let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?)); let href = format!("/{}", normalize_path(path.strip_prefix(&self.args.path)?));
let data = IndexData { let data = IndexData {
href, href,
@@ -769,23 +820,10 @@ impl Server {
dir_exists: exist, dir_exists: exist,
}; };
let data = serde_json::to_string(&data).unwrap(); let data = serde_json::to_string(&data).unwrap();
let asset_js = format!("{}index.js", self.assets_prefix); let output = self
let asset_css = format!("{}index.css", self.assets_prefix); .html
let asset_ico = format!("{}favicon.ico", self.assets_prefix); .replace("__ASSERTS_PREFIX__", &self.assets_prefix)
let output = INDEX_HTML.replace( .replace("__INDEX_DATA__", &data);
"__SLOT__",
&format!(
r#"
<link rel="icon" type="image/x-icon" href="{}">
<link rel="stylesheet" href="{}">
<script>
DATA = {}
</script>
<script src="{}"></script>
"#,
asset_ico, asset_css, data, asset_js
),
);
res.headers_mut() res.headers_mut()
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8)); .typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
res.headers_mut() res.headers_mut()

View File

@@ -39,7 +39,7 @@ pub fn glob(source: &str, target: &str) -> bool {
continue 'outer; continue 'outer;
} }
} }
return true; return false;
} }
None => return true, None => return true,
}, },
@@ -80,4 +80,7 @@ fn test_glob_key() {
assert!(!glob("abc", "adc")); assert!(!glob("abc", "adc"));
assert!(!glob("abc", "abcd")); assert!(!glob("abc", "abcd"));
assert!(!glob("a?c", "abbc")); assert!(!glob("a?c", "abbc"));
assert!(!glob("*.log", "log"));
assert!(glob("*.log", ".log"));
assert!(glob("*.log", "a.log"));
} }

View File

@@ -1,8 +1,11 @@
mod fixtures; mod fixtures;
mod utils; mod utils;
use fixtures::{server, Error, TestServer}; use assert_cmd::prelude::*;
use assert_fs::fixture::TempDir;
use fixtures::{port, server, tmpdir, wait_for_port, Error, TestServer, DIR_ASSETS};
use rstest::rstest; use rstest::rstest;
use std::process::{Command, Stdio};
#[rstest] #[rstest]
fn assets(server: TestServer) -> Result<(), Error> { fn assets(server: TestServer) -> Result<(), Error> {
@@ -91,3 +94,29 @@ fn asset_js_with_prefix(
); );
Ok(()) Ok(())
} }
#[rstest]
fn assets_override(tmpdir: TempDir, port: u16) -> Result<(), Error> {
let mut child = Command::cargo_bin("dufs")?
.arg(tmpdir.path())
.arg("-p")
.arg(port.to_string())
.arg("--assets")
.arg(tmpdir.join(DIR_ASSETS))
.stdout(Stdio::piped())
.spawn()?;
wait_for_port(port);
let url = format!("http://localhost:{}", port);
let resp = reqwest::blocking::get(&url)?;
assert!(resp.text()?.starts_with(&format!(
"/__dufs_v{}_index.js;DATA",
env!("CARGO_PKG_VERSION")
)));
let resp = reqwest::blocking::get(&url)?;
assert_resp_paths!(resp);
child.kill()?;
Ok(())
}

View File

@@ -27,9 +27,13 @@ pub static DIR_NO_INDEX: &str = "dir-no-index/";
#[allow(dead_code)] #[allow(dead_code)]
pub static DIR_GIT: &str = ".git/"; pub static DIR_GIT: &str = ".git/";
/// Directory names for testings assets override
#[allow(dead_code)]
pub static DIR_ASSETS: &str = "dir-assets/";
/// Directory names for testing purpose /// Directory names for testing purpose
#[allow(dead_code)] #[allow(dead_code)]
pub static DIRECTORIES: &[&str] = &["dira/", "dirb/", "dirc/", DIR_NO_INDEX, DIR_GIT]; pub static DIRECTORIES: &[&str] = &["dira/", "dirb/", "dirc/", DIR_NO_INDEX, DIR_GIT, DIR_ASSETS];
/// Test fixture which creates a temporary directory with a few files and directories inside. /// Test fixture which creates a temporary directory with a few files and directories inside.
/// The directories also contain files. /// The directories also contain files.
@@ -44,14 +48,21 @@ pub fn tmpdir() -> TempDir {
.expect("Couldn't write to file"); .expect("Couldn't write to file");
} }
for directory in DIRECTORIES { for directory in DIRECTORIES {
for file in FILES { if *directory == DIR_ASSETS {
if *directory == DIR_NO_INDEX && *file == "index.html" {
continue;
}
tmpdir tmpdir
.child(format!("{}{}", directory, file)) .child(format!("{}{}", directory, "index.html"))
.write_str(&format!("This is {}{}", directory, file)) .write_str("__ASSERTS_PREFIX__index.js;DATA = __INDEX_DATA__")
.expect("Couldn't write to file"); .expect("Couldn't write to file");
} else {
for file in FILES {
if *directory == DIR_NO_INDEX && *file == "index.html" {
continue;
}
tmpdir
.child(format!("{}{}", directory, file))
.write_str(&format!("This is {}{}", directory, file))
.expect("Couldn't write to file");
}
} }
} }
@@ -93,34 +104,6 @@ where
TestServer::new(port, tmpdir, child, is_tls) TestServer::new(port, tmpdir, child, is_tls)
} }
/// Same as `server()` but ignore stderr
#[fixture]
#[allow(dead_code)]
pub fn server_no_stderr<I>(#[default(&[] as &[&str])] args: I) -> TestServer
where
I: IntoIterator + Clone,
I::Item: AsRef<std::ffi::OsStr>,
{
let port = port();
let tmpdir = tmpdir();
let child = Command::cargo_bin("dufs")
.expect("Couldn't find test binary")
.arg(tmpdir.path())
.arg("-p")
.arg(port.to_string())
.args(args.clone())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Couldn't run test binary");
let is_tls = args
.into_iter()
.any(|x| x.as_ref().to_str().unwrap().contains("tls"));
wait_for_port(port);
TestServer::new(port, tmpdir, child, is_tls)
}
/// Wait a max of 1s for the port to become available. /// Wait a max of 1s for the port to become available.
pub fn wait_for_port(port: u16) { pub fn wait_for_port(port: u16) {
let start_wait = Instant::now(); let start_wait = Instant::now();

View File

@@ -11,11 +11,25 @@ fn hidden_get_dir(#[case] server: TestServer, #[case] exist: bool) -> Result<(),
let resp = reqwest::blocking::get(server.url())?; let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 200); assert_eq!(resp.status(), 200);
let paths = utils::retrieve_index_paths(&resp.text()?); let paths = utils::retrieve_index_paths(&resp.text()?);
assert!(paths.contains("dira/"));
assert_eq!(paths.contains(".git/"), exist); assert_eq!(paths.contains(".git/"), exist);
assert_eq!(paths.contains("index.html"), exist); assert_eq!(paths.contains("index.html"), exist);
Ok(()) Ok(())
} }
#[rstest]
#[case(server(&[] as &[&str]), true)]
#[case(server(&["--hidden", "*.html"]), false)]
fn hidden_get_dir2(#[case] server: TestServer, #[case] exist: bool) -> Result<(), Error> {
let resp = reqwest::blocking::get(server.url())?;
assert_eq!(resp.status(), 200);
let paths = utils::retrieve_index_paths(&resp.text()?);
assert!(paths.contains("dira/"));
assert_eq!(paths.contains("index.html"), exist);
assert_eq!(paths.contains("test.html"), exist);
Ok(())
}
#[rstest] #[rstest]
#[case(server(&[] as &[&str]), true)] #[case(server(&[] as &[&str]), true)]
#[case(server(&["--hidden", ".git,index.html"]), false)] #[case(server(&["--hidden", ".git,index.html"]), false)]
@@ -23,6 +37,7 @@ fn hidden_propfind_dir(#[case] server: TestServer, #[case] exist: bool) -> Resul
let resp = fetch!(b"PROPFIND", server.url()).send()?; let resp = fetch!(b"PROPFIND", server.url()).send()?;
assert_eq!(resp.status(), 207); assert_eq!(resp.status(), 207);
let body = resp.text()?; let body = resp.text()?;
assert!(body.contains("<D:href>/dira/</D:href>"));
assert_eq!(body.contains("<D:href>/.git/</D:href>"), exist); assert_eq!(body.contains("<D:href>/.git/</D:href>"), exist);
assert_eq!(body.contains("<D:href>/index.html</D:href>"), exist); assert_eq!(body.contains("<D:href>/index.html</D:href>"), exist);
Ok(()) Ok(())

29
tests/sort.rs Normal file
View File

@@ -0,0 +1,29 @@
mod fixtures;
mod utils;
use fixtures::{server, Error, TestServer};
use rstest::rstest;
#[rstest]
fn ls_dir_sort_by_name(server: TestServer) -> Result<(), Error> {
let url = server.url();
let resp = reqwest::blocking::get(format!("{}?sort=name&order=asc", url))?;
let paths1 = self::utils::retrieve_index_paths(&resp.text()?);
let resp = reqwest::blocking::get(format!("{}?sort=name&order=desc", url))?;
let mut paths2 = self::utils::retrieve_index_paths(&resp.text()?);
paths2.reverse();
assert_eq!(paths1, paths2);
Ok(())
}
#[rstest]
fn search_dir_sort_by_name(server: TestServer) -> Result<(), Error> {
let url = server.url();
let resp = reqwest::blocking::get(format!("{}?q={}&sort=name&order=asc", url, "test.html"))?;
let paths1 = self::utils::retrieve_index_paths(&resp.text()?);
let resp = reqwest::blocking::get(format!("{}?q={}&sort=name&order=desc", url, "test.html"))?;
let mut paths2 = self::utils::retrieve_index_paths(&resp.text()?);
paths2.reverse();
assert_eq!(paths1, paths2);
Ok(())
}

View File

@@ -1,5 +1,5 @@
use indexmap::IndexSet;
use serde_json::Value; use serde_json::Value;
use std::collections::HashSet;
#[macro_export] #[macro_export]
macro_rules! assert_resp_paths { macro_rules! assert_resp_paths {
@@ -25,7 +25,7 @@ macro_rules! fetch {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn retrieve_index_paths(index: &str) -> HashSet<String> { pub fn retrieve_index_paths(index: &str) -> IndexSet<String> {
retrieve_index_paths_impl(index).unwrap_or_default() retrieve_index_paths_impl(index).unwrap_or_default()
} }
@@ -35,10 +35,11 @@ pub fn encode_uri(v: &str) -> String {
parts.join("/") parts.join("/")
} }
fn retrieve_index_paths_impl(index: &str) -> Option<HashSet<String>> { fn retrieve_index_paths_impl(index: &str) -> Option<IndexSet<String>> {
let lines: Vec<&str> = index.lines().collect(); let lines: Vec<&str> = index.lines().collect();
let line = lines.iter().find(|v| v.contains("DATA ="))?; let line = lines.iter().find(|v| v.contains("DATA ="))?;
let value: Value = line[7..].parse().ok()?; let line_col = line.find("DATA =").unwrap() + 6;
let value: Value = line[line_col..].parse().ok()?;
let paths = value let paths = value
.get("paths")? .get("paths")?
.as_array()? .as_array()?