From 352c9ae8f2ca66f703bab3172daf98528c182bf9 Mon Sep 17 00:00:00 2001 From: Ignat Loskutov Date: Thu, 2 Oct 2025 02:08:10 +0200 Subject: [PATCH 1/2] Torrent creation: add support for mtime-based file sorting --- src/file_info.rs | 6 ++++++ src/hasher.rs | 1 + src/metainfo.rs | 2 ++ src/mode.rs | 1 + src/sort_key.rs | 1 + src/sort_spec.rs | 1 + src/subcommand/torrent/create.rs | 11 ++++++++++- src/walker.rs | 1 + 8 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/file_info.rs b/src/file_info.rs index 06294114..aa4da6a1 100644 --- a/src/file_info.rs +++ b/src/file_info.rs @@ -9,5 +9,11 @@ pub(crate) struct FileInfo { default, with = "unwrap_or_skip" )] + pub(crate) mtime: Option, + #[serde( + skip_serializing_if = "Option::is_none", + default, + with = "unwrap_or_skip" + )] pub(crate) md5sum: Option, } diff --git a/src/hasher.rs b/src/hasher.rs index cc06ac1c..ee0c5e68 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -73,6 +73,7 @@ impl Hasher { files.push(FileInfo { path: file_path.clone(), + mtime: None, md5sum, length, }); diff --git a/src/metainfo.rs b/src/metainfo.rs index 9e4098b4..82bcac6e 100644 --- a/src/metainfo.rs +++ b/src/metainfo.rs @@ -171,6 +171,7 @@ impl Metainfo { files: vec![FileInfo { length: Bytes(32 * 1024), path: FilePath::from_components(&["DIR", "FILE"]), + mtime: None, md5sum: Some(Md5Digest::from_hex("000102030405060708090a0b0c0d0e0f")), }], }; @@ -231,6 +232,7 @@ impl Metainfo { mode: Mode::Multiple { files: vec![FileInfo { length: Bytes(1024), + mtime: None, md5sum: None, path: FilePath::from_components(&["a", "b"]), }], diff --git a/src/mode.rs b/src/mode.rs index be9d06ae..cd7d9c8a 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -86,6 +86,7 @@ mod tests { length: Bytes(10), path: FilePath::from_components(&["foo", "bar"]), md5sum: Some(Md5Digest::from_hex("000102030405060708090a0b0c0d0e0f")), + mtime: None, }], }; diff --git a/src/sort_key.rs b/src/sort_key.rs index d4b0da1c..479058e9 100644 --- a/src/sort_key.rs +++ b/src/sort_key.rs @@ -5,6 +5,7 @@ use crate::common::*; pub(crate) enum SortKey { Path, Size, + Mtime, } impl SortKey { diff --git a/src/sort_spec.rs b/src/sort_spec.rs index b627f73f..984f8996 100644 --- a/src/sort_spec.rs +++ b/src/sort_spec.rs @@ -25,6 +25,7 @@ impl SortSpec { let ordering = match self.key { SortKey::Path => a.path.cmp(&b.path), SortKey::Size => a.length.cmp(&b.length), + SortKey::Mtime => a.mtime.cmp(&b.mtime), }; match self.order { diff --git a/src/subcommand/torrent/create.rs b/src/subcommand/torrent/create.rs index fc4bd8f5..a17af0f2 100644 --- a/src/subcommand/torrent/create.rs +++ b/src/subcommand/torrent/create.rs @@ -187,7 +187,7 @@ Examples: long = "sort-by", value_name = "SPEC", help = "Set the order of files within a torrent. `SPEC` should be of the form `KEY:ORDER`, \ - with `KEY` being one of `path` or `size`, and `ORDER` being `ascending` or \ + with `KEY` being one of `path`, `size` or `mtime`, and `ORDER` being `ascending` or \ `descending`. `:ORDER` defaults to `ascending` if omitted. The `--sort-by` flag may \ be given more than once, with later values being used to break ties. Ties that remain \ are broken in ascending path order. @@ -1274,11 +1274,13 @@ mod tests { path: FilePath::from_components(&["bar"]), length: Bytes(4), md5sum: Some(Md5Digest::from_data("5678")), + mtime: None, }, FileInfo { path: FilePath::from_components(&["foo"]), length: Bytes(4), md5sum: Some(Md5Digest::from_data("1234")), + mtime: None, }, ], } @@ -1422,6 +1424,7 @@ mod tests { &[FileInfo { length: Bytes(3), md5sum: Some(Md5Digest::from_hex("37b51d194a7513e45b56f6524f2d51f2")), + mtime: None, path: FilePath::from_components(&["bar"]), },] ); @@ -1457,6 +1460,7 @@ mod tests { &[FileInfo { length: Bytes(3), md5sum: None, + mtime: None, path: FilePath::from_components(&["bar"]), },] ); @@ -1495,16 +1499,19 @@ mod tests { &[ FileInfo { length: Bytes(3), + mtime: None, md5sum: Some(Md5Digest::from_hex("900150983cd24fb0d6963f7d28e17f72")), path: FilePath::from_components(&["a"]), }, FileInfo { length: Bytes(3), + mtime: None, md5sum: Some(Md5Digest::from_hex("857c4402ad934005eae4638a93812bf7")), path: FilePath::from_components(&["h"]), }, FileInfo { length: Bytes(3), + mtime: None, md5sum: Some(Md5Digest::from_hex("d16fb36f0911f878998c136191af705e")), path: FilePath::from_components(&["x"]), }, @@ -1993,11 +2000,13 @@ Content Size 9 bytes FileInfo { length: Bytes(3), md5sum: Some(Md5Digest::from_hex("37b51d194a7513e45b56f6524f2d51f2")), + mtime: None, path: FilePath::from_components(&["bar"]), }, FileInfo { length: Bytes(3), md5sum: Some(Md5Digest::from_hex("73feffa4b7f6bb68e44cf984c85f6e88")), + mtime: None, path: FilePath::from_components(&["dir", "baz"]), }, ] diff --git a/src/walker.rs b/src/walker.rs index 95b6ce64..47a3a6a5 100644 --- a/src/walker.rs +++ b/src/walker.rs @@ -155,6 +155,7 @@ impl Walker { file_infos.push(FileInfo { path: file_path, length: Bytes(len), + mtime: metadata.modified().ok(), md5sum: None, }); } From 152cacef0396864b4a18e1420467507cb0274a9b Mon Sep 17 00:00:00 2001 From: Ignat Loskutov Date: Sat, 4 Oct 2025 16:22:51 +0200 Subject: [PATCH 2/2] Review fixes --- src/error.rs | 2 ++ src/file_info.rs | 4 ++-- src/sort_spec.rs | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 3136fcba..a56ae29b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -62,6 +62,8 @@ pub(crate) enum Error { input: InputTarget, source: MetainfoError, }, + #[snafu(display("Modification time invalid/missing"))] + ModificationTimeMissing, #[snafu(display("Network error: {}", source))] Network { source: io::Error }, #[snafu(display("Failed to invoke opener: {}", source))] diff --git a/src/file_info.rs b/src/file_info.rs index aa4da6a1..f8f59289 100644 --- a/src/file_info.rs +++ b/src/file_info.rs @@ -9,11 +9,11 @@ pub(crate) struct FileInfo { default, with = "unwrap_or_skip" )] - pub(crate) mtime: Option, + pub(crate) md5sum: Option, #[serde( skip_serializing_if = "Option::is_none", default, with = "unwrap_or_skip" )] - pub(crate) md5sum: Option, + pub(crate) mtime: Option, } diff --git a/src/sort_spec.rs b/src/sort_spec.rs index 984f8996..df3d6a8a 100644 --- a/src/sort_spec.rs +++ b/src/sort_spec.rs @@ -1,4 +1,5 @@ use crate::common::*; +use snafu::OptionExt; #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct SortSpec { @@ -25,7 +26,10 @@ impl SortSpec { let ordering = match self.key { SortKey::Path => a.path.cmp(&b.path), SortKey::Size => a.length.cmp(&b.length), - SortKey::Mtime => a.mtime.cmp(&b.mtime), + SortKey::Mtime => { + a.mtime.context(error::ModificationTimeMissing).unwrap() + .cmp(&b.mtime.context(error::ModificationTimeMissing).unwrap()) + } }; match self.order {