diff --git a/AVE-PHP.iss b/AVE-PHP.iss index 439f7a2..232fdb7 100644 --- a/AVE-PHP.iss +++ b/AVE-PHP.iss @@ -1,5 +1,5 @@ #define MyAppName "AVE-PHP" -#define MyAppVersion "1.9.3" +#define MyAppVersion "1.9.4" #define MyAppPublisher "Abyss Morgan" #define MyAppURL "https://github.com/AbyssMorgan" #define MyAppExeName "AVE-PHP.cmd" diff --git a/Changelog.txt b/Changelog.txt index b991ddc..d3a807b 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,11 @@ +v1.9.4: +- Added tool: FTP Tools > Import FileZilla XML +- Added tool: File Functions > Clone files with structure (Mirror) +- Increased MySQL and FTP label max length from 20 to 32 characters + +v1.9.3: +- Fix functional error in "Sort Files: Extension" + v1.9.2: - Compatibility updates for Linux operating system - Added config AVE_OPEN_FILE_BINARY diff --git a/includes/AVE.php b/includes/AVE.php index 4139fd9..e1c4b0c 100644 --- a/includes/AVE.php +++ b/includes/AVE.php @@ -25,7 +25,7 @@ class AVE extends AveCore { public bool $abort = false; public string $app_name = "AVE-PHP"; - public string $version = "1.9.3"; + public string $version = "1.9.4"; private array $folders_to_scan = [ 'bin', @@ -158,6 +158,7 @@ public function execute() : void { break; } case '--interactive': { + $this->can_exit = false; while(!$this->abort){ $this->abort = $this->select_tool(); } @@ -248,7 +249,7 @@ public function select_tool() : bool { break; } case '#': { - return true; + if($this->can_exit) return true; } } if(!$this->abort && !is_null($this->tool)){ diff --git a/includes/services/AveCore.php b/includes/services/AveCore.php index f4a43a5..9c32d49 100644 --- a/includes/services/AveCore.php +++ b/includes/services/AveCore.php @@ -38,6 +38,7 @@ class AveCore { public string $utilities_version = "1.0.0"; public string $current_title; public array $drives = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']; + public bool $can_exit = true; public function __construct(array $arguments, bool $require_utilities){ date_default_timezone_set(IntlTimeZone::createDefault()->getID()); @@ -187,7 +188,7 @@ public function progress(int|float $count, int|float $total) : void { } public function is_valid_label(string $label) : bool { - return preg_match('/(?=[a-zA-Z0-9_\-]{3,20}$)/i', $label) == 1; + return preg_match('/(?=[a-zA-Z0-9_\-]{3,32}$)/i', $label) == 1; } public function progress_ex(string $label, int|float $count, int|float $total) : void { diff --git a/includes/tools/AveSettings.php b/includes/tools/AveSettings.php index aaa82df..dc94b83 100644 --- a/includes/tools/AveSettings.php +++ b/includes/tools/AveSettings.php @@ -124,7 +124,7 @@ public function check_for_updates(string &$version) : bool { $ver_repo = explode(".", $response['data']); $ver_current = intval($ver_current[0])*10000 + intval($ver_current[1])*100 + intval($ver_current[2]); $ver_repo = intval($ver_repo[0])*10000 + intval($ver_repo[1])*100 + intval($ver_repo[2]); - $version = strval($ver_repo); + $version = strval($response['data']); return ($ver_repo > $ver_current); } $this->ave->echo(" Failed check for updates: ".$response['code']); diff --git a/includes/tools/FileFunctions.php b/includes/tools/FileFunctions.php index b01de06..3534b7b 100644 --- a/includes/tools/FileFunctions.php +++ b/includes/tools/FileFunctions.php @@ -28,6 +28,7 @@ public function help() : void { ' 3 - Overwrite folders content', ' 4 - Move files with structure', ' 5 - Copy files with structure', + ' 6 - Clone files with structure (Mirror)', ]); } @@ -41,6 +42,7 @@ public function action(string $action) : bool { case '3': return $this->ToolOverwriteFoldersContent(); case '4': return $this->ToolMoveFilesWithStructure(); case '5': return $this->ToolCopyFilesWithStructure(); + case '6': return $this->ToolCloneFilesWithStructure(); } return false; } @@ -175,8 +177,6 @@ public function ToolValidateCheckSum() : bool { $except_extensions = explode(" ", $this->ave->config->get('AVE_IGNORE_VALIDATE_EXTENSIONS')); foreach($folders as $folder){ if(!file_exists($folder)) continue; - $file_id = 1; - $list = []; $files = $this->ave->get_files($folder, null, $except_extensions); $items = 0; $total = count($files); @@ -585,6 +585,152 @@ public function ToolCopyFilesWithStructure() : bool { return false; } + public function ToolCloneFilesWithStructure() : bool { + $this->ave->set_subtool("CloneFilesWithStructure"); + + set_mode: + $this->ave->clear(); + $this->ave->print_help([ + ' Checksum algorithm:', + ' 0 - md5 (default)', + ' 1 - sha256', + ' 2 - crc32', + ' 3 - whirlpool', + ]); + + $line = $this->ave->get_input(" Algorithm: "); + if($line == '#') return false; + + $this->params = [ + 'algo' => strtolower($line[0] ?? '0'), + ]; + + if(!in_array($this->params['algo'], ['0', '1', '2', '3'])) goto set_mode; + + $this->ave->clear(); + + set_input: + $line = $this->ave->get_input(" Input: "); + if($line == '#') return false; + $folders = $this->ave->get_input_folders($line); + if(!isset($folders[0])) goto set_input; + $input = $folders[0]; + + if(!file_exists($input) || !is_dir($input)){ + $this->ave->echo(" Invalid input folder"); + goto set_input; + } + + set_output: + $line = $this->ave->get_input(" Output: "); + if($line == '#') return false; + $folders = $this->ave->get_input_folders($line); + if(!isset($folders[0])) goto set_output; + $output = $folders[0]; + + if($input == $output){ + $this->ave->echo(" Output folder must be different than input folder"); + goto set_output; + } + + if((file_exists($output) && !is_dir($output)) || !$this->ave->mkdir($output)){ + $this->ave->echo(" Invalid output folder"); + goto set_output; + } + + $errors = 0; + $this->ave->set_errors($errors); + + $algo = $this->ave->get_hash_alghoritm(intval($this->params['algo']))['name']; + + $this->ave->echo(" Delete not existing files on output"); + $files = $this->ave->get_files($output); + $items = 0; + $total = count($files); + foreach($files as $file){ + $items++; + $new_name = str_ireplace($output, $input, $file); + if(!file_exists($new_name)){ + if(!$this->ave->unlink($file)){ + $errors++; + } + } + } + $this->ave->progress($items, $total); + + $this->ave->echo(" Delete not existing folders on output"); + $files = $this->ave->get_folders($output); + $items = 0; + $total = count($files); + foreach($files as $file){ + $items++; + $new_name = str_ireplace($output, $input, $file); + if(!file_exists($new_name)){ + if(!$this->ave->rmdir($file)){ + $errors++; + } + } + } + $this->ave->progress($items, $total); + + $this->ave->echo(" Clone folder structure"); + $folders = $this->ave->get_folders($input); + $items = 0; + $total = count($folders); + foreach($folders as $folder){ + $items++; + $directory = str_ireplace($input, $output, $folder); + if(!file_exists($directory)){ + if(!$this->ave->mkdir($directory)){ + $errors++; + } + } + $this->ave->progress($items, $total); + $this->ave->set_errors($errors); + } + $this->ave->progress($items, $total); + + $this->ave->echo(" Clone new/changed files"); + $files = $this->ave->get_files($input); + $items = 0; + $total = count($files); + foreach($files as $file){ + $items++; + if(!file_exists($file)) continue; + $new_name = str_ireplace($input, $output, $file); + if(file_exists($new_name)){ + if(file_exists("$file.$algo")){ + $hash_input = file_get_contents("$file.$algo"); + } else { + $hash_input = hash_file($algo, $file); + } + if(file_exists("$new_name.$algo")){ + $hash_output = file_get_contents("$new_name.$algo"); + } else { + $hash_output = hash_file($algo, $new_name); + } + if($hash_input != $hash_output){ + if(!$this->ave->unlink($new_name)){ + $errors++; + } else if(!$this->ave->copy($file, $new_name)){ + $errors++; + } + } + } else { + if(!$this->ave->copy($file, $new_name)){ + $errors++; + } + } + $this->ave->progress($items, $total); + $this->ave->set_errors($errors); + } + $this->ave->progress($items, $total); + + $this->ave->open_logs(true); + $this->ave->pause(" Operation done, press any key to back to menu"); + return false; + } + } ?> diff --git a/includes/tools/FileNamesEditor.php b/includes/tools/FileNamesEditor.php index f0ec448..dd89c95 100644 --- a/includes/tools/FileNamesEditor.php +++ b/includes/tools/FileNamesEditor.php @@ -474,14 +474,19 @@ public function ToolGenerateSeriesName() : bool { foreach($files as $file){ $items++; if(!file_exists($file)) continue 1; - $file_name = str_replace(['SEASON','EPISODE',' '], ['S','E',''], strtoupper(pathinfo($file, PATHINFO_FILENAME))); - if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}(.*)E[0-9]{1,3}/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); - } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); - } else if(preg_match("/\[S[0-9]{2}\.E[0-9]{1,3}\]/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); - } else if(preg_match("/(\[S0\.)(E[0-9]{1,3})\]/", $file_name, $mathes) == 1){ + $file_name = str_replace(['SEASON', 'EPISODE'], ['S', 'E'], strtoupper(pathinfo($file, PATHINFO_FILENAME))); + $file_name = str_replace([' ', '.', '[', ']'], '', $file_name); + if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}E[0-9]{1,3}/", $file_name, $mathes) == 1){ + $escaped_name = $mathes[0]; + } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}-E[0-9]{1,3}/", $file_name, $mathes) == 1){ + $escaped_name = $mathes[0]; + } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}-[0-9]{1,3}/", $file_name, $mathes) == 1){ + $escaped_name = $mathes[0]; + } else if(preg_match("/(S[0-9]{1,2})(E[0-9]{1,3})/", $file_name, $mathes) == 1){ + if(strlen($mathes[1]) == 2) $mathes[1] = "S0".substr($mathes[1],1,1); + if(strlen($mathes[2]) == 2) $mathes[2] = "E0".substr($mathes[2],1,1); + $escaped_name = $mathes[1].$mathes[2]; + } else if(preg_match("/(S0)(E[0-9]{1,3})/", $file_name, $mathes) == 1){ $escaped_name = "S01".preg_replace("/[^E0-9]/i", "", $mathes[2], 1); } else { $escaped_name = ''; diff --git a/includes/tools/FtpTools.php b/includes/tools/FtpTools.php index 52de7d5..605c3ef 100644 --- a/includes/tools/FtpTools.php +++ b/includes/tools/FtpTools.php @@ -43,6 +43,7 @@ public function help() : void { ' 8 - Delete empty folders', ' 9 - Delete structure (folders and files)', ' 10 - Copy files from FTP to FTP', + ' 11 - Import FileZilla XML', ]); } @@ -61,6 +62,7 @@ public function action(string $action) : bool { case '8': return $this->ToolDeleteEmptyFolders(); case '9': return $this->ToolDeleteStructure(); case '10': return $this->ToolCopyFilesFromFTPToFTP(); + case '11': return $this->ToolImportFileZillaXML(); } return false; } @@ -99,7 +101,7 @@ public function ToolConfigureConnection() : bool { $this->ave->print_help([ ' Allowed characters: A-Z a-z 0-9 _ -', - ' Label length 3 - 20', + ' Label length 3 - 32', ]); set_label: @@ -216,7 +218,7 @@ public function ToolShowConnections() : bool { $ini = new IniFile($file); if($ini->isValid() && $ini->isSet('FTP_HOST')){ $label = pathinfo($file, PATHINFO_FILENAME); - $this->ave->echo(" $label".str_repeat(" ",20-strlen($label))." ".$ini->get('FTP_HOST').":".$ini->get('FTP_PORT')."@".$ini->get('FTP_USER')); + $this->ave->echo(" $label".str_repeat(" ",32-strlen($label))." ".$ini->get('FTP_HOST').":".$ini->get('FTP_PORT')."@".$ini->get('FTP_USER')); $cnt++; } } @@ -857,6 +859,85 @@ public function ToolCopyFilesFromFTPToFTP() : bool { return false; } + public function ToolImportFileZillaXML() : bool { + $this->ave->clear(); + $this->ave->set_subtool("ImportFileZillaXML"); + + set_xml_file: + $line = $this->ave->get_input(" XML file: "); + if($line == '#') return false; + $line = $this->ave->get_input_folders($line); + if(!isset($line[0])) goto set_xml_file; + $input = $line[0]; + + if(!file_exists($input) || is_dir($input)){ + $this->ave->echo(" Invalid file"); + goto set_xml_file; + } + + $xml = file_get_contents($this->ave->get_file_path($input)); + + $xml = str_replace(["\r","\n","\t"], '', $xml); + $xml = preg_replace('/]+>[^>]+>/', '', $xml); + $xml = str_replace(['', '', '', ''], '', $xml); + $xml = @simplexml_load_string($xml); + if($xml === false){ + $this->ave->echo(" Failed parse XML"); + goto set_xml_file; + } + + $data = json_decode(json_encode($xml), true); + + if(isset($data['Server']) && gettype($data['Server']) == 'array'){ + foreach($data['Server'] as $key => $server){ + if(!isset($server['Name'])){ + $this->ave->echo(" Import servers[$key] failed, missing property: Name"); + continue; + } + if(!isset($server['Host'])){ + $this->ave->echo(" Import ".$server['Name']." failed, missing property: Host"); + continue; + } + if(!isset($server['Port'])){ + $this->ave->echo(" Import ".$server['Name']." failed, missing property: Port"); + continue; + } + if(!isset($server['User'])){ + $this->ave->echo(" Import ".$server['Name']." failed, missing property: User"); + continue; + } + if(!isset($server['Pass'])){ + $this->ave->echo(" Import ".$server['Name']." failed, missing property: Pass"); + continue; + } + if(!isset($server['Protocol'])){ + $this->ave->echo(" Import ".$server['Name']." failed, missing property: Protocol"); + continue; + } + $label = substr(preg_replace("/[^A-Za-z0-9_\-]/", '', str_replace(" ", "_", trim($server['Name']))), 0, 32); + if(strlen($label) < 3) substr($label."___", 0, 3); + if($this->ave->is_valid_label($label)){ + $ini = $this->getConfig($label); + $ini->update([ + 'FTP_HOST' => $server['Host'], + 'FTP_USER' => $server['User'], + 'FTP_PASSWORD' => base64_decode($server['Pass']), + 'FTP_SSL' => ($server['Protocol'] == 1), + 'FTP_PORT' => intval($server['Port']), + ], true); + $ini->close(); + $this->ave->echo(" Import ".$server['Name']." as $label success"); + } else { + $this->ave->echo(" Import ".$server['Name']." failed, invalid label"); + } + } + } + + $this->ave->open_logs(true); + $this->ave->pause(" Operation done, press any key to back to menu"); + return false; + } + public function SetupFTP(string $name, bool $print = true) : FtpClient|bool { if($print) $this->getSelectLabel(); set_label: diff --git a/includes/tools/MediaSorter.php b/includes/tools/MediaSorter.php index 2051f24..4892501 100644 --- a/includes/tools/MediaSorter.php +++ b/includes/tools/MediaSorter.php @@ -362,8 +362,6 @@ public function ToolSortVideosAutoDetectSeriesName() : bool { goto set_input; } - $media = new MediaFunctions($this->ave); - $errors = 0; $this->ave->set_errors($errors); $video_extensions = explode(" ", $this->ave->config->get('AVE_EXTENSIONS_VIDEO')); @@ -373,26 +371,24 @@ public function ToolSortVideosAutoDetectSeriesName() : bool { foreach($files as $file){ $items++; if(!file_exists($file)) continue; - - $file_name = str_replace(['SEASON', 'EPISODE', ' '], ['S', 'E', ''], strtoupper(pathinfo($file, PATHINFO_FILENAME))); - if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}(.*)E[0-9]{1,3}/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); + $file_name = str_replace(['SEASON', 'EPISODE'], ['S', 'E'], strtoupper(pathinfo($file, PATHINFO_FILENAME))); + $file_name = str_replace([' ', '.', '[', ']'], '', $file_name); + if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}E[0-9]{1,3}/", $file_name, $mathes) == 1){ $marker = $mathes[0]; - } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); + } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}-E[0-9]{1,3}/", $file_name, $mathes) == 1){ $marker = $mathes[0]; - } else if(preg_match("/\[S[0-9]{2}\.E[0-9]{1,3}\]/", $file_name, $mathes) == 1){ - $escaped_name = preg_replace("/[^SE0-9]/i", "", $mathes[0], 1); + } else if(preg_match("/S[0-9]{1,2}E[0-9]{1,3}-[0-9]{1,3}/", $file_name, $mathes) == 1){ $marker = $mathes[0]; - } else if(preg_match("/(\[S0\.)(E[0-9]{1,3})\]/", $file_name, $mathes) == 1){ - $escaped_name = "S01".preg_replace("/[^E0-9]/i", "", $mathes[2], 1); - $marker = $mathes[2]; + } else if(preg_match("/(S[0-9]{1,2})(E[0-9]{1,3})/", $file_name, $mathes) == 1){ + if(strlen($mathes[1]) == 2) $mathes[1] = "S0".substr($mathes[1],1,1); + if(strlen($mathes[2]) == 2) $mathes[2] = "E0".substr($mathes[2],1,1); + $marker = $mathes[1].$mathes[2]; + } else if(preg_match("/(S0)(E[0-9]{1,3})/", $file_name, $mathes) == 1){ + $marker = "S01".preg_replace("/[^E0-9]/i", "", $mathes[2], 1); } else { - $escaped_name = ''; - $this->ave->write_error("FAILED GET SERIES ID \"$file\""); - $errors++; + $marker = ''; } - if(!empty($escaped_name)){ + if(!empty($marker)){ $end = strpos($file_name, $marker); if($end === false){ $this->ave->write_error("FAILED GET MARKER \"$file\""); @@ -409,7 +405,6 @@ public function ToolSortVideosAutoDetectSeriesName() : bool { $errors++; } else { $new_name = $this->ave->get_file_path("$input/$folder_name/".pathinfo($file, PATHINFO_BASENAME)); - $new_path = $this->ave->get_file_path("$input/$folder_name"); if(file_exists($new_name) && strtoupper($new_name) != strtoupper($file)){ $this->ave->write_error("DUPLICATE \"$file\" AS \"$new_name\""); $errors++; @@ -471,15 +466,12 @@ public function ToolSortMediaDuration() : bool { } if($renamed){ $name = pathinfo($file, PATHINFO_FILENAME); - $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); - $follow_extensions = explode(" ", $this->ave->config->get('AVE_EXTENSIONS_VIDEO_FOLLOW')); foreach($follow_extensions as $a){ if(file_exists("$file.$a")){ if(!$this->ave->rename("$file.$a", "$new_name.$a")) $errors++; } } - $name_old = $this->ave->get_file_path(pathinfo($file, PATHINFO_DIRNAME)."/$name.srt"); $name_new = $this->ave->get_file_path("$directory/$name.srt"); if(file_exists($name_old)){ diff --git a/includes/tools/MySQLTools.php b/includes/tools/MySQLTools.php index 3efab9c..c278e01 100644 --- a/includes/tools/MySQLTools.php +++ b/includes/tools/MySQLTools.php @@ -113,7 +113,7 @@ public function ToolConfigureConnection() : bool { $this->ave->print_help([ ' Allowed characters: A-Z a-z 0-9 _ -', - ' Label length 3 - 20', + ' Label length 3 - 32', ]); set_label: @@ -253,7 +253,7 @@ public function ToolShowConnections() : bool { $ini = new IniFile($file); if($ini->isValid() && $ini->isSet('DB_HOST')){ $label = pathinfo($file, PATHINFO_FILENAME); - $this->ave->echo(" $label".str_repeat(" ",20-strlen($label))." ".$ini->get('DB_HOST').":".$ini->get('DB_PORT')."@".$ini->get('DB_USER')); + $this->ave->echo(" $label".str_repeat(" ",32-strlen($label))." ".$ini->get('DB_HOST').":".$ini->get('DB_PORT')."@".$ini->get('DB_USER')); $cnt++; } } @@ -1474,7 +1474,7 @@ public function ToolCompareDataBaseInfo() : bool { } } - foreach($errors as $error_type => $error_data){ + foreach($errors as $error_data){ if(!empty($error_data)) $this->ave->write_data($error_data); } diff --git a/version b/version index 7b0231f..70b02ff 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.9.3 \ No newline at end of file +1.9.4 \ No newline at end of file