#include #include #include #include #include #include "filesystem.hpp" #include "functions.hpp" #include "progress.hpp" #include "sqlitepp.hpp" #include "tv_rename.hpp" #ifdef _WIN32 #include "resources_windows.h" #include #include #define cout std::wcout #define cerr std::wcerr #define cin std::wcin constexpr const char_t *dir_divider = L"\\"; #else // UNIX #include "resources_linux.h" #include #include #include #include #define cout std::cout #define cerr std::cerr #define cin std::cin constexpr const char_t *dir_divider = "/"; #endif // UNIX #ifdef _WIN32 // functions to convert between string and wstring std::string wstring_to_utf8( const std::wstring &wstring ) { std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv; return wconv.to_bytes( wstring ); } std::wstring utf8_to_wstring( const std::string &utf8 ) { std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv; return wconv.from_bytes( utf8 ); } std::wstring LMsg( int id, ... ) { static wchar_t local[MAX_PATH]; auto hInstance = GetModuleHandle( NULL ); LoadString( hInstance, id, local, MAX_PATH ); va_list args; va_start( args, id ); // _vscprintf doesn't count ending '\0' int len = _vscwprintf( local, args ) + 1; va_end( args ); wchar_t *text = new wchar_t[len]; va_start( args, id ); vswprintf( text, len + 1, local, args ); va_end( args ); std::wstring ret = text; delete[] text; return ret; } #else std::string getlocalized( const char *id, ... ) { const char *local = gettext( id ); va_list args; va_start( args, id ); int len = vsnprintf( nullptr, 0, local, args ) + 1; va_end( args ); char *text = new char[len]; va_start( args, id ); vsnprintf( text, len + 1, local, args ); va_end( args ); std::string ret = text; delete[] text; return ret; } #endif // _WIN32 // encode url so it's valid even with UTF-8 characters string encodeUrl( const string &url ) { // stolen from here - // https://stackoverflow.com/questions/154536/encode-decode-urls-in-c #ifdef _WIN32 std::wstringstream encoded; auto url_c = wstring_to_utf8( url ); #else std::stringstream encoded; const auto &url_c = url; #endif encoded.fill( '0' ); encoded << std::hex; for ( const auto &x : url_c ) { if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' || x == '_' || x == '.' || x == '~' || x == '+' ) { encoded << x; continue; } encoded << std::uppercase << '%' << std::setw( 2 ); encoded << int( static_cast< unsigned char >( x ) ) << std::nouppercase; } return encoded.str(); } // return true if file contains S[0-9]+E[0-9]+, set // season_pos to start of season number and ep_pos to start of episode number bool searchSeason( const char_t *const path, size_t &season_pos, size_t &ep_pos ) { size_t cur_pos{}; while ( path[cur_pos] != '\0' ) { if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) && iswdigit( path[cur_pos + 1] ) ) { cur_pos++; if ( path[cur_pos] == '\0' ) return false; season_pos = cur_pos; // after ++ because we want the first pos to // point to season's number while ( iswdigit( path[cur_pos] ) ) cur_pos++; if ( path[cur_pos] == '\0' ) return false; if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) && iswdigit( path[cur_pos + 1] ) ) { ep_pos = cur_pos + 1; return true; } } cur_pos++; } return false; } bool searchSeason( const char_t *const path, size_t &season_pos ) { size_t tmp; return searchSeason( path, season_pos, tmp ); } void iterateFS( std::map< int, std::map< int, string > > &seasons, const string &path ) { // season_pos - position of first digit of the season number // ep_pos - position of first digit of the episode number size_t season_pos{ string::npos }; size_t ep_pos{ string::npos }; for ( const auto p : FSLib::Directory( path ) ) { // if p is directory, iterate through it if ( FSLib::isDirectory( path + dir_divider + p ) ) { iterateFS( seasons, path + dir_divider + p ); continue; } // if file is a correct format, add it to file list // for its season if ( searchSeason( p, season_pos, ep_pos ) ) seasons[std::stoi( p + season_pos )][std::stoi( p + ep_pos )] = path + dir_divider + p; } } #ifndef GUI // following functions are only needed for CLI version void printHelp() { cout << _( HELP_USAGE ) << std::endl; cout << " tv_rename [" << _( HELP_OPTIONS_SMALL ) << "] [" << _( HELP_PATH_SMALL ) << "]" << std::endl << std::endl; cout << " -h, --help " << _( HELP_HELP ) << std::endl << std::endl; cout << _( HELP_PATH_INFO ) << std::endl << std::endl; cout << _( HELP_OPTIONS ) << std::endl; cout << " -s, --show " << _( HELP_SHOW ) << std::endl; cout << " -n, --season " << _( HELP_SEASON ) << std::endl; cout << "" << std::endl; cout << " -d, --dvd " << _( HELP_DVD ) << std::endl; cout << " --name-pattern " << _( HELP_PATTERN ) << std::endl; cout << " --pattern-help " << _( HELP_PATTERN_HELP ) << std::endl; cout << " -c, --correct-path " << _( HELP_CORRECT_PATH ) << std::endl; cout << " -t, --trust " << _( HELP_TRUST ) << std::endl; cout << " -x, --linux " << _( HELP_LINUX ) << std::endl; cout << " -l, --lang " << _( HELP_LANG ) << std::endl; cout << " --print-langs " << _( HELP_LANGS ) << std::endl; cout << std::endl; cout << _( HELP_DATABASE_OPTS ) << std::endl; cout << " --db-add " << _( HELP_ADD_DB ) << std::endl; cout << " --db-refresh " << _( HELP_REFRESH_DB ) << std::endl; cout << " --db-update " << _( HELP_UPDATE_DB ) << std::endl; cout << " --db-name-pattern " << _( HELP_DB_PATTERN ) << std::endl; cout << " --db-clean " << _( HELP_CLEAN_DB ) << std::endl; cout << " --db-remove " << _( HELP_REMOVE_DB ) << std::endl; } void printPatternHelp() { cout << _( PATTERN_POSSIBLE ) << std::endl; cout << " %filename - " << _( PATTERN_FILENAME ) << std::endl; cout << " %show - " << _( PATTERN_SHOW ) << std::endl; cout << " %epname - " << _( PATTERN_EPNAME ) << std::endl; cout << " %season - " << _( PATTERN_SEASON ) << std::endl; cout << " " << _( PATTERN_LEADING_ZERO ) << std::endl; cout << " %2season " << _( PATTERN_LEADING_NUM ) << std::endl; cout << " %episode - " << _( PATTERN_EPISODE ) << std::endl; cout << " " << _( PATTERN_LEADING_ZERO ) << std::endl; cout << " %2episode " << _( PATTERN_LEADING_NUM ) << std::endl; cout << _( PATTERN_DEFAULT ) << " \"%filename - %epname\"" << std::endl; } // parse command line argument --seasons (e.g. '1 2 3 4 5') // and store season numbers as integers in seasons_num void parseSeasonNumbers( std::set< int > &seasons_num, const char_t *argument ) { size_t pos{ 0 }; while ( !iswdigit( argument[pos] ) && argument[pos] != '\0' ) pos++; if ( argument[pos] == '\0' ) { seasons_num.clear(); return; } int temp; #ifdef _WIN32 std::wstringstream iss( argument + pos ); #else std::stringstream iss( argument + pos ); #endif while ( iss >> temp ) { seasons_num.insert( temp ); } } #endif // ndef GUI #ifdef _WIN32 // get user's %APPDATA% folder location string userHome() { wchar_t *dir = static_cast< wchar_t * >( CoTaskMemAlloc( MAX_PATH ) ); auto res = SHGetKnownFolderPath( FOLDERID_RoamingAppData, 0, NULL, &dir ); if ( res == S_OK ) { string dir_s = dir; CoTaskMemFree( dir ); return dir_s; } CoTaskMemFree( dir ); // runtime_error doesn't support wstring throw std::runtime_error( "Couldn't find user's appdata" ); } #else // UNIX // get user's home directory string userHome() { uid_t user_uid; // current user's uid { uid_t eid; uid_t sid; getresuid( &user_uid, &eid, &sid ); // don't need eid and sid } // password file entry auto user_passwd = getpwuid( user_uid ); if ( user_passwd == nullptr ) { throw std::runtime_error( _( USER_NOT_EXIST, user_uid ) ); } return user_passwd->pw_dir; } #endif // UNIX // create file name based on given pattern string compilePattern( const string &pattern, int season, int episode, const string &filename, const string &episodeName, const string &showName ) { string output; #ifdef _WIN32 auto season_num = std::to_wstring( season ); auto ep_num = std::to_wstring( episode ); #else auto season_num = std::to_string( season ); auto ep_num = std::to_string( episode ); #endif for ( size_t i = 0; i < pattern.size(); i++ ) { // if current character is % check if a pattern follows, otherwise // put % if ( pattern[i] == '%' ) { // check for numbers right after % indicating size of zero // padding for numbers auto pos = pattern.find_first_not_of( TEXT( "0123456789" ), i + 1 ); if ( pattern.find( TEXT( "season" ), pos - 1 ) == pos && pos != i + 1 ) { // if season is AFTER numbers, put season number padded // with zeros // get number of leading zeros auto leading = std::stoi( pattern.c_str() + i + 1 ); // move i to the last char of 'season' i = pos + 5; // get number of zeros to be put before the season number leading -= season_num.size(); if ( leading < 0 ) leading = 0; // add padded season to output output += string( leading, '0' ) + season_num; } else if ( pattern.find( TEXT( "season" ), i ) == i + 1 ) { // if season isn't after numbers, just put season number to // output i += 6; output += season_num; } else if ( pattern.find( TEXT( "episode" ), pos - 1 ) == pos && pos != i + 1 ) { // same principle as with season after number auto leading = std::stoi( pattern.c_str() + i + 1 ); i = pos + 6; leading -= ep_num.size(); if ( leading < 0 ) leading = 0; output += string( leading, '0' ) + ep_num; } else if ( pattern.find( TEXT( "episode" ), i ) == i + 1 ) { // if episode isn't after number, just put the episode number to // output i += 7; output += ep_num; } else if ( pattern.find( TEXT( "epname" ), i ) == i + 1 ) { // episode name from thetvdb i += 6; output += episodeName; } else if ( pattern.find( TEXT( "show" ), i ) == i + 1 ) { // show name from thetvdb i += 4; output += showName; } else if ( pattern.find( TEXT( "filename" ), i ) == i + 1 ) { // original file name i += 8; output += filename; } else { // output % if no escape sequence was found output += '%'; } } else if ( pattern[i] == '\\' ) { // possibility to escape % if ( pattern[i + 1] == '%' ) { output += '%'; i++; } else if ( pattern[i + 1] == '\\' ) { output += '\\'; i++; } else { output += '\\'; } } else { // if char isn't % or / just add it to the output string output += pattern[i]; } } return output; } #ifdef _WIN32 std::wstring getDBName() { return userHome() + L"\\tv_rename\\database.db"; } #else std::string getDBName() { return userHome() + "/.cache/tv_rename.db"; } #endif string sanitize( const string &str ) { string ret; size_t prev_pos{}; size_t pos = str.find_first_of( '\'' ); while ( pos != string::npos ) { ret += str.substr( prev_pos, pos - prev_pos ); prev_pos = pos + 1; pos = str.find_first_of( '\'', prev_pos ); ret += TEXT( "\'\'" ); } ret += str.substr( prev_pos, pos ); return ret; } void prepareDB( const string &_pattern ) { auto dbPath = getDBName(); auto dbDir = dbPath.substr( 0, dbPath.find_last_of( dir_divider, dbPath.length() ) ); if ( !FSLib::exists( dbDir ) ) FSLib::createDirectoryFull( dbDir ); SQLite::Database db{}; try { db.open( dbPath, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE ); } catch ( std::exception &e ) { cerr << _( DB_NOT_CREATE ) << " " << dbPath << std::endl; throw; } db.exec( "CREATE TABLE IF NOT EXISTS SHOWS (ID INTEGER NOT NULL " "PRIMARY KEY AUTOINCREMENT, TVID TEXT NOT NULL, SHOW TEXT NOT NULL," "PATH TEXT NOT NULL UNIQUE, LANGUAGE TEXT NOT NULL, DVD INTEGER NOT " "NULL);" ); db.exec( "CREATE TABLE IF NOT EXISTS EPISODES (SHOWID INTEGER NOT NULL," "PATH TEXT NOT NULL UNIQUE, FOREIGN KEY(SHOWID) " "REFERENCES SHOWS(ID));" ); const string *pattern; if ( _pattern.empty() ) { cout << _( DB_INSERT_PATTERN ) << std::endl; auto *p = new string; std::getline( cin, *p ); pattern = p; } else { pattern = &_pattern; } db.exec( TEXT( "INSERT INTO SHOWS ( TVID, SHOW, PATH, LANGUAGE, DVD ) " "VALUES ( 'pattern', 'pattern', '" ) + sanitize( *pattern ) + TEXT( "', 'pattern', 0 );" ) ); if ( pattern != &_pattern ) { delete pattern; } } #ifndef GUI void addToDB( string &show, const string &path, const string &language, bool unix_names, bool dvd ) { #else void addToDB( const string &show, const string &path, const string &language, const string &id, const string &pattern, bool unix_names, bool dvd, void *progress_ptr ) { #endif if ( !FSLib::exists( getDBName() ) ) prepareDB(); SQLite::Database db{}; auto absolute = FSLib::canonical( path ); try { db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } #ifndef GUI // gui gives id and correct show name auto id = getShowId( show, language ); show = showNameFromId( id, language ); #endif db.exec( TEXT( "INSERT OR IGNORE INTO SHOWS ( TVID, SHOW, PATH, LANGUAGE, DVD ) " "VALUES ( '" ) + sanitize( id ) + TEXT( "', '" ) + sanitize( show ) + TEXT( "', '" ) + sanitize( absolute ) + TEXT( "', '" ) + sanitize( language ) + TEXT( "', " ) + ( dvd ? TEXT( "1" ) : TEXT( "0" ) ) + TEXT( " );" ) ); #ifdef _WIN32 string db_id = std::to_wstring( db.lastRowID() ); #else string db_id = std::to_string( db.lastRowID() ); #endif #ifndef GUI string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE TVID == 'pattern';" ), pattern ); #endif std::map< int, std::map< int, string > > seasons; // get all seasons and episodes iterateFS( seasons, absolute ); #ifndef GUI ProgressBar p; #else ProgressBar p( progress_ptr ); #endif if ( seasons.size() == 0 ) return; p.print( _( RENAMING ) ); p.print( 0 ); size_t i = 0; size_t seasons_size = seasons.size(); for ( auto &x : seasons ) { singleSeason( absolute, show, x.first, id, language, pattern, unix_names, true, &x.second, false, dvd ); i++; p.print( ( i * 100 ) / seasons_size ); } #ifndef GUI cout << std::endl; #endif p.print( _( ADDING_TO_DB ) ); p.print( 0 ); i = 0; for ( auto &season : seasons ) { for ( auto &episode : season.second ) { db.exec( TEXT( "INSERT OR IGNORE INTO EPISODES ( SHOWID, PATH ) " "VALUES ( " ) + db_id + TEXT( ", '" ) + sanitize( episode.second ) + TEXT( "' );" ) ); } i++; p.print( ( i * 100 ) / seasons_size ); } #ifndef GUI cout << std::endl; #endif } #ifdef _WIN32 void cleanUpLine() { CONSOLE_SCREEN_BUFFER_INFO csbi; int width; GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi ); width = csbi.srWindow.Right - csbi.srWindow.Left + 1; static HANDLE h = GetStdHandle( STD_OUTPUT_HANDLE ); CONSOLE_SCREEN_BUFFER_INFO info; GetConsoleScreenBufferInfo( h, &info ); COORD c = { 0, info.dwCursorPosition.Y - 3 }; SetConsoleCursorPosition( h, c ); cout << string( width, ' ' ) << std::endl << std::endl; SetConsoleCursorPosition( h, c ); } #else void cleanUpLine() { struct winsize w; ioctl( 0, TIOCGWINSZ, &w ); auto width = w.ws_col; cout << "\x1b[2A"; cout << string( width, ' ' ) << std::endl << std::endl; cout << "\x1b[2A"; } #endif #ifndef GUI void refreshDB( bool unix_names ) { #else void refreshDB( bool unix_names, void *progress_ptr ) { #endif if ( !FSLib::exists( getDBName() ) ) return; std::vector< std::unordered_map< string, string > > shows; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } db.exec( "DELETE FROM EPISODES;" ); db.exec( TEXT( "SELECT ID, TVID, SHOW, PATH, LANGUAGE, DVD FROM SHOWS WHERE TVID " "!= 'pattern';" ), shows ); string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE TVID == 'pattern';" ), pattern ); #ifndef GUI ProgressBar p; p.print( _( REFRESHING_DB ) ); cout << std::endl << std::endl; #else // GUI ProgressBar p( progress_ptr ); #endif // GUI if ( shows.size() == 0 ) return; for ( auto &show : shows ) { if ( FSLib::exists( show[TEXT( "PATH" )] ) ) { #ifndef GUI cleanUpLine(); #endif p.print( _( REFRESHING ) + string( 1, ' ' ) + show[TEXT( "SHOW" )] ); p.print( 0 ); std::map< int, std::map< int, string > > seasons; iterateFS( seasons, show[TEXT( "PATH" )] ); auto seasons_size = seasons.size(); size_t i{}; for ( auto &x : seasons ) { singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], x.first, show[TEXT( "TVID" )], show[TEXT( "LANGUAGE" )], pattern, unix_names, true, &x.second, false, show[TEXT( "DVD" )] == TEXT( "1" ) ); i++; p.print( ( i * 100 ) / seasons_size ); } p.print( 100 ); #ifndef GUI cout << std::endl; cleanUpLine(); #endif p.print( _( UPDATING_IN_DB, show[TEXT( "SHOW" )].c_str() ) ); p.print( 0 ); i = 0; size_t addition = 100 / seasons_size; for ( auto &season : seasons ) { size_t j = 0; size_t smalladdition = addition / season.second.size(); for ( auto &episode : season.second ) { db.exec( TEXT( "INSERT INTO EPISODES ( SHOWID, PATH ) " "VALUES ( " ) + show[TEXT( "ID" )] + TEXT( ", '" ) + sanitize( episode.second ) + TEXT( "' );" ) ); j++; p.print( i * addition + j * smalladdition ); } i++; p.print( i * addition ); } p.print( 100 ); #ifndef GUI cout << std::endl; #endif } } } #ifndef GUI void updateDB( bool unix_names ) { #else void updateDB( bool unix_names, void *progress_ptr ) { #endif if ( !FSLib::exists( getDBName() ) ) return; std::vector< std::unordered_map< string, string > > shows; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } db.exec( TEXT( "SELECT ID, TVID, SHOW, PATH, LANGUAGE, DVD FROM SHOWS WHERE TVID" " != 'pattern';" ), shows ); string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE TVID == 'pattern';" ), pattern ); #ifndef GUI ProgressBar p; #else // GUI ProgressBar p( progress_ptr ); #endif if ( shows.size() == 0 ) return; p.print( _( UPDATING_DB ) ); #ifndef GUI cout << std::endl << std::endl; #endif for ( auto &show : shows ) { if ( !FSLib::exists( show[TEXT( "PATH" )] ) ) { continue; } #ifndef GUI cleanUpLine(); #endif p.print( _( UPDATING ) + string( 1, ' ' ) + show[TEXT( "SHOW" )] ); std::unordered_set< string > episodes; db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) + show[TEXT( "ID" )] + TEXT( ";" ), episodes ); std::map< int, std::map< int, string > > seasons; std::map< int, std::map< int, string > > new_eps; // get all season numbers from this directory and subdirectories iterateFS( seasons, show[TEXT( "PATH" )] ); for ( const auto &x : seasons ) { for ( const auto &episode : x.second ) { if ( episodes.find( episode.second ) == episodes.end() ) { new_eps[x.first].insert( episode ); } } } p.print( 0 ); if ( !new_eps.empty() ) { auto size = new_eps.size(); size_t i{}; for ( auto &x : new_eps ) { singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], x.first, show[TEXT( "TVID" )], show[TEXT( "LANGUAGE" )], pattern, unix_names, true, &x.second, false, show[TEXT( "DVD" )] == TEXT( "1" ) ); i++; p.print( ( i * 100 ) / size ); } for ( auto &season : new_eps ) { for ( auto &episode : season.second ) { db.exec( TEXT( "INSERT OR IGNORE INTO EPISODES ( SHOWID, " "PATH ) VALUES ( " ) + show[TEXT( "ID" )] + TEXT( ", '" ) + sanitize( episode.second ) + TEXT( "' );" ) ); } } } p.print( 100 ); #ifndef GUI cout << std::endl; #endif } } void changeDBPattern( const string &pattern ) { if ( !FSLib::exists( getDBName() ) ) return; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } db.exec( TEXT( "UPDATE SHOWS SET PATH = '" ) + pattern + TEXT( "' WHERE TVID == 'pattern';" ) ); } #ifndef GUI void cleanDB() { #else void cleanDB( void *progress_ptr ) { #endif if ( !FSLib::exists( getDBName() ) ) return; std::vector< std::unordered_map< string, string > > shows; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } db.exec( TEXT( "SELECT ID, PATH FROM SHOWS WHERE TVID" " != 'pattern';" ), shows ); #ifndef GUI ProgressBar p; #else // GUI ProgressBar p( progress_ptr ); #endif // GUI if ( shows.size() == 0 ) return; p.print( _( CLEANING_DB ) ); p.print( 0 ); int percent = 0; int increment = 100 / shows.size(); for ( auto &show : shows ) { p.print( percent ); percent += increment; bool dir_exists = FSLib::exists( show[TEXT( "PATH" )] ); std::unordered_set< string > episodes; db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) + show[TEXT( "ID" )] + TEXT( ";" ), episodes ); for ( const auto &episode : episodes ) { if ( !dir_exists || !FSLib::exists( episode ) ) { db.exec( TEXT( "DELETE FROM EPISODES WHERE PATH == '" ) + sanitize( episode ) + TEXT( "';" ) ); } } episodes.clear(); db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) + show[TEXT( "ID" )] + TEXT( ";" ), episodes ); if ( episodes.empty() ) { db.exec( TEXT( "DELETE FROM SHOWS WHERE ID == " ) + show[TEXT( "ID" )] + TEXT( ";" ) ); } } p.print( 100 ); } void removeFromDB( const string &path ) { if ( !FSLib::exists( getDBName() ) ) return; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } string show_id{}; db.exec( TEXT( "SELECT ID FROM SHOWS WHERE PATH == '" ) + sanitize( path ) + TEXT( "';" ), show_id ); db.exec( TEXT( "DELETE FROM EPISODES WHERE SHOWID == " ) + show_id + TEXT( ";" ) ); db.exec( TEXT( "DELETE FROM SHOWS WHERE ID == " ) + show_id + TEXT( ";" ) ); } #ifdef GUI std::vector< std::unordered_map< string, string > > dbGetShows() { if ( !FSLib::exists( getDBName() ) ) return {}; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } std::vector< std::unordered_map< string, string > > ret; db.exec( TEXT( "SELECT * FROM SHOWS;" ), ret ); return ret; } void changeDB( size_t index, const string &path, const string &language, const string &id, bool dvd ) { if ( !FSLib::exists( getDBName() ) ) return; SQLite::Database db{}; auto absolute = FSLib::canonical( path ); try { db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } #ifndef _WIN32 string show_id = std::to_string( index ); #else string show_id = std::to_wstring( index ); #endif auto real_show = showNameFromId( id, language ); db.exec( TEXT( "UPDATE SHOWS SET TVID = '" ) + sanitize( id ) + TEXT( "', SHOW = '" ) + sanitize( real_show ) + TEXT( "', PATH = '" ) + sanitize( absolute ) + TEXT( "', LANGUAGE = '" ) + sanitize( language ) + TEXT( "', DVD = " ) + ( dvd ? TEXT( "1" ) : TEXT( "0" ) ) + TEXT( " WHERE ID == " ) + show_id + TEXT( ";" ) ); } void refreshSelectDB( std::unordered_set< size_t > indexes, bool unix_names, void *progress_ptr ) { if ( !FSLib::exists( getDBName() ) ) return; std::vector< std::unordered_map< string, string > > shows; string index_list{ '(' }; for ( auto &index : indexes ) { #ifndef WIN32 index_list += std::to_string( index ); #else index_list += std::to_wstring( index ); #endif index_list += ','; } index_list.back() = ')'; SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } db.exec( TEXT( "DELETE FROM EPISODES WHERE SHOWID IN " ) + index_list + TEXT( ";" ) ); db.exec( TEXT( "SELECT ID, TVID, SHOW, PATH, LANGUAGE, DVD FROM SHOWS " "WHERE ID IN " ) + index_list + TEXT( ";" ), shows ); string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE TVID == 'pattern';" ), pattern ); ProgressBar p( progress_ptr ); for ( auto &show : shows ) { p.print( _( REFRESHING ) + string( 1, ' ' ) + show[TEXT( "SHOW" )] ); if ( FSLib::exists( show[TEXT( "PATH" )] ) ) { p.print( 0 ); std::map< int, std::map< int, string > > seasons; // get all season number from this directory and subdirectories iterateFS( seasons, show[TEXT( "PATH" )] ); auto size = seasons.size(); size_t i{}; for ( auto &x : seasons ) { singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], x.first, show[TEXT( "TVID" )], show[TEXT( "LANGUAGE" )], pattern, unix_names, true, &x.second, false, show[TEXT( "DVD" )] == TEXT( "1" ) ); i++; p.print( ( i * 100 ) / size ); } p.print( 100 ); p.print( _( UPDATING_IN_DB, show[TEXT( "SHOW" )].c_str() ) ); p.print( 0 ); i = 0; for ( auto &season : seasons ) { for ( auto &episode : season.second ) { db.exec( TEXT( "INSERT INTO EPISODES ( SHOWID, PATH ) " "VALUES ( " ) + show[TEXT( "ID" )] + TEXT( ", '" ) + sanitize( episode.second ) + TEXT( "' );" ) ); } i++; p.print( ( i * 100 ) / seasons.size() ); } } } } #endif string getDBPattern() { if ( !FSLib::exists( getDBName() ) ) return TEXT( "" ); SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READONLY ); } catch ( std::exception &e ) { cerr << _( DB_CANT_OPEN ) << std::endl; throw; } string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE TVID == 'pattern';" ), pattern ); return pattern; } /* change names of original files to generated new names * orig - original filenames * renamed - renamed filenames (sorted in the same order as `orig`) */ void renameFiles( const std::vector< std::tuple< int, string, string, string > > &renamed_files ) { for ( const auto &renamed : renamed_files ) { FSLib::rename( std::get< 1 >( renamed ) + dir_divider + std::get< 2 >( renamed ), std::get< 1 >( renamed ) + dir_divider + std::get< 3 >( renamed ) ); } }