Skip to content

Commit

Permalink
support seeding completed read-only files (fixes #136)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikatson committed Jun 22, 2024
1 parent 5c4b06e commit d980e29
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 37 deletions.
57 changes: 30 additions & 27 deletions crates/librqbit/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -787,34 +787,37 @@ impl Session {
creation_date: None,
info_hash: Id20::from_str(&storrent.info_hash)?,
};
futures.push({
let session = self.clone();
async move {
session
.add_torrent(
AddTorrent::TorrentInfo(Box::new(info)),
Some(AddTorrentOptions {
paused: storrent.is_paused,
output_folder: Some(
storrent
.output_folder
.to_str()
.context("broken path")?
.to_owned(),
),
only_files: storrent.only_files,
overwrite: true,
preferred_id: Some(id),
..Default::default()
}),
)
.await
.map_err(|e| {
error!("error adding torrent from stored session: {:?}", e);
e
})
futures.push(
{
let session = self.clone();
async move {
session
.add_torrent(
AddTorrent::TorrentInfo(Box::new(info)),
Some(AddTorrentOptions {
paused: storrent.is_paused,
output_folder: Some(
storrent
.output_folder
.to_str()
.context("broken path")?
.to_owned(),
),
only_files: storrent.only_files,
overwrite: true,
preferred_id: Some(id),
..Default::default()
}),
)
.await
.map_err(|e| {
error!("error adding torrent from stored session: {:?}", e);
e
})
}
}
});
.instrument(error_span!(parent: None, "torrent", id)),
);
}
futures::future::join_all(futures).await;
Ok(())
Expand Down
34 changes: 26 additions & 8 deletions crates/librqbit/src/storage/filesystem/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ impl TorrentStorage for FilesystemStorage {

fn pwrite_all(&self, file_id: usize, offset: u64, buf: &[u8]) -> anyhow::Result<()> {
let of = self.opened_files.get(file_id).context("no such file")?;
of.ensure_writeable()?;
#[cfg(target_family = "unix")]
{
use std::os::unix::fs::FileExt;
Expand Down Expand Up @@ -114,7 +115,7 @@ impl TorrentStorage for FilesystemStorage {
if !path.is_dir() {
anyhow::bail!("cannot remove dir: {path:?} is not a directory")
}
if std::fs::read_dir(&path)?.count() == 0 {
if std::fs::read_dir(&path)?.next().is_none() {
std::fs::remove_dir(&path).with_context(|| format!("error removing {path:?}"))
} else {
warn!("did not remove {path:?} as it was not empty");
Expand All @@ -133,17 +134,34 @@ impl TorrentStorage for FilesystemStorage {
full_path.push(relative_path);

std::fs::create_dir_all(full_path.parent().context("bug: no parent")?)?;
let file = if meta.options.allow_overwrite {
OpenOptions::new()
if meta.options.allow_overwrite {
// ensure file exists
let (file, writeable) = match OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.append(false)
.truncate(false)
.open(&full_path)
.with_context(|| format!("error opening {full_path:?} in read/write mode"))?
{
Ok(file) => (file, true),
Err(e) => {
warn!(?full_path, "error opening file in create+write mode: {e:?}");
// open the file in read-only mode, will reopen in write mode later.
(
OpenOptions::new()
.create(false)
.read(true)
.open(&full_path)
.with_context(|| format!("error opening {full_path:?}"))?,
false,
)
}
};
files.push(OpenedFile::new(full_path.clone(), file, writeable));
} else {
// create_new does not seem to work with read(true), so calling this twice.
OpenOptions::new()
let file = OpenOptions::new()
.create_new(true)
.write(true)
.open(&full_path)
Expand All @@ -153,9 +171,9 @@ impl TorrentStorage for FilesystemStorage {
&full_path
)
})?;
OpenOptions::new().read(true).write(true).open(&full_path)?
OpenOptions::new().read(true).write(true).open(&full_path)?;
files.push(OpenedFile::new(full_path.clone(), file, true));
};
files.push(OpenedFile::new(file));
}

self.opened_files = files;
Expand Down
37 changes: 35 additions & 2 deletions crates/librqbit/src/storage/filesystem/opened_file.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
use std::fs::File;
use std::{
fs::File,
path::PathBuf,
sync::atomic::{AtomicBool, Ordering},
};

use anyhow::Context;
use parking_lot::RwLock;

#[derive(Debug)]
pub(crate) struct OpenedFile {
pub filename: PathBuf,
pub file: RwLock<File>,
pub is_writeable: AtomicBool,
}

pub(crate) fn dummy_file() -> anyhow::Result<std::fs::File> {
Expand All @@ -21,9 +27,11 @@ pub(crate) fn dummy_file() -> anyhow::Result<std::fs::File> {
}

impl OpenedFile {
pub fn new(f: File) -> Self {
pub fn new(filename: PathBuf, f: File, is_writeable: bool) -> Self {
Self {
filename,
file: RwLock::new(f),
is_writeable: AtomicBool::new(is_writeable),
}
}

Expand All @@ -37,7 +45,32 @@ impl OpenedFile {
pub fn take_clone(&self) -> anyhow::Result<Self> {
let f = self.take()?;
Ok(Self {
filename: self.filename.clone(),
file: RwLock::new(f),
is_writeable: AtomicBool::new(self.is_writeable.load(Ordering::SeqCst)),
})
}

pub fn ensure_writeable(&self) -> anyhow::Result<()> {
match self
.is_writeable
.compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
{
Ok(_) => {
// Updated, need to reopen writeable
let mut g = self.file.write();
let new_file = std::fs::OpenOptions::new()
.write(true)
.create(false)
.open(&self.filename)
.with_context(|| format!("error opening {:?} in write mode", self.filename))?;
*g = new_file;
}
Err(_) => {
// Didn't update, no need to reopen
}
}

Ok(())
}
}

0 comments on commit d980e29

Please sign in to comment.