caesura/utils/fs/
path_manager.rs1use crate::prelude::*;
2
3const TRACKER_SUFFIXES: &[&str] = &["red", "ops", "pth"];
5
6#[injectable]
7pub struct PathManager {
8 shared_options: Ref<SharedOptions>,
9 cache_options: Ref<CacheOptions>,
10 file_options: Ref<FileOptions>,
11}
12
13impl PathManager {
14 #[must_use]
19 pub fn default_config_path() -> PathBuf {
20 if is_docker() {
21 return PathBuf::from("/config.yml");
22 }
23 dirs::config_dir()
24 .expect("config directory should be determinable")
25 .join(APP_NAME)
26 .join("config.yml")
27 }
28
29 #[must_use]
34 pub fn default_cache_dir() -> PathBuf {
35 if is_docker() {
36 return PathBuf::from("/cache");
37 }
38 dirs::cache_dir()
39 .expect("cache directory should be determinable")
40 .join(APP_NAME)
41 }
42
43 #[must_use]
48 pub fn default_output_dir() -> PathBuf {
49 if is_docker() {
50 return PathBuf::from("/output");
51 }
52 dirs::data_dir()
53 .expect("data directory should be determinable")
54 .join(APP_NAME)
55 .join("output")
56 }
57
58 #[must_use]
59 pub fn get_cache_dir(&self) -> PathBuf {
60 self.cache_options.path()
61 }
62
63 #[must_use]
65 pub fn get_source_torrent_path(&self, source: &Source) -> PathBuf {
66 let id = source.torrent.id;
67 let indexer = self.shared_options.indexer_lowercase();
68 self.get_cache_dir()
69 .join("torrents")
70 .join(format!("{id}.{indexer}.torrent"))
71 }
72
73 #[must_use]
74 pub fn get_output_dir(&self) -> PathBuf {
75 self.shared_options.output_path()
76 }
77
78 #[must_use]
79 pub fn get_spectrogram_dir(&self, source: &Source) -> PathBuf {
80 self.get_output_dir()
81 .join(SpectrogramName::get(&source.metadata))
82 }
83
84 #[must_use]
85 pub fn get_transcode_target_dir(&self, source: &Source, target: TargetFormat) -> PathBuf {
86 self.get_output_dir()
87 .join(TranscodeName::get(&source.metadata, target))
88 }
89
90 #[must_use]
91 pub fn get_transcode_path(
92 &self,
93 source: &Source,
94 target: TargetFormat,
95 flac: &FlacFile,
96 ) -> PathBuf {
97 let extension = target.get_file_extension();
98 let rename_tracks = self.file_options.rename_tracks;
99 let (base_name, sub_dir) = if rename_tracks && flac.disc_context.is_some() {
101 (
102 flac.renamed_file_stem(),
103 flac.renamed_sub_dir().unwrap_or_default(),
104 )
105 } else {
106 (flac.file_name.clone(), flac.sub_dir.clone())
107 };
108 self.get_transcode_target_dir(source, target)
109 .join(sub_dir)
110 .join(format!("{base_name}.{extension}"))
111 }
112
113 #[must_use]
114 pub fn get_torrent_path(&self, source: &Source, target: TargetFormat) -> PathBuf {
115 let indexer = self.shared_options.indexer_lowercase();
116 self.get_torrent_path_for_indexer(source, target, &indexer)
117 }
118
119 #[must_use]
120 fn get_torrent_path_for_indexer(
121 &self,
122 source: &Source,
123 target: TargetFormat,
124 indexer: &str,
125 ) -> PathBuf {
126 let mut filename = TranscodeName::get(&source.metadata, target);
127 filename.push('.');
128 filename.push_str(indexer);
129 filename.push_str(".torrent");
130 self.get_output_dir().join(filename)
131 }
132
133 pub async fn get_or_duplicate_existing_torrent_path(
146 &self,
147 source: &Source,
148 target: TargetFormat,
149 ) -> Result<Option<PathBuf>, Failure<TorrentCreateAction>> {
150 let target_path = self.get_torrent_path(source, target);
151 if target_path.is_file() {
152 return Ok(Some(target_path));
153 }
154 let fallback_path = self.find_fallback_torrent(source, target);
155 let Some(fallback_path) = fallback_path else {
156 return Ok(None);
157 };
158 let announce_url = self.shared_options.announce_url.clone();
159 let indexer = self.shared_options.indexer_lowercase();
160 TorrentCreator::duplicate(&fallback_path, &target_path, announce_url, indexer).await?;
161 Ok(Some(target_path))
162 }
163
164 fn find_fallback_torrent(&self, source: &Source, target: TargetFormat) -> Option<PathBuf> {
166 let current_indexer = self.shared_options.indexer_lowercase();
167 for suffix in TRACKER_SUFFIXES {
168 if *suffix == current_indexer {
169 continue;
170 }
171 let path = self.get_torrent_path_for_indexer(source, target, suffix);
172 if path.is_file() {
173 return Some(path);
174 }
175 }
176 None
177 }
178}