Add access to new fields

This commit is contained in:
Igor Katson 2024-10-14 15:16:55 +01:00
parent 98f011673e
commit 18755d8971
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
7 changed files with 78 additions and 70 deletions

View file

@ -533,23 +533,23 @@ fn make_torrent_details(
output_folder: String,
) -> Result<TorrentDetailsResponse> {
let files = info
.iter_filenames_and_lengths()
.iter_file_details()
.context("error iterating filenames and lengths")?
.enumerate()
.map(|(idx, (filename_it, length))| {
let name = match filename_it.to_string() {
.map(|(idx, d)| {
let name = match d.filename.to_string() {
Ok(s) => s,
Err(err) => {
warn!("error reading filename: {:?}", err);
"<INVALID NAME>".to_string()
}
};
let components = filename_it.to_vec().unwrap_or_default();
let components = d.filename.to_vec().unwrap_or_default();
let included = only_files.map(|o| o.contains(&idx)).unwrap_or(true);
TorrentDetailsResponseFile {
name,
components,
length,
length: d.len,
included,
}
})
@ -568,10 +568,11 @@ fn torrent_file_mime_type(
info: &TorrentMetaV1Info<ByteBufOwned>,
file_idx: usize,
) -> Result<&'static str> {
info.iter_filenames_and_lengths()?
info.iter_file_details()?
.nth(file_idx)
.and_then(|(f, _)| {
f.iter_components()
.and_then(|d| {
d.filename
.iter_components()
.last()
.and_then(|r| r.ok())
.and_then(|s| mime_guess::from_path(s).first_raw())

View file

@ -181,7 +181,8 @@ impl<'a> FileOps<'a> {
let mut piece_remaining_bytes = piece_length as usize;
for (file_idx, (name, file_len)) in self.torrent.iter_filenames_and_lengths()?.enumerate() {
for (file_idx, file_details) in self.torrent.iter_file_details()?.enumerate() {
let file_len = file_details.len;
if absolute_offset > file_len {
absolute_offset -= file_len;
continue;
@ -205,7 +206,10 @@ impl<'a> FileOps<'a> {
to_read_in_file,
)
.with_context(|| {
format!("error reading {to_read_in_file} bytes, file_id: {file_idx} (\"{name:?}\")")
format!(
"error reading {to_read_in_file} bytes, file_id: {file_idx} (\"{:?}\")",
file_details.filename
)
})?;
piece_remaining_bytes -= to_read_in_file;
@ -292,7 +296,8 @@ impl<'a> FileOps<'a> {
let mut buf = data.block.as_ref();
let mut absolute_offset = self.lengths.chunk_absolute_offset(chunk_info);
for (file_idx, (name, file_len)) in self.torrent.iter_filenames_and_lengths()?.enumerate() {
for (file_idx, file_details) in self.torrent.iter_file_details()?.enumerate() {
let file_len = file_details.len;
if absolute_offset > file_len {
absolute_offset -= file_len;
continue;
@ -313,7 +318,12 @@ impl<'a> FileOps<'a> {
);
self.files
.pwrite_all(file_idx, absolute_offset, &buf[..to_write])
.with_context(|| format!("error writing to file {file_idx} (\"{name:?}\")"))?;
.with_context(|| {
format!(
"error writing to file {file_idx} (\"{:?}\")",
file_details.filename
)
})?;
buf = &buf[to_write..];
if buf.is_empty() {
break;

View file

@ -153,10 +153,10 @@ impl HttpApi {
let mut playlist_items = handle
.shared()
.info
.iter_filenames_and_lengths()?
.iter_file_details()?
.enumerate()
.filter_map(|(file_idx, (filename, _))| {
let filename = filename.to_vec().ok()?.join("/");
.filter_map(|(file_idx, file_details)| {
let filename = file_details.filename.to_vec().ok()?.join("/");
let is_playable = mime_guess::from_path(&filename)
.first()
.map(|mime| {

View file

@ -156,8 +156,9 @@ fn compute_only_files_regex<ByteBuf: AsRef<[u8]>>(
) -> anyhow::Result<Vec<usize>> {
let filename_re = regex::Regex::new(filename_re).context("filename regex is incorrect")?;
let mut only_files = Vec::new();
for (idx, (filename, _)) in torrent.iter_filenames_and_lengths()?.enumerate() {
let full_path = filename
for (idx, fd) in torrent.iter_file_details()?.enumerate() {
let full_path = fd
.filename
.to_pathbuf()
.with_context(|| format!("filename of file {idx} is not valid utf8"))?;
if filename_re.is_match(full_path.to_str().unwrap()) {
@ -191,12 +192,12 @@ fn compute_only_files(
}
(None, Some(filename_re)) => {
let only_files = compute_only_files_regex(info, &filename_re)?;
for (idx, (filename, _)) in info.iter_filenames_and_lengths()?.enumerate() {
for (idx, fd) in info.iter_file_details()?.enumerate() {
if !only_files.contains(&idx) {
continue;
}
if !list_only {
info!(?filename, "will download");
info!(filename=?fd.filename, "will download");
}
}
Ok(Some(only_files))
@ -1043,8 +1044,8 @@ impl Session {
info: &TorrentMetaV1Info<ByteBufOwned>,
) -> anyhow::Result<Option<PathBuf>> {
let files = info
.iter_filenames_and_lengths()?
.map(|(f, l)| Ok((f.to_pathbuf()?, l)))
.iter_file_details()?
.map(|fd| Ok((fd.filename.to_pathbuf()?, fd.len)))
.collect::<anyhow::Result<Vec<(PathBuf, u64)>>>()?;
if files.len() < 2 {
return Ok(None);
@ -1145,7 +1146,7 @@ impl Session {
.map(|fd| {
Ok::<_, anyhow::Error>(FileInfo {
relative_filename: fd.details.filename.to_pathbuf()?,
offset_in_torrent: fd.details.offset,
offset_in_torrent: fd.offset,
piece_range: fd.pieces,
len: fd.details.len,
})

View file

@ -68,10 +68,10 @@ impl TorrentFileTreeNode {
let last_url_bit = torrent
.shared()
.info
.iter_filenames_and_lengths()
.iter_file_details()
.ok()
.and_then(|mut it| it.nth(fid))
.and_then(|(fi, _)| fi.to_vec().ok())
.and_then(|fd| fd.filename.to_vec().ok())
.map(|components| {
components
.into_iter()
@ -111,10 +111,10 @@ struct TorrentFileTree {
}
fn is_single_file_at_root(info: &TorrentMetaV1Info<ByteBufOwned>) -> bool {
info.iter_filenames_and_lengths()
info.iter_file_details()
.into_iter()
.flatten()
.flat_map(|(f, _)| f.iter_components())
.flat_map(|fd| fd.filename.iter_components())
.nth(1)
.is_none()
}
@ -123,10 +123,10 @@ impl TorrentFileTree {
fn build(torent_id: TorrentId, info: &TorrentMetaV1Info<ByteBufOwned>) -> anyhow::Result<Self> {
if is_single_file_at_root(info) {
let filename = info
.iter_filenames_and_lengths()?
.iter_file_details()?
.next()
.context("bug")?
.0
.filename
.iter_components()
.last()
.context("bug")??;
@ -159,8 +159,8 @@ impl TorrentFileTree {
let mut name_cache = HashMap::new();
for (fid, (fi, _)) in info.iter_filenames_and_lengths()?.enumerate() {
let components = match fi.to_vec() {
for (fid, fd) in info.iter_file_details()?.enumerate() {
let components = match fd.filename.to_vec() {
Ok(v) => v,
Err(_) => continue,
};

View file

@ -182,18 +182,19 @@ where
pub struct FileDetails<'a, BufType> {
pub filename: FileIteratorName<'a, BufType>,
// absolute offset in torrent if it was a flat blob of bytes
pub offset: u64,
pub len: u64,
// bep-47
pub attr: Option<BufType>,
pub sha1: Option<BufType>,
pub symlink_path: Option<Vec<BufType>>,
pub attr: Option<&'a BufType>,
pub sha1: Option<&'a BufType>,
pub symlink_path: Option<&'a [BufType]>,
}
pub struct FileDetailsExt<'a, BufType> {
pub details: FileDetails<'a, BufType>,
// absolute offset in torrent if it was a flat blob of bytes
pub offset: u64,
// the pieces that contain this file
pub pieces: std::ops::Range<u32>,
}
@ -220,33 +221,38 @@ impl<BufType: AsRef<[u8]>> TorrentMetaV1Info<BufType> {
}
#[inline(never)]
pub fn iter_filenames_and_lengths(
pub fn iter_file_details(
&self,
) -> anyhow::Result<impl Iterator<Item = (FileIteratorName<'_, BufType>, u64)>> {
) -> anyhow::Result<impl Iterator<Item = FileDetails<'_, BufType>>> {
match (self.length, self.files.as_ref()) {
// Single-file
(Some(length), None) => Ok(Either::Left(once((
FileIteratorName::Single(self.name.as_ref()),
length,
)))),
(Some(length), None) => Ok(Either::Left(once(FileDetails {
filename: FileIteratorName::Single(self.name.as_ref()),
len: length,
attr: self.attr.as_ref(),
sha1: self.sha1.as_ref(),
symlink_path: self.symlink_path.as_deref(),
}))),
// Multi-file
(None, Some(files)) => {
if files.is_empty() {
anyhow::bail!("expected multi-file torrent to have at least one file")
}
Ok(Either::Right(
files
.iter()
.map(|f| (FileIteratorName::Tree(&f.path), f.length)),
))
Ok(Either::Right(files.iter().map(|f| FileDetails {
filename: FileIteratorName::Tree(&f.path),
len: f.length,
attr: f.attr.as_ref(),
sha1: f.sha1.as_ref(),
symlink_path: f.symlink_path.as_deref(),
})))
}
_ => anyhow::bail!("torrent can't be both in single and multi-file mode"),
}
}
pub fn iter_file_lengths(&self) -> anyhow::Result<impl Iterator<Item = u64> + '_> {
Ok(self.iter_filenames_and_lengths()?.map(|(_, l)| l))
Ok(self.iter_file_details()?.map(|d| d.len))
}
// NOTE: lenghts MUST be construced with Lenghts::from_torrent, otherwise
@ -255,23 +261,15 @@ impl<BufType: AsRef<[u8]>> TorrentMetaV1Info<BufType> {
&'a self,
lengths: &'a Lengths,
) -> anyhow::Result<impl Iterator<Item = FileDetailsExt<'a, BufType>> + 'a> {
Ok(self
.iter_filenames_and_lengths()?
.scan(0u64, |acc_offset, (filename, len)| {
let offset = *acc_offset;
*acc_offset += len;
Some(FileDetailsExt {
details: FileDetails {
filename,
offset,
len,
attr: None,
sha1: None,
symlink_path: None,
},
pieces: lengths.iter_pieces_within_offset(offset, len),
})
}))
Ok(self.iter_file_details()?.scan(0u64, |acc_offset, details| {
let offset = *acc_offset;
*acc_offset += details.len;
Some(FileDetailsExt {
pieces: lengths.iter_pieces_within_offset(offset, details.len),
details,
offset,
})
}))
}
}

View file

@ -762,17 +762,15 @@ async fn async_main(opts: Opts, cancel: CancellationToken) -> anyhow::Result<()>
only_files,
..
}) => {
for (idx, (filename, len)) in
info.iter_filenames_and_lengths()?.enumerate()
{
for (idx, fd) in info.iter_file_details()?.enumerate() {
let included = match &only_files {
Some(files) => files.contains(&idx),
None => true,
};
info!(
"File {}, size {}{}",
filename.to_string()?,
SF::new(len),
"File {:?}, size {}{}",
fd.filename,
SF::new(fd.len),
if included { "" } else { ", will skip" }
)
}