use crate::prelude::*;
use flat_db::{Hash, Table};
use futures::stream::{StreamExt, iter};
use std::fs::create_dir;
pub(crate) struct Queue {
table: Table<20, 1, QueueItem>,
}
#[injectable]
impl Queue {
pub(crate) fn from_path(path: PathBuf) -> Self {
Self {
table: Table::new(path),
}
}
#[inject]
pub(crate) fn from_options(options: Ref<CacheOptions>) -> Self {
let path = options.cache.join("queue");
if !path.exists() {
create_dir(&path)
.expect("should be able to create queue directory if it does not exist");
}
Self::from_path(path)
}
pub(crate) async fn get(
&self,
hash: Hash<20>,
) -> Result<Option<QueueItem>, Failure<QueueAction>> {
self.table
.get(hash)
.await
.map_err(Failure::wrap(QueueAction::Get))
}
pub(crate) async fn get_unprocessed(
&self,
indexer: Indexer,
transcode_enabled: bool,
upload_enabled: bool,
retry_failed_transcodes: bool,
) -> Result<Vec<Hash<20>>, Failure<QueueAction>> {
let is_red = indexer == Indexer::Red;
let mut items = self
.get_unprocessed_internal(
indexer,
transcode_enabled,
upload_enabled,
retry_failed_transcodes,
)
.await?;
if is_red {
let mut pth_items = self
.get_unprocessed_internal(
Indexer::Pth,
transcode_enabled,
upload_enabled,
retry_failed_transcodes,
)
.await?;
items.append(&mut pth_items);
}
Ok(items)
}
async fn get_unprocessed_internal(
&self,
indexer: Indexer,
transcode_enabled: bool,
upload_enabled: bool,
retry_failed_transcodes: bool,
) -> Result<Vec<Hash<20>>, Failure<QueueAction>> {
let items = self
.table
.get_all()
.await
.map_err(Failure::wrap(QueueAction::GetAll))?;
let mut items: Vec<&QueueItem> = items
.values()
.filter(|item| {
item.indexer.as_ref() == Some(&indexer)
&& exclude_verified_if_transcode_disabled(item, transcode_enabled)
&& exclude_transcoded_if_upload_disabled(item, upload_enabled)
&& exclude_verify_failures(item)
&& exclude_transcode_failures(item, retry_failed_transcodes)
&& item.upload.is_none()
})
.collect();
items.sort_by_key(|x| &x.name);
let hashes = items.iter().map(|x| x.hash).collect();
Ok(hashes)
}
pub(crate) async fn get_all(
&self,
) -> Result<BTreeMap<Hash<20>, QueueItem>, Failure<QueueAction>> {
self.table
.get_all()
.await
.map_err(Failure::wrap(QueueAction::GetAll))
}
pub(crate) async fn set(&self, item: QueueItem) -> Result<(), Failure<QueueAction>> {
self.table
.set(item.hash, item)
.await
.map_err(Failure::wrap(QueueAction::Set))
}
pub(crate) async fn set_many(
&self,
items: BTreeMap<Hash<20>, QueueItem>,
replace: bool,
) -> Result<usize, Failure<QueueAction>> {
self.table
.set_many(items, replace)
.await
.map_err(Failure::wrap(QueueAction::SetMany))
}
pub(crate) async fn remove(
&self,
hash: Hash<20>,
) -> Result<Option<QueueItem>, Failure<QueueAction>> {
self.table
.remove(hash)
.await
.map_err(Failure::wrap(QueueAction::Remove))
}
pub(crate) async fn insert_new_torrent_files(
&self,
paths: Vec<PathBuf>,
) -> Result<usize, Failure<QueueAction>> {
let stream = iter(paths.into_iter());
let items: BTreeMap<_, _> = stream
.filter_map(|path| async {
let torrent = match TorrentReader::execute(&path).await {
Ok(torrent) => Some(torrent),
Err(error) => {
warn!("{}", error.render());
None
}
};
let item = QueueItem::from_torrent(path, &torrent?);
Some((item.hash, item))
})
.collect()
.await;
self.table
.set_many(items, false)
.await
.map_err(Failure::wrap(QueueAction::SetMany))
}
}
fn exclude_verify_failures(item: &QueueItem) -> bool {
!matches!(
item.verify,
Some(VerifyStatus {
verified: false,
..
})
)
}
fn exclude_transcode_failures(item: &QueueItem, retry_failed_transcodes: bool) -> bool {
retry_failed_transcodes
|| !matches!(item.transcode, Some(TranscodeStatus { success: false, .. }))
}
fn exclude_verified_if_transcode_disabled(item: &QueueItem, transcode_enabled: bool) -> bool {
transcode_enabled || item.verify.is_none()
}
fn exclude_transcoded_if_upload_disabled(item: &QueueItem, upload_enabled: bool) -> bool {
upload_enabled || !matches!(item.transcode, Some(TranscodeStatus { success: true, .. }))
}