Implemented sorting

This commit is contained in:
boasting-squirrel 2019-02-04 20:28:06 +01:00
parent ba9b92a415
commit 4585d5456c
4 changed files with 156 additions and 14 deletions

7
Cargo.lock generated
View File

@ -126,6 +126,11 @@ dependencies = [
"memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "alphanumeric-sort"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ansi_term"
version = "0.11.0"
@ -688,6 +693,7 @@ version = "0.3.0"
dependencies = [
"actix 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
"actix-web 0.7.18 (registry+https://github.com/rust-lang/crates.io-index)",
"alphanumeric-sort 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bytesize 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1720,6 +1726,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum actix_derive 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4300e9431455322ae393d43a2ba1ef96b8080573c0fc23b196219efedfb6ba69"
"checksum adler32 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7e522997b529f05601e05166c07ed17789691f562762c7f3b987263d2dedee5c"
"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
"checksum alphanumeric-sort 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7cd2580c95c654d681db0194a310af67a293f5e1c8bafa5b35b63269c4665a39"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arc-swap 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1025aeae2b664ca0ea726a89d574fe8f4e77dd712d443236ad1de00379450cf6"
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"

View File

@ -27,4 +27,5 @@ base64 = "0.10"
percent-encoding = "1.0.1"
htmlescape = "0.3.1"
bytesize = "1.0.0"
nanoid = "0.2.0"
nanoid = "0.2.0"
alphanumeric-sort = "1.0.6"

View File

@ -34,6 +34,25 @@ Sometimes this is just a more practical and quick way than doing things properly
miniserve -i 192.168.0.1 -i 10.13.37.10 -i ::1 -- /tmp/myshare
### Sort files for easier navigation
miniserve --sort=natural /tmp/myshare # (default behaviour)
# 1/
# 2/
# 3
# 11
miniserve --sort=alpha /tmp/myshare
# 1/
# 11
# 2/
# 3
miniserve --sort=dirsfirst /tmp/myshare
# 1/
# 2/
# 11
# 3
## Features
- Easy to use

View File

@ -6,10 +6,12 @@ use clap::{crate_authors, crate_description, crate_name, crate_version};
use htmlescape::encode_minimal as escape_html_entity;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use simplelog::{Config, LevelFilter, TermLogger};
use std::cmp::Ordering;
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::thread;
use std::time::Duration;
use yansi::{Color, Paint};
@ -29,6 +31,13 @@ struct BasicAuthParams {
password: String,
}
#[derive(Clone, Debug)]
enum SortingMethods {
Natural,
Alpha,
DirsFirst,
}
#[derive(Clone, Debug)]
pub struct MiniserveConfig {
verbose: bool,
@ -39,6 +48,59 @@ pub struct MiniserveConfig {
path_explicitly_chosen: bool,
no_symlinks: bool,
random_route: Option<String>,
sort_method: SortingMethods,
}
#[derive(PartialEq)]
enum EntryType {
Directory,
File,
}
impl PartialOrd for EntryType {
fn partial_cmp(&self, other: &EntryType) -> Option<Ordering> {
match (self, other) {
(EntryType::Directory, EntryType::File) => Some(Ordering::Less),
(EntryType::File, EntryType::Directory) => Some(Ordering::Greater),
_ => Some(Ordering::Equal),
}
}
}
struct Entry {
name: String,
entry_type: EntryType,
link: String,
size: Option<bytesize::ByteSize>,
}
impl Entry {
fn new(
name: String,
entry_type: EntryType,
link: String,
size: Option<bytesize::ByteSize>,
) -> Self {
Entry {
name,
entry_type,
link,
size,
}
}
}
impl FromStr for SortingMethods {
type Err = ();
fn from_str(s: &str) -> Result<SortingMethods, ()> {
match s {
"natural" => Ok(SortingMethods::Natural),
"alpha" => Ok(SortingMethods::Alpha),
"dirsfirst" => Ok(SortingMethods::DirsFirst),
_ => Err(()),
}
}
}
/// Decode a HTTP basic auth string into a tuple of username and password.
@ -140,6 +202,14 @@ pub fn parse_args() -> MiniserveConfig {
.long("random-route")
.help("Generate a random 6-hexdigit route"),
)
.arg(
Arg::with_name("sort")
.short("s")
.long("sort")
.possible_values(&["natural", "alpha", "dirsfirst"])
.default_value("natural")
.help("Sort results"),
)
.arg(
Arg::with_name("no-symlinks")
.short("P")
@ -180,6 +250,12 @@ pub fn parse_args() -> MiniserveConfig {
None
};
let sort_method = matches
.value_of("sort")
.unwrap()
.parse::<SortingMethods>()
.unwrap();
MiniserveConfig {
verbose,
path: PathBuf::from(path.unwrap_or(".")),
@ -189,6 +265,7 @@ pub fn parse_args() -> MiniserveConfig {
path_explicitly_chosen: path.is_some(),
no_symlinks,
random_route,
sort_method,
}
}
@ -202,6 +279,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
let path = &app.state().path;
let no_symlinks = app.state().no_symlinks;
let random_route = app.state().random_route.clone();
let sort_method = app.state().sort_method.clone();
if path.is_file() {
None
} else {
@ -210,7 +288,13 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
.expect("Couldn't create path")
.show_files_listing()
.files_listing_renderer(move |dir, req| {
directory_listing(dir, req, no_symlinks, random_route.clone())
directory_listing(
dir,
req,
no_symlinks,
random_route.clone(),
sort_method.clone(),
)
}),
)
}
@ -399,6 +483,7 @@ fn directory_listing<S>(
req: &HttpRequest<S>,
skip_symlinks: bool,
random_route: Option<String>,
sort_method: SortingMethods,
) -> Result<HttpResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
@ -415,6 +500,8 @@ fn directory_listing<S>(
}
}
let mut entries: Vec<Entry> = Vec::new();
for entry in dir.path.read_dir()? {
if dir.is_visible(&entry) {
let entry = entry.unwrap();
@ -433,21 +520,15 @@ fn directory_listing<S>(
if skip_symlinks && metadata.file_type().is_symlink() {
continue;
}
if metadata.is_dir() {
let _ = write!(
body,
"<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td></tr>",
file_url, file_name
);
entries.push(Entry::new(file_name, EntryType::Directory, file_url, None));
} else {
let _ = write!(
body,
"<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td></tr>",
file_url,
entries.push(Entry::new(
file_name,
ByteSize::b(metadata.len())
);
EntryType::File,
file_url,
Some(ByteSize::b(metadata.len())),
));
}
} else {
continue;
@ -455,6 +536,40 @@ fn directory_listing<S>(
}
}
match sort_method {
SortingMethods::Natural => entries
.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())),
SortingMethods::Alpha => {
entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap());
entries.sort_by_key(|e| e.name.clone())
}
SortingMethods::DirsFirst => {
entries.sort_by_key(|e| e.name.clone());
entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap());
}
};
for entry in entries {
match entry.entry_type {
EntryType::Directory => {
let _ = write!(
body,
"<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td></tr>",
entry.link, entry.name
);
}
EntryType::File => {
let _ = write!(
body,
"<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td></tr>",
entry.link,
entry.name,
entry.size.unwrap()
);
}
}
}
let html = format!(
"<html>\
<head>\