Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ orchestration_workers = 10
# Optional number of download workers, default 4. This controls how many downloads we run in parallel.
download_workers = 4

# Optional. When false (default), locally downloaded files are deleted after
# the *arr imports them. Set to true to keep them in download_directory instead
# (put.io transfers/files are still cleaned up).
keep_downloads = false

[putio]
# Required. Putio API key. You can generate one using `putioarr get-token`
api_key = "MYPUTIOKEY"
Expand Down Expand Up @@ -180,9 +185,6 @@ To prevent Sonarr/Radarr/Whisparr from seeing each other's downloads, you can co
This feature ensures each *arr application only sees and processes its own downloads.

## TODO:
- Better Error handling and retry behavior
- The session ID provided is hard coded. Not sure if it matters.
- (Add option to not delete downloads)
- Figure out a better way to map a transfer to a completed import. Since a transfer can contain multiple files (e.g. a whole season) we currently check if all video files have been imported. Most of the time this is fine, except when there are sample videos. sonarr/radarr/whisparr will not import samples, but will make no mention of the fact that the sample was skipped. Right now we check against the `skip_directories` list, which works, but might be tedious.
- Automatically pick the right putio proxy based on speed

Expand Down
39 changes: 23 additions & 16 deletions src/download_system/orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ use anyhow::Result;
use async_channel::{Receiver, Sender};
use colored::*;
use log::{info, warn};
use std::{
fs,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};
use tokio::{fs::metadata, time::sleep};

use super::transfer::TransferMessage;
Expand Down Expand Up @@ -157,19 +154,29 @@ async fn watch_for_import(
info!("{}: imported", transfer);
let top_level_target = transfer.get_top_level();

match metadata(&top_level_target.to).await {
Ok(m) if m.is_dir() => {
fs::remove_dir_all(&top_level_target.to).unwrap();
info!("{}: deleted", &top_level_target);
}
Ok(m) if m.is_file() => {
fs::remove_file(&top_level_target.to).unwrap();
info!("{}: deleted", &top_level_target);
}
Ok(_) | Err(_) => {
panic!("{}: no idea how to handle", &top_level_target)
// Remove the local copy now that it's imported, unless the user
// opted to keep downloads (e.g. the *arr copies imports and they
// want to keep the original).
if app_data.config.keep_downloads {
info!("{}: keeping local download (keep_downloads)", &top_level_target);
} else {
// Use async fs and log failures instead of unwrap/panic: a failed
// delete (permissions, a concurrent removal, the path already gone)
// shouldn't take the process down, and blocking fs here could stall
// the runtime under load.
let result = match metadata(&top_level_target.to).await {
Ok(m) if m.is_dir() => {
tokio::fs::remove_dir_all(&top_level_target.to).await
}
Ok(m) if m.is_file() => tokio::fs::remove_file(&top_level_target.to).await,
// Neither a file nor a dir (e.g. already removed): nothing to do.
Ok(_) | Err(_) => Ok(()),
};
match result {
Ok(_) => info!("{}: deleted", &top_level_target),
Err(e) => warn!("{}: failed to delete local copy: {}", &top_level_target, e),
}
};
}
// An orphan has no put.io transfer to remove or seed, so finish it
// here directly instead of routing an Imported message through a
// worker (which may be busy downloading and never pick it up),
Expand Down
9 changes: 9 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ pub struct Config {
/// lets the put.io account be shared with manual downloads.
#[serde(default)]
download_unmanaged: bool,
/// When true, keep the locally downloaded files in `download_directory`
/// after the *arr imports them, instead of deleting them. Off by default,
/// which preserves the normal behaviour of removing a download once its
/// import is confirmed. Useful when the *arr copies (rather than
/// hardlinks/moves) imports and you want to keep the original. Only affects
/// the local files; put.io transfers/files are still cleaned up as usual.
#[serde(default)]
keep_downloads: bool,
/// put.io folder ids to additionally scan for *orphaned* completed files:
/// files that were downloaded but whose transfer record no longer exists
/// (e.g. put.io's "clear completed transfers" removes the transfer while
Expand Down Expand Up @@ -184,6 +192,7 @@ async fn main() -> Result<()> {
.join(Serialized::default("port", 9091))
.join(Serialized::default("uid", 1000))
.join(Serialized::default("download_unmanaged", false))
.join(Serialized::default("keep_downloads", false))
.join(Serialized::default(
"skip_directories",
vec!["sample", "extras"],
Expand Down
5 changes: 5 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ skip_directories = ["sample", "extras"]
# transfers. Set to true to download every transfer on the account.
download_unmanaged = false

# Optional. When false (default), locally downloaded files are deleted after the
# *arr imports them. Set to true to keep them in download_directory instead (put.io
# transfers/files are still cleaned up as usual).
keep_downloads = false

# Optional. put.io folder ids to scan for *orphaned* completed files: files that
# were downloaded but whose transfer record no longer exists (e.g. put.io's
# "clear completed transfers" removes the transfer but leaves the file). Such
Expand Down