doc: Adding some comments for documentation
This commit is contained in:
parent
72baadbec7
commit
676c922a3a
|
@ -3,20 +3,24 @@ use inquire::{Text, CustomUserError, Autocomplete, autocompletion::Replacement};
|
|||
use log::{warn, info, error};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
// Struct to hold the config values
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct Config {
|
||||
pub tmdb_key: String,
|
||||
pub plex_library: PathBuf,
|
||||
}
|
||||
|
||||
// Load config, or trigger first run wizard
|
||||
pub fn load(path: &PathBuf, first: bool) -> Result<Config, Box<dyn Error>> {
|
||||
if first {
|
||||
// If first run wizard should be re-run don't bother with the existing config, run wizard and save it
|
||||
info!("Running first run wizard...");
|
||||
let cfg = first_run()?;
|
||||
save(cfg.clone(), path)?;
|
||||
return Ok(cfg);
|
||||
}
|
||||
|
||||
// Find and read config file, deserialise it into a config object
|
||||
let f = fs::read_to_string(path);
|
||||
let f = match f {
|
||||
Ok(file) => file,
|
||||
|
@ -36,6 +40,7 @@ pub fn load(path: &PathBuf, first: bool) -> Result<Config, Box<dyn Error>> {
|
|||
Ok(cfg)
|
||||
}
|
||||
|
||||
// First run wizard
|
||||
pub fn first_run() -> Result<Config, Box<dyn Error>> {
|
||||
let tmdb_key = Text::new("Enter your TMDB API Read Access Token:")
|
||||
.with_help_message("The API key can be found at https://www.themoviedb.org/settings/api (you must be logged in).")
|
||||
|
@ -64,6 +69,7 @@ pub fn first_run() -> Result<Config, Box<dyn Error>> {
|
|||
Ok(Config { tmdb_key: tmdb_key, plex_library: plex_library})
|
||||
}
|
||||
|
||||
// Serialise and save config object to disk
|
||||
pub fn save(cfg: Config, path: &PathBuf) -> Result<(), Box<dyn Error>> {
|
||||
let serialized = serde_json::to_string_pretty(&cfg)?;
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
|
|
|
@ -4,24 +4,8 @@ use log::trace;
|
|||
|
||||
use crate::{movie::handle_movie_files_and_folders, config::Config, media::Move, show::handle_show_files_and_folders};
|
||||
|
||||
/*fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||
entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map(|s| entry.depth() == 0 || (!s.starts_with(".") && !s.starts_with("@"))) // todo!: Allow ignored chars to be configured, here, @ is QNAP special folders
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn walk_path(path: PathBuf) -> Vec<PathBuf> {
|
||||
let mut entries: Vec<PathBuf> = vec![];
|
||||
WalkDir::new(path)
|
||||
.into_iter()
|
||||
.filter_entry(|e| is_not_hidden(e))
|
||||
.filter_map(|v| v.ok())
|
||||
.for_each(|x| entries.push(x.into_path()));
|
||||
entries
|
||||
}*/
|
||||
|
||||
// Search a given path for movies or shows
|
||||
// TODO: Add support for single file as well
|
||||
pub fn search_path(path: PathBuf, cfg: Config, shows: bool) -> Result<Vec<Move>, Box<dyn Error>> {
|
||||
let entries = fs::read_dir(path.clone())?;
|
||||
let mut files: Vec<DirEntry> = Vec::new();
|
||||
|
@ -40,6 +24,7 @@ pub fn search_path(path: PathBuf, cfg: Config, shows: bool) -> Result<Vec<Move>,
|
|||
}
|
||||
}
|
||||
|
||||
// Sort the files and directory vectors by size, so the main movie file (the biggest usually) is the first
|
||||
folders.sort_by(|a, b| b.metadata().unwrap().len().cmp(&a.metadata().unwrap().len()));
|
||||
files.sort_by(|a, b| b.metadata().unwrap().len().cmp(&a.metadata().unwrap().len()));
|
||||
trace!("Sorted Dirs: {:#?}", folders);
|
||||
|
@ -47,14 +32,17 @@ pub fn search_path(path: PathBuf, cfg: Config, shows: bool) -> Result<Vec<Move>,
|
|||
|
||||
let mut moves: Vec<Move> = Vec::new();
|
||||
if shows {
|
||||
// Find shows in directory (only one show per run supported right now)
|
||||
moves.append(&mut handle_show_files_and_folders(path, files, folders, cfg.clone()));
|
||||
} else {
|
||||
// Find movies in directory or subdirectories, find extras
|
||||
moves.append(&mut handle_movie_files_and_folders(files, folders, cfg.clone()));
|
||||
}
|
||||
|
||||
Ok(moves)
|
||||
}
|
||||
|
||||
// Some lgecy documentation, rough description of the algorithm
|
||||
/*
|
||||
Look at current directory:
|
||||
Only directories, no media files ->
|
||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -47,6 +47,7 @@ struct Args {
|
|||
fn main() {
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialise error logger to use `stderr` and verbosity/quiet mode from command line flags
|
||||
stderrlog::new()
|
||||
.module(module_path!())
|
||||
.quiet(args.quiet)
|
||||
|
@ -54,12 +55,7 @@ fn main() {
|
|||
.init()
|
||||
.unwrap();
|
||||
|
||||
trace!("trace message");
|
||||
debug!("debug message");
|
||||
info!("info message");
|
||||
warn!("warn message");
|
||||
error!("error message");
|
||||
|
||||
// Set config path config to home folder, or if provided to specified file
|
||||
let config_path = if args.config.is_none() {
|
||||
PathBuf::from(std::env::var("HOME").unwrap()).join(".plex-media-ingest").join("config.json")
|
||||
} else {
|
||||
|
@ -68,16 +64,19 @@ fn main() {
|
|||
|
||||
info!("Loading config from \"{}\"", config_path.to_str().unwrap());
|
||||
|
||||
// Read config, or run first run wizard and write config, if none can be found
|
||||
let cfg = config::load(&config_path, args.first_run).unwrap();
|
||||
|
||||
info!("Found config: {:#?}", cfg);
|
||||
|
||||
// Use either provided or current path as search path for movies/shows
|
||||
let search_path = if args.path.is_none() {
|
||||
env::current_dir().unwrap()
|
||||
} else {
|
||||
args.path.unwrap()
|
||||
};
|
||||
|
||||
// Search path and put everything in vector to hold all the file moves (or copies)
|
||||
let moves = directory::search_path(search_path, cfg, args.shows).unwrap();
|
||||
|
||||
for move_file in moves {
|
||||
|
@ -117,13 +116,4 @@ fn main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
//let files = directory::walk_path(search_path);
|
||||
|
||||
/*for file in files.clone() {
|
||||
info!("Found: {}", file.to_str().unwrap());
|
||||
}*/
|
||||
|
||||
//search_media(files).unwrap();
|
||||
|
||||
}
|
|
@ -2,12 +2,14 @@ use std::{path::PathBuf, error::Error, fs::File, cmp, io::Read};
|
|||
|
||||
use log::trace;
|
||||
|
||||
// Struct holding two paths for the move/copy command
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Move {
|
||||
pub from: PathBuf,
|
||||
pub to: PathBuf
|
||||
}
|
||||
|
||||
// Extract the header/magic bytes from a file
|
||||
pub fn get_file_header(path: PathBuf) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
let f = File::open(path)?;
|
||||
|
||||
|
@ -20,6 +22,7 @@ pub fn get_file_header(path: PathBuf) -> Result<Vec<u8>, Box<dyn Error>> {
|
|||
Ok(bytes)
|
||||
}
|
||||
|
||||
// Check validity of a file-/foldername token (strip common torrent parts)
|
||||
fn token_valid(t: &&str) -> bool {
|
||||
if
|
||||
t.eq_ignore_ascii_case("dvd") ||
|
||||
|
@ -64,6 +67,7 @@ fn token_valid(t: &&str) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
// Separate file-/foldernames into a vector of tokens, stripping of whitespace or other separation characters
|
||||
pub fn tokenize_media_name(file_name: String) -> Vec<String> {
|
||||
let tokens: Vec<String> = file_name.split(&['-', ' ', ':', '@', '.'][..]).filter(|t| token_valid(t)).map(String::from).collect();
|
||||
trace!("Tokens are: {:#?}", tokens);
|
||||
|
|
|
@ -11,12 +11,14 @@ use walkdir::WalkDir;
|
|||
|
||||
use crate::{config::Config, directory::search_path, media::{self, Move, get_file_header}};
|
||||
|
||||
// Struct to hold the TMDB API response
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct TMDBResponse {
|
||||
results: Vec<TMDBEntry>,
|
||||
total_results: i32
|
||||
}
|
||||
|
||||
// Struct to hold a movie from the TMDB API response
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct TMDBEntry {
|
||||
id: i32,
|
||||
|
@ -25,12 +27,14 @@ struct TMDBEntry {
|
|||
release_date: Option<String>,
|
||||
}
|
||||
|
||||
// Display implementation for the inquire selection dialog
|
||||
impl fmt::Display for TMDBEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} ({}, {}) (ID: {})", self.title, self.release_date.clone().unwrap_or("unknown".to_string()), self.original_language.as_ref().unwrap(), self.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Look up movie on the TMDB API
|
||||
fn lookup_movie(file_name: PathBuf, mut name_tokens: Vec<String>, cfg: Config) -> Option<TMDBEntry> {
|
||||
let mut h = HeaderMap::new();
|
||||
h.insert("Accept", HeaderValue::from_static("application/json"));
|
||||
|
@ -85,6 +89,7 @@ fn lookup_movie(file_name: PathBuf, mut name_tokens: Vec<String>, cfg: Config) -
|
|||
}
|
||||
}
|
||||
|
||||
// Handle single video file
|
||||
fn movie_video_file_handler(entry: PathBuf, cfg: Config) -> Option<TMDBEntry> {
|
||||
info!("Found movie video file: {:#?}", entry);
|
||||
|
||||
|
@ -99,6 +104,7 @@ fn movie_video_file_handler(entry: PathBuf, cfg: Config) -> Option<TMDBEntry> {
|
|||
lookup_movie(entry, name_tokens, cfg)
|
||||
}
|
||||
|
||||
// Handler for the sorted vectors of files and folders, gets called recursively for subfolders, if no primary media can be found
|
||||
pub fn handle_movie_files_and_folders(files: Vec<DirEntry>, folders: Vec<DirEntry>, cfg: Config) -> Vec<Move> {
|
||||
let mut moves: Vec<Move> = Vec::new();
|
||||
let mut primary_media: Option<TMDBEntry> = None; // Assuming first file (biggest file) is primary media, store the information of this, for the rest, do lazy matching for extra content/subs and so on
|
||||
|
@ -140,6 +146,7 @@ pub fn handle_movie_files_and_folders(files: Vec<DirEntry>, folders: Vec<DirEntr
|
|||
moves
|
||||
}
|
||||
|
||||
// Check files for movie, or if primary media has been marked as found for extras, show required inquire dialoges
|
||||
fn check_movie_file(file: PathBuf, primary_media: &mut Option<TMDBEntry>, cfg: &Config, moves: &mut Vec<Move>) {
|
||||
trace!("Checking {:#?}", file);
|
||||
match get_file_header(file.clone()) {
|
||||
|
|
|
@ -12,12 +12,14 @@ use regex::RegexBuilder;
|
|||
|
||||
use crate::{config::Config, media::{Move, self, get_file_header}, directory::search_path};
|
||||
|
||||
// Struct to hold the TMDB API response
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct TMDBResponse {
|
||||
results: Vec<TMDBEntry>,
|
||||
total_results: i32
|
||||
}
|
||||
|
||||
// Struct to hold a show from the TMDB API response
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
struct TMDBEntry {
|
||||
id: i32,
|
||||
|
@ -26,12 +28,14 @@ struct TMDBEntry {
|
|||
first_air_date: Option<String>,
|
||||
}
|
||||
|
||||
// Display implementation for the inquire selection dialog
|
||||
impl fmt::Display for TMDBEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} ({}, {}) (ID: {})", self.name, self.first_air_date.clone().unwrap_or("unknown".to_string()), self.original_language.as_ref().unwrap(), self.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Use directory name to find out show name, as opposed to file name for movies
|
||||
fn check_show_name(entry: PathBuf, cfg: Config) -> Option<TMDBEntry> {
|
||||
info!("Found folder: {:#?}", entry);
|
||||
|
||||
|
@ -42,6 +46,7 @@ fn check_show_name(entry: PathBuf, cfg: Config) -> Option<TMDBEntry> {
|
|||
lookup_show(entry, name_tokens, cfg)
|
||||
}
|
||||
|
||||
// Look up show on the TMDB API
|
||||
fn lookup_show(folder_name: PathBuf, mut name_tokens: Vec<String>, cfg: Config) -> Option<TMDBEntry> {
|
||||
if name_tokens.first().unwrap_or(&"".to_string()).eq_ignore_ascii_case("season") {
|
||||
// Is a season folder most likely, skip useless TMDB requests
|
||||
|
@ -100,14 +105,13 @@ fn lookup_show(folder_name: PathBuf, mut name_tokens: Vec<String>, cfg: Config)
|
|||
}
|
||||
}
|
||||
|
||||
// Handler for the sorted vectors of files and folders, gets called recursively for subfolders, if no primary media can be found
|
||||
pub fn handle_show_files_and_folders(directory: PathBuf, files: Vec<DirEntry>, folders: Vec<DirEntry>, cfg: Config) -> Vec<Move> {
|
||||
let mut moves: Vec<Move> = Vec::new();
|
||||
let mut primary_media: Option<TMDBEntry>;
|
||||
|
||||
// Check current directory for possible name
|
||||
primary_media = check_show_name(directory, cfg.clone());
|
||||
|
||||
//check_show_file(file.path(), &mut primary_media, &cfg, &mut moves);
|
||||
match primary_media {
|
||||
Some(_) => {
|
||||
// There is already primary media, check files and directories for more media for same show
|
||||
|
@ -148,6 +152,7 @@ pub fn handle_show_files_and_folders(directory: PathBuf, files: Vec<DirEntry>, f
|
|||
moves
|
||||
}
|
||||
|
||||
// Check files for episodes or subtitles, show required inquire dialoges
|
||||
fn check_show_file(file: PathBuf, primary_media: &mut Option<TMDBEntry>, cfg: &Config, moves: &mut Vec<Move>) {
|
||||
trace!("Checking {:#?}", file);
|
||||
match get_file_header(file.clone()) {
|
||||
|
|
Loading…
Reference in a new issue