Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removed sorting from CLI + added sorting from HTML #44

Merged
merged 16 commits into from Mar 6, 2019
Merged
27 changes: 21 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -36,3 +36,4 @@ structopt = "0.2.14"
chrono = "0.4.6"
chrono-humanize = "0.0.11"
maud = { version = "0.20.0", features = ["actix-web"] }
serde = { version = "1.0.89", features = ["derive"] }
18 changes: 0 additions & 18 deletions src/args.rs
Expand Up @@ -3,7 +3,6 @@ use std::path::PathBuf;
use structopt::StructOpt;

use crate::auth;
use crate::listing;

/// Possible characters for random routes
const ROUTE_ALPHABET: [char; 16] = [
Expand Down Expand Up @@ -45,21 +44,6 @@ struct CLIArgs {
#[structopt(long = "random-route")]
random_route: bool,

/// Sort files
#[structopt(
short = "s",
long = "sort",
raw(
possible_values = "&listing::SortingMethods::variants()",
case_insensitive = "true"
)
)]
sort_method: Option<listing::SortingMethods>,

/// Reverse sorting
#[structopt(long = "reverse")]
reverse_sort: bool,

/// Do not follow symbolic links
#[structopt(short = "P", long = "no-symlinks")]
no_symlinks: bool,
Expand Down Expand Up @@ -116,7 +100,5 @@ pub fn parse_args() -> crate::MiniserveConfig {
path_explicitly_chosen,
no_symlinks: args.no_symlinks,
random_route,
sort_method: args.sort_method.unwrap_or(listing::SortingMethods::Natural),
reverse_sort: args.reverse_sort,
}
}
157 changes: 101 additions & 56 deletions src/listing.rs
@@ -1,34 +1,67 @@
use actix_web::{fs, HttpRequest, HttpResponse, Result};
use actix_web::{fs, FromRequest, HttpRequest, HttpResponse, Query, Result};
use bytesize::ByteSize;
use clap::{_clap_count_exprs, arg_enum};
use htmlescape::encode_minimal as escape_html_entity;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use std::cmp::Ordering;
use serde::Deserialize;
use std::io;
use std::path::Path;
use std::time::SystemTime;

use crate::renderer;

arg_enum! {
#[derive(Clone, Copy, Debug)]
/// Available sorting methods
///
/// Natural: natural sorting method
/// 1 -> 2 -> 3 -> 11
///
/// Alpha: pure alphabetical sorting method
/// 1 -> 11 -> 2 -> 3
///
/// DirsFirst: directories are listed first, alphabetical sorting is also applied
/// 1/ -> 2/ -> 3/ -> 11 -> 12
///
/// Date: sort by last modification date (most recent first)
pub enum SortingMethods {
Natural,
Alpha,
DirsFirst,
Date
/// Query parameters
#[derive(Debug, Deserialize)]
struct QueryParameters {
sort: Option<SortingMethod>,
order: Option<SortingOrder>,
}

/// Available sorting methods
#[derive(Debug, Deserialize, Clone)]
pub enum SortingMethod {
/// Sort by name
#[serde(alias = "name")]
Name,

/// Sort by size
#[serde(alias = "size")]
Size,

/// Sort by last modification date (natural sort: follows alphanumerical order)
#[serde(alias = "date")]
Date,
}

impl SortingMethod {
pub fn to_string(&self) -> String {
match &self {
SortingMethod::Name => "name",
SortingMethod::Size => "size",
SortingMethod::Date => "date",
}
.to_string()
}
}

/// Available sorting orders
#[derive(Debug, Deserialize, Clone)]
pub enum SortingOrder {
/// Ascending order
#[serde(alias = "asc")]
Ascending,

/// Descending order
#[serde(alias = "desc")]
Descending,
}

impl SortingOrder {
pub fn to_string(&self) -> String {
match &self {
SortingOrder::Ascending => "asc",
SortingOrder::Descending => "desc",
}
.to_string()
}
}

Expand All @@ -42,16 +75,6 @@ pub enum EntryType {
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),
}
}
}

