-
Notifications
You must be signed in to change notification settings - Fork 2
/
mod.rs
308 lines (282 loc) · 9.51 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
extern crate regex;
extern crate zip;
pub mod config;
pub mod converter;
pub mod db;
pub mod downloader;
pub mod http;
pub mod logger;
use crate::lib::downloader::Filename;
use mysql;
use mysql::{Pool, PooledConn};
use thiserror::Error;
use std::cell::RefCell;
use std::cell::RefMut;
use std::env::current_exe;
use std::error::Error as OriginError;
use std::fs::remove_file;
use std::fs::{metadata, read_dir, rename, File};
use std::io::copy;
use std::path::{Path, PathBuf};
use std::{self, io};
use crate::CONFIG;
/// Struct holding all data concerning the request
pub struct Request {
pub url: String,
pub quality: i16,
pub playlist: bool,
/// split up a playlist request into multiple requests instead of one big file
pub split: bool,
/// query id
pub qid: u64,
/// Reserved int to specify other options in case quality & playlist codes aren't enough
/// Can be used for additional conversion targets etc
pub r_type: i16,
pub from: i16,
pub to: i16,
/// Path for save folder
pub path: PathBuf,
/// Path for temp save folder, can be changed to, for example, sub dirs
/// If it should differe from the default folder this folder will be deleted on failure with all it's content!
pub temp_path: PathBuf,
pub conn: RefCell<PooledConn>,
/// User ID, needed for non-zipped playlist downloads, creating new query & job entries
pub uid: u32,
}
/// Core for assertions
#[cfg(test)]
#[derive(Clone, Debug)]
pub struct ReqCore {
url: String,
quality: i16,
playlist: bool,
split: bool,
qid: u64,
r_type: i16,
from: i16,
to: i16,
path: PathBuf,
temp_path: PathBuf,
uid: u32,
}
#[cfg(test)]
impl ReqCore {
pub fn verify(&self, input: &Request) {
assert_eq!(self.url, input.url);
assert_eq!(self.quality, input.quality);
assert_eq!(self.split, input.split);
assert_eq!(self.qid, input.qid);
assert_eq!(self.r_type, input.r_type);
assert_eq!(self.from, input.from);
assert_eq!(self.to, input.to);
assert_eq!(self.playlist, input.playlist);
assert_eq!(self.path, input.path);
assert_eq!(self.temp_path, input.temp_path);
assert_eq!(self.uid, input.uid);
}
}
impl<'a> Request {
pub fn get_conn(&self) -> RefMut<PooledConn> {
self.conn.borrow_mut()
}
}
/// Error trait
/// HandlerWarn is NOT for errors, it's value will be inserted into the warn DB
#[derive(Debug, Error)]
pub enum Error {
/// used by downloader lib
#[error("Failed to download source: `{0}`")]
DownloadError(String),
/// used by converter lib
#[error("Failed to convert: `{0}`")]
FFMPEGError(String),
/// Content down as of region lock, could be bypassed, see youtube handler
#[error("DMCA error, failed to retrieve source")]
DMCAError,
/// Unavailable (login, region lock etc)
#[error("Source not available, region lock or login required")]
NotAvailable,
/// Quality not available => valid input, but unavailable
#[error("Quality not vailable for source")]
QualityNotAvailable,
/// Error thrown by youtube-dl for some DASH containers, see youtube handler
#[error("Website extraction error, can't parse website")]
ExtractorError,
/// For wrong quality => invalid input, always unavailable
#[error("Invalid quality selected: `{0}`")]
InputError(String),
/// Unexpected lib internal error
#[error("Internal error: `{0}`")]
InternalError(String),
/// Can't handle this URL, no valid handler found
#[error("Unknown URL, can't handle this website")]
UnknownURL,
#[error("Database error: `{0}`")]
MysqlError(#[from] mysql::Error),
#[error("Database error: `{0}`")]
MysqlDeserializeError(#[from] mysql::FromRowError),
#[error("Internal Error: `{0}`")]
IOError(#[from] std::io::Error)
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<zip::result::ZipError> for Error {
fn from(err: zip::result::ZipError) -> Error {
Error::InternalError(err.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Error {
Error::InternalError(err.to_string())
}
}
impl<T> From<std::sync::PoisonError<T>> for Error {
fn from(err: std::sync::PoisonError<T>) -> Error {
Error::InternalError(format!(
"descr:{} cause:{:?}",
err.to_string(),
err.source()
))
}
}
/// Check the SHA256 of a given file against the provided expected output
/// The expected value has to be in lowercase
#[allow(non_snake_case)]
pub fn check_SHA256<P: AsRef<Path>>(path: P, expected: &str) -> Result<bool> {
use sha2::{Digest, Sha256};
trace!("Checking SHA256..");
let mut file = File::open(path)?;
let mut hasher = Sha256::new();
io::copy(&mut file, &mut hasher)?;
let result = format!("{:X}", hasher.finalize());
let result = result.to_lowercase();
let is_matching = result == expected;
if !is_matching {
debug!("SHA Expected: {} Result: {}", expected, result);
}
Ok(is_matching)
}
/// Custom expect function logging errors plus custom messages on panic
/// &'static str to prevent the usage of format!(), which would result in overhead
#[inline]
pub fn l_expect<T, E: std::fmt::Debug>(result: std::result::Result<T, E>, msg: &'static str) -> T {
match result {
Ok(v) => v,
Err(e) => {
error!("{}: {:?}", msg, e);
panic!();
}
}
}
/// Move file to location
pub fn move_file<P: AsRef<Path>, Q: AsRef<Path>>(original: P, destination: Q) -> Result<()> {
match rename(original, destination) {
// no try possible..
Err(v) => Err(v.into()),
Ok(_) => Ok(()),
}
}
/// Returns a sanitized String, usable via url encode
pub fn url_sanitize(input: &str) -> String {
// iterator over input, apply function to each element(function
input
.chars()
.map(|char| match char {
c if c.is_ascii() => c,
_ => '_',
})
.collect()
// match for each char, then do collect: loop through the iterator, collect all elements
// into container from iterator
}
/// Returns a unique path, if the file already exists, a '-X' number will be added to it.
pub fn format_save_path<'a>(path: &Path, fname: &Filename) -> Result<PathBuf> {
let clean_name = &url_sanitize(&fname.name);
let mut path = path.to_path_buf();
path.push(format!("{}.{}", clean_name, fname.extension));
if metadata(path.as_path()).is_ok() {
// 90% of the time we don't need this
for i in 1..100 {
if metadata(path.as_path()).is_ok() {
debug!("Path exists: {}", path.to_string_lossy());
path.pop(); // we can't use set_file_name, as some extensions will overwrite the name
path.push(format!("{}-{}.{}", clean_name, i, fname.extension));
} else {
break;
}
}
}
debug!("Path: {}", path.to_string_lossy());
Ok(path)
}
/// Zips all files inside folder into one file
pub fn zip_folder(folder: &Path, destination: &Path) -> Result<()> {
trace!("Starting zipping..");
if metadata(folder)?.is_dir() {
let output_file = File::create(destination)?;
let mut writer = zip::ZipWriter::new(output_file);
for entry in read_dir(folder)? {
let entry = entry?;
if entry.metadata()?.is_file() {
let mut f_options = zip::write::FileOptions::default();
f_options = f_options.compression_method(zip::CompressionMethod::Deflated);
writer.start_file(entry.file_name().to_string_lossy().into_owned(), f_options)?;
let mut reader = File::open(entry.path())?;
let _ = reader.sync_data();
copy(&mut reader, &mut writer)?;
}
}
writer.finish()?;
trace!("finished zipping");
Ok(())
} else {
Err(Error::InternalError(
"zip source is not a folder!".to_string(),
))
}
}
/// Returns the current executable folder
pub fn get_executable_folder() -> std::result::Result<std::path::PathBuf, io::Error> {
let mut folder = current_exe()?;
folder.pop();
Ok(folder)
}
/// Delete files aged or marked for removal
/// Additionally erases the DB entries if configured to do so
/// dir_path markes the directory the files are located in
pub fn delete_files(
pool: &Pool,
delete_type: db::DeleteRequestType,
dir_path: &Path,
) -> Result<()> {
let mut conn = pool.get_conn()?;
let (qids, mut files) = db::get_files_to_delete(&mut conn, delete_type)?;
debug!("Len before: {}", files.len());
files.retain(|&(_, ref url)| {
// remove all not matching
trace!("deleting {:?}", url);
let mut path = dir_path.to_path_buf();
path.push(url);
match remove_file(&path) {
Ok(_) => true,
Err(e) => {
if path.exists() {
error!("Couldn't delete file {:?} {:?}", path, e);
false
} else {
warn!("File was already deleted: {:?}", path);
true
}
}
}
});
debug!("Len after: {}", files.len());
if CONFIG.cleanup.auto_delete_request {
db::delete_requests(&mut conn, qids, files)?;
} else {
for (fid, _) in files {
db::set_file_delete_flag(&mut conn, &fid, false)?;
db::set_file_valid_flag(&mut conn, &fid, false)?;
}
}
Ok(())
}