aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock11
-rw-r--r--Cargo.toml4
-rw-r--r--README.md3
-rw-r--r--src/listing.rs90
4 files changed, 100 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5ad9a75..13bc678 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -276,6 +276,14 @@ dependencies = [
]
[[package]]
+name = "chrono-humanize"
+version = "0.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "clap"
version = "2.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -719,6 +727,8 @@ dependencies = [
"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)",
+ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"nanoid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1821,6 +1831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)" = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
+"checksum chrono-humanize 0.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2ff48a655fe8d2dae9a39e66af7fd8ff32a879e8c4e27422c25596a8b5e90d"
"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum cookie 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1465f8134efa296b4c19db34d909637cb2bf0f7aaf21299e23e18fa29ac557cf"
diff --git a/Cargo.toml b/Cargo.toml
index b556e27..510256a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,4 +29,6 @@ htmlescape = "0.3.1"
bytesize = "1.0.0"
nanoid = "0.2.0"
alphanumeric-sort = "1.0.6"
-structopt = "0.2.14" \ No newline at end of file
+structopt = "0.2.14"
+chrono = "0.4.6"
+chrono-humanize = "0.0.11" \ No newline at end of file
diff --git a/README.md b/README.md
index 8b59706..7c5e149 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,9 @@ Sometimes this is just a more practical and quick way than doing things properly
# 11
# 3
+ miniserve --sort=date /tmp/myshare
+ # list files in chronological order (most recent files on top of the list)
+
miniserve --reverse /tmp/myshare
# 11
# 3
diff --git a/src/listing.rs b/src/listing.rs
index 056c847..21aebec 100644
--- a/src/listing.rs
+++ b/src/listing.rs
@@ -1,5 +1,7 @@
use actix_web::{fs, HttpRequest, HttpResponse, Result};
use bytesize::ByteSize;
+use chrono::{DateTime, Duration, Utc};
+use chrono_humanize::{Accuracy, HumanTime, Tense};
use clap::{_clap_count_exprs, arg_enum};
use htmlescape::encode_minimal as escape_html_entity;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
@@ -7,6 +9,7 @@ use std::cmp::Ordering;
use std::fmt::Write as FmtWrite;
use std::io;
use std::path::Path;
+use std::time::SystemTime;
arg_enum! {
#[derive(Clone, Copy, Debug)]
@@ -20,10 +23,13 @@ arg_enum! {
///
/// 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
}
}
@@ -60,6 +66,9 @@ struct Entry {
/// Size in byte of the entry. Only available for EntryType::File
size: Option<bytesize::ByteSize>,
+
+ /// Last modification date
+ last_modification_date: Option<SystemTime>,
}
impl Entry {
@@ -68,12 +77,14 @@ impl Entry {
entry_type: EntryType,
link: String,
size: Option<bytesize::ByteSize>,
+ last_modification_date: Option<SystemTime>,
) -> Self {
Entry {
name,
entry_type,
link,
size,
+ last_modification_date,
}
}
}
@@ -128,14 +139,26 @@ pub fn directory_listing<S>(
if skip_symlinks && metadata.file_type().is_symlink() {
continue;
}
+ let last_modification_date = match metadata.modified() {
+ Ok(date) => Some(date),
+ Err(_) => None,
+ };
+
if metadata.is_dir() {
- entries.push(Entry::new(file_name, EntryType::Directory, file_url, None));
+ entries.push(Entry::new(
+ file_name,
+ EntryType::Directory,
+ file_url,
+ None,
+ last_modification_date,
+ ));
} else {
entries.push(Entry::new(
file_name,
EntryType::File,
file_url,
Some(ByteSize::b(metadata.len())),
+ last_modification_date,
));
}
} else {
@@ -155,28 +178,39 @@ pub fn directory_listing<S>(
entries.sort_by_key(|e| e.name.clone());
entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap());
}
+ 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();
}
-
for entry in entries {
+ let (modification_date, modification_time) = convert_to_utc(entry.last_modification_date);
+
match entry.entry_type {
EntryType::Directory => {
let _ = write!(
body,
- "<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td></tr>",
- entry.link, entry.name
+ "<tr><td><a class=\"directory\" href=\"{}\">{}/</a></td><td></td><td class=\"date-cell\"><span>{}</span><span>{}</span><span>{}</span></td></tr>",
+ entry.link, entry.name, modification_date, modification_time, humanize_systemtime(entry.last_modification_date)
);
}
EntryType::File => {
let _ = write!(
body,
- "<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td></tr>",
+ "<tr><td><a class=\"file\" href=\"{}\">{}</a></td><td>{}</td><td class=\"date-cell\"><span>{}</span><span>{}</span><span>{}</span></td></tr>",
entry.link,
entry.name,
- entry.size.unwrap()
+ entry.size.unwrap(),
+ modification_date,
+ modification_time,
+ humanize_systemtime(entry.last_modification_date)
);
}
}
@@ -210,6 +244,7 @@ pub fn directory_listing<S>(
color: #777c82;\
text-align: left;\
line-height: 1.125rem;\
+ width: 33.333%;\
}}\
table thead tr th {{\
padding: 0.5rem 0.625rem 0.625rem;\
@@ -236,6 +271,17 @@ pub fn directory_listing<S>(
a:visited {{\
color: #8e44ad;\
}}\
+ td.date-cell {{\
+ display: flex;\
+ width: calc(100% - 1.25rem);\
+ }}\
+ td.date-cell span:first-of-type,\
+ td.date-cell span:nth-of-type(2) {{\
+ flex-basis:4.5rem;\
+ }}\
+ td.date-cell span:nth-of-type(3) {{\
+ color: #c5c5c5;\
+ }}\
@media (max-width: 600px) {{\
h1 {{\
font-size: 1.375em;\
@@ -250,7 +296,7 @@ pub fn directory_listing<S>(
</head>\
<body><h1>{}</h1>\
<table>\
- <thead><th>Name</th><th>Size</th></thead>\
+ <thead><th>Name</th><th>Size</th><th>Last modification</th></thead>\
<tbody>\
{}\
</tbody></table></body>\n</html>",
@@ -260,3 +306,33 @@ pub fn directory_listing<S>(
.content_type("text/html; charset=utf-8")
.body(html))
}
+
+/// Converts a SystemTime object to a strings tuple (date, time)
+/// Date is formatted as %e %b, e.g. Jul 12
+/// Time is formatted as %R, e.g. 22:34
+///
+/// If no SystemTime was given, returns a tuple containing empty strings
+fn convert_to_utc(src_time: Option<SystemTime>) -> (String, String) {
+ src_time
+ .map(|time| DateTime::<Utc>::from(time))
+ .map(|date_time| {
+ (
+ date_time.format("%e %b").to_string(),
+ date_time.format("%R").to_string(),
+ )
+ })
+ .unwrap_or_default()
+}
+
+/// Converts a SystemTime to a string readable by a human,
+/// i.e. calculates the duration between now() and the given SystemTime,
+/// and gives a rough approximation of the elapsed time since
+///
+/// If no SystemTime was given, returns an empty string
+fn humanize_systemtime(src_time: Option<SystemTime>) -> String {
+ src_time
+ .and_then(|std_time| SystemTime::now().duration_since(std_time).ok())
+ .and_then(|from_now| Duration::from_std(from_now).ok())
+ .map(|duration| HumanTime::from(duration).to_text_en(Accuracy::Rough, Tense::Past))
+ .unwrap_or_default()
+}