/// Entry
pub struct Entry {
/// Name of the entry
Expand Down Expand Up @@ -104,15 +127,21 @@ pub fn directory_listing<S>(
req: &HttpRequest<S>,
skip_symlinks: bool,
random_route: Option<String>,
sort_method: SortingMethods,
reverse_sort: bool,
) -> Result<HttpResponse, io::Error> {
let title = format!("Index of {}", req.path());
let base = Path::new(req.path());
let random_route = format!("/{}", random_route.unwrap_or_default());
let is_root = base.parent().is_none() || req.path() == random_route;
let page_parent = base.parent().map(|p| p.display().to_string());

let mut sort_method: Option<SortingMethod> = None;
let mut sort_order: Option<SortingOrder> = None;

if let Ok(query) = Query::<QueryParameters>::extract(req) {
This conversation was marked as resolved.
Show resolved Hide resolved
sort_method = query.sort.clone();
sort_order = query.order.clone();
}

let mut entries: Vec<Entry> = Vec::new();

for entry in dir.path.read_dir()? {
Expand Down Expand Up @@ -161,31 +190,47 @@ pub 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());
if let Some(sorting_method) = &sort_method {
match sorting_method {
SortingMethod::Name => entries
.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())),
SortingMethod::Size => entries.sort_by(|e1, e2| {
// If we can't get the size of the entry (directory for instance)
// let's consider it's 0b
e2.size
.unwrap_or_else(|| ByteSize::b(0))
.cmp(&e1.size.unwrap_or_else(|| ByteSize::b(0)))
}),
SortingMethod::Date => entries.sort_by(|e1, e2| {
// If, for some reason, we can't get the last modification date of an entry
// let's consider it was modified on UNIX_EPOCH (01/01/19270 00:00:00)
e2.last_modification_date
.unwrap_or(SystemTime::UNIX_EPOCH)
.cmp(&e1.last_modification_date.unwrap_or(SystemTime::UNIX_EPOCH))
}),
};
} else {
// Sort in alphanumeric order by default
entries.sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone()))
}

if let Some(sorting_order) = &sort_order {
if let SortingOrder::Descending = sorting_order {
entries.reverse()
}
SortingMethods::Date => entries.sort_by(|e1, e2| {
// If, for some reason, we can't get the last modification date of an entry
// let's consider it was modified on UNIX_EPOCH (01/01/19270 00:00:00)
e2.last_modification_date
.unwrap_or(SystemTime::UNIX_EPOCH)
.cmp(&e1.last_modification_date.unwrap_or(SystemTime::UNIX_EPOCH))
}),
};

if reverse_sort {
entries.reverse();
}

Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(renderer::page(&title, entries, is_root, page_parent).into_string()))
.body(
renderer::page(
&title,
entries,
is_root,
page_parent,
sort_method,
sort_order,
)
.into_string(),
))
}
17 changes: 1 addition & 16 deletions src/main.rs
Expand Up @@ -40,12 +40,6 @@ pub struct MiniserveConfig {

/// Enable random route generation
pub random_route: Option<String>,

/// Sort files/directories
pub sort_method: listing::SortingMethods,

/// Enable inverse sorting
pub reverse_sort: bool,
}

fn main() {
Expand Down Expand Up @@ -181,8 +175,6 @@ 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;
let reverse_sort = app.state().reverse_sort;
if path.is_file() {
None
} else {
Expand All @@ -191,14 +183,7 @@ fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> {
.expect("Couldn't create path")
.show_files_listing()
.files_listing_renderer(move |dir, req| {
listing::directory_listing(
dir,
req,
no_symlinks,
random_route.clone(),
sort_method,
reverse_sort,
)
listing::directory_listing(dir, req, no_symlinks, random_route.clone())
}),
)
}
Expand Down