#include #include #include #ifdef _WIN32 #include #include "resources_windows.h" #include #include using char_t = wchar_t; using string = std::wstring; constexpr const char_t *dir_divider = L"\\"; #define cerr std::wcerr #define cout std::wcout #define cin std::wcin #else #include "resources_linux.h" #include #include #include using char_t = char; using string = std::string; constexpr const char_t *dir_divider = "/"; #define cerr std::cerr #define cout std::cout #define cin std::cin #define DOMAIN "tv_rename" #endif #include "filesystem.hpp" #include "functions.hpp" #include "tv_rename.hpp" // DB flags #define DB_ADD 0x0001 #define DB_REFRESH 0x0002 #define DB_UPDATE 0x0004 #define DB_CLEAN 0x0008 #define DB_REMOVE 0x0010 #define DB_PATTERN 0x0020 #define API_KEY "42B66F5E-C6BF-423F-ADF9-CC97163472F6" // return 0 - all went as expected, 1 - request of help or print languages, // -1 - error int handleArgument( char_t c, string &show, std::set< int > &seasons_num, string &path, string &language, string &pattern, size_t &tv_flags, size_t &db_flags, string &db_pattern, char_t *optional, int &i ) { switch ( c ) { case 's': show = optional; i++; break; case 'n': parseSeasonNumbers( seasons_num, optional ); i++; break; case 'c': tv_flags |= TV_CHDIR; break; case 'p': path = optional; i++; // if path provided, assume it's correct tv_flags &= ~TV_CHDIR; break; case 't': tv_flags |= TV_TRUST; break; case 'x': tv_flags |= TV_LINUX; break; case 'd': tv_flags |= TV_DVD; break; case 'l': language = optional; i++; break; case 'h': printHelp(); return 1; case '0': return 2; case '1': pattern = optional; i++; break; case 'a': db_flags |= DB_ADD; break; case 'r': db_flags |= DB_REFRESH; break; case 'u': db_flags |= DB_UPDATE; break; case '2': db_flags |= DB_CLEAN; break; case '3': db_flags |= DB_REMOVE; break; case '4': db_pattern = optional; db_flags |= DB_PATTERN; i++; break; case '5': printPatternHelp(); return 1; default: return -1; } return 0; } #ifdef _WIN32 string getOptions( const char_t *option ) { if ( option[0] != '-' ) return L""; else if ( option[1] != '-' ) return option + 1; else if ( !wcscmp( option, L"--show" ) ) return L"s"; else if ( !wcscmp( option, L"--season" ) ) return L"n"; else if ( !wcscmp( option, L"--correct-path" ) ) return L"c"; else if ( !wcscmp( option, L"--trust" ) ) return L"t"; else if ( !wcscmp( option, L"--linux" ) ) return L"x"; else if ( !wcscmp( option, L"--dvd" ) ) return L"d"; else if ( !wcscmp( option, L"--lang" ) ) return L"l"; else if ( !wcscmp( option, L"--help" ) ) return L"h"; else if ( !wcscmp( option, L"--print-langs" ) ) return L"0"; else if ( !wcscmp( option, L"--name-pattern" ) ) return L"1"; else if ( !wcscmp( option, L"--db-add" ) ) return L"a"; else if ( !wcscmp( option, L"--db-refresh" ) ) return L"r"; else if ( !wcscmp( option, L"--db-update" ) ) return L"u"; else if ( !wcscmp( option, L"--db-clean" ) ) return L"2"; else if ( !wcscmp( option, L"--db-remove" ) ) return L"3"; else if ( !wcscmp( option, L"--db-name-pattern" ) ) return L"4"; else if ( !wcscmp( option, L"--pattern-help" ) ) return L"5"; return L""; } // there's no getopt for windows, so just use wcscmp int parseCommandLine( string &show, std::set< int > &seasons_num, string &path, string &language, string &pattern, size_t &tv_flags, size_t &db_flags, string &db_pattern, const int argc, char_t **argv ) { string options{}; char_t *optional; for ( auto i = 1; i < argc; i++ ) { options = getOptions( argv[i] ); if ( options == L"" ) { options = L"p"; optional = argv[i]; } else { optional = ( i < argc - 1 ) ? argv[i + 1] : nullptr; } for ( const auto &x : options ) { auto res = handleArgument( x, show, seasons_num, path, language, pattern, tv_flags, db_flags, db_pattern, optional, i ); if ( res != 0 ) return res; } } return 0; } #else // parse command line arguments using getopt int parseCommandLine( string &show, std::set< int > &seasons_num, string &path, string &language, string &pattern, size_t &tv_flags, size_t &db_flags, string &db_pattern, int argc, char **argv ) { static struct option long_options[] = { { "show", required_argument, 0, 's' }, { "season", required_argument, 0, 'n' }, { "correct-path", no_argument, 0, 'c' }, { "trust", no_argument, 0, 't' }, { "linux", no_argument, 0, 'x' }, { "dvd", no_argument, 0, 'd' }, { "lang", required_argument, 0, 'l' }, { "help", no_argument, 0, 'h' }, { "print-langs", no_argument, 0, '0' }, { "name-pattern", required_argument, 0, '1' }, { "db-add", no_argument, 0, 'a' }, { "db-refresh", no_argument, 0, 'r' }, { "db-update", no_argument, 0, 'u' }, { "db-clean", no_argument, 0, '2' }, { "db-remove", no_argument, 0, '3' }, { "db-name-pattern", required_argument, 0, '4' }, { "pattern-help", no_argument, 0, '5' }, { 0, 0, 0, 0 } }; int i{}; // this is useless, but needed for handleArgument while ( 1 ) { int option_index{ 0 }; auto c = getopt_long( argc, argv, "s:n:cp:txl:arudh", long_options, &option_index ); if ( c == -1 ) break; auto res = handleArgument( c, show, seasons_num, path, language, pattern, tv_flags, db_flags, db_pattern, optarg, i ); if ( res != 0 ) return res; } if ( optind < argc ) { path = string( argv[optind] ); // if path provided, assume it's correct tv_flags &= ~TV_CHDIR; } return 0; } #endif void ensureShowNotEmpty( string &show, const string &path ) { if ( show.empty() ) { show = FSLib::canonical( path ); auto pos = show.find_last_of( dir_divider ); if ( pos != string::npos ) show = show.substr( ++pos ); cout << _( SHOW_CONFIRM ) << ' ' << show << std::endl; string response; cin >> response; cin.ignore( 1, '\n' ); cin.clear(); if ( response[0] != 'y' && response[0] != 'Y' ) { cout << _( SHOW_REENTER ) << std::endl; std::getline( cin, show ); } } } // windows needs wmain for unicode support #ifdef _WIN32 int wmain #else int main #endif ( int argc, char_t **argv ) { #ifdef _WIN32 // set console to unicode _setmode( _fileno( stdout ), _O_U8TEXT ); #else if ( setlocale( LC_ALL, "" ) == NULL ) error( 1, errno, "%s", _( INVALID_LOCALE ).c_str() ); if ( FSLib::exists( "/usr/share/locale/en_US/LC_MESSAGES/tv_rename.mo" ) ) { if ( bindtextdomain( DOMAIN, "/usr/share/locale" ) == NULL ) error( 1, errno, "%s", _( MEM_ALLOC_FAILED ).c_str() ); if ( textdomain( DOMAIN ) == NULL ) error( 1, errno, "%s", _( MEM_ALLOC_FAILED ).c_str() ); } else { if ( bindtextdomain( DOMAIN, "./locale" ) == NULL ) error( 1, errno, "%s", _( MEM_ALLOC_FAILED ).c_str() ); if ( textdomain( DOMAIN ) == NULL ) error( 1, errno, "%s", _( MEM_ALLOC_FAILED ).c_str() ); } #endif string show{}; std::set< int > seasons_num{}; size_t tv_flags{}; size_t db_flags{}; string path{ TEXT( "." ) }; string language{ TEXT( "en" ) }; string pattern{ TEXT( "%filename - %epname" ) }; string db_pattern{}; auto ret = parseCommandLine( show, seasons_num, path, language, pattern, tv_flags, db_flags, db_pattern, argc, argv ); if ( ret == -1 ) return 1; else if ( ret == 1 ) return 0; authenticate( API_KEY ); if ( ret == 2 ) { printLangs(); return 0; } if ( !findLanguage( language.c_str() ) ) { cerr << _( INVALID_CHOICE ) << std::endl; printLangs(); return -1; } if ( !FSLib::isDirectory( path ) && FSLib::exists( path ) ) { // specified file, not directory std::map< int, string > file_set{}; size_t season_pos{}, ep_pos{}; if ( !searchSeason( path.c_str(), season_pos, ep_pos ) ) { cerr << _( PATTERN_ERROR ) << std::endl; return 1; } else { file_set[std::stoi( path.c_str() + ep_pos )] = path; auto season = std::stoi( path.c_str() + season_pos ); singleSeason( path, show, season, TEXT( "" ), language, pattern, tv_flags, &file_set, true ); return 0; } } else if ( !FSLib::isDirectory( path ) ) { tv_flags |= TV_CHDIR; } if ( !db_pattern.empty() ) { changeDBPattern( db_pattern ); } if ( db_flags & DB_REMOVE && db_flags & DB_ADD ) { cerr << _( ADD_REMOVE_FLAG_ERROR ) << std::endl; return 1; } if ( db_flags & DB_REMOVE ) { removeFromDB( FSLib::canonical( path ) ); } if ( db_flags & DB_ADD ) { ensureShowNotEmpty( show, path ); addToDB( show, path, language, tv_flags & TV_LINUX, tv_flags & TV_DVD ); cout << _( ADDED_TO_DB ) << std::endl; } if ( db_flags & DB_REFRESH ) { refreshDB( tv_flags & TV_LINUX ); cout << _( REFRESHED_DB ) << std::endl; } if ( db_flags & DB_UPDATE ) { updateDB( tv_flags & TV_LINUX ); cout << _( UPDATED_DB ) << std::endl; } if ( db_flags & DB_CLEAN ) { cleanDB(); cout << _( CLEANED_DB ) << std::endl; } // if db operations happened don't continue if ( db_flags ) return 0; while ( tv_flags & TV_CHDIR ) { if ( !FSLib::isDirectory( path ) ) { cout << _( DIR_FAIL_REENTER ) << std::endl; std::getline( cin, path ); continue; } cout << _( DIR_CONFIRM ) << ' ' << FSLib::canonical( path ) << std::endl; string response; cin >> response; cin.ignore( 1, '\n' ); cin.clear(); if ( response[0] == 'y' || response[0] == 'Y' ) { tv_flags &= ~TV_CHDIR; } else { cout << _( DIR_REENTER ) << std::endl; std::getline( cin, path ); } } ensureShowNotEmpty( show, path ); if ( seasons_num.size() == 1 ) { singleSeason( path, show, *seasons_num.begin(), string(), language, pattern, tv_flags, nullptr, true ); } else if ( seasons_num.size() != 0 ) { multipleSeasons( path, show, seasons_num, language, pattern, tv_flags ); } else { allSeasons( path, show, language, pattern, tv_flags ); } }