From 55a8f32f49aa95701b9037a4a3a92c1121f3d852 Mon Sep 17 00:00:00 2001 From: zvon Date: Mon, 4 Feb 2019 17:39:48 +0100 Subject: [PATCH] Add Windows support --- Makefile | 17 +- filesystem.cpp | 149 ++++++++++--- filesystem.hpp | 174 ++++++++++----- functions.cpp | 535 +++++++++++++++++++++++++++++++--------------- functions.hpp | 78 ++++--- gui.cpp | 9 +- main.cpp | 320 ++++++++++++++++++--------- mainwindow.cpp | 280 +++++++++++++----------- mainwindow.hpp | 31 ++- network.cpp | 77 +++++-- network.hpp | 20 +- resource.h | 50 +++++ seasonwindow.cpp | 67 +++--- seasonwindow.hpp | 9 +- small.ico | Bin 0 -> 46227 bytes tv_rename.cpp | 278 ++++++++++++++++-------- tv_rename.hpp | 38 +++- tv_rename_gui.cpp | 360 +++++++++++++++++++++++++++++++ tv_rename_gui.ico | Bin 0 -> 46227 bytes tv_rename_gui.rc | Bin 0 -> 11636 bytes 20 files changed, 1815 insertions(+), 677 deletions(-) create mode 100644 resource.h create mode 100644 small.ico create mode 100644 tv_rename_gui.cpp create mode 100644 tv_rename_gui.ico create mode 100644 tv_rename_gui.rc diff --git a/Makefile b/Makefile index 79de7de..9e484e1 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ default: tv_rename .PHONY: clean clean: - rm -f *.o tv_rename + rm -f *.o tv_rename tv_rename_gui .PHONY: install install: tv_rename @@ -50,3 +50,18 @@ functions_gui.o: functions.cpp tv_rename_gui.o: tv_rename.cpp $(CXX) $(CFLAGS) -c tv_rename.cpp -o tv_rename_gui.o -DGUI + +.PHONY: windows +windows: tv_rename.exe + +tv_rename.exe: tv_rename.cpp filesystem.cpp functions.cpp network.cpp main.cpp + $(CXX) -MD -EHsc -Fe"tv_rename" tv_rename.cpp filesystem.cpp functions.cpp network.cpp main.cpp -D_WIN32 -DUNICODE -link wininet.lib shlwapi.lib + +.PHONY: windows_gui +windows_gui: tv_rename_gui.exe + +tv_rename_gui.exe: tv_rename_gui.res tv_rename_gui.cpp tv_rename.cpp filesystem.cpp functions.cpp network.cpp + $(CXX) -MD -EHsc -Fe"tv_rename_gui" tv_rename_gui.cpp tv_rename.cpp filesystem.cpp functions.cpp network.cpp -D_WIN32 -DUNICODE -DGUI -link wininet.lib shlwapi.lib ole32.lib shell32.lib gdi32.lib user32.lib tv_rename_gui.res + +tv_rename_gui.res: tv_rename_gui.rc + rc tv_rename_gui.rc diff --git a/filesystem.cpp b/filesystem.cpp index cd37e38..d91c724 100644 --- a/filesystem.cpp +++ b/filesystem.cpp @@ -1,44 +1,91 @@ -#include "filesystem.hpp" -#include +#include #include -#include +#include -#ifndef GUI +#ifdef _WIN32 +#include +#include +#endif -bool FSLib::exists(const std::string &path) { - struct stat path_stat; +#include "filesystem.hpp" + +#ifdef _WIN32 + +#define S_ISDIR( a ) a & _S_IFDIR + +using stat_t = struct _stat; + +#else + +using stat_t = struct stat; + +#endif // _WIN32 + +#ifndef GUI // these functions aren't needed in GUI + +bool FSLib::exists( const string &path ) { + stat_t path_stat; + +#ifdef _WIN32 + return _wstat( path.c_str(), &path_stat ) == 0; +#else return stat( path.c_str(), &path_stat ) == 0; +#endif } -std::string FSLib::canonical(const std::string &path) { - char *canonical_path = static_cast(malloc(PATH_MAX)); - if( realpath(path.c_str(), canonical_path) == nullptr ) { - free(canonical_path); - return ""; +string FSLib::canonical( const string &path ) { + +#ifdef _WIN32 + auto PATH_MAX = MAX_PATH; +#endif + + char_t *canonical_path = new char_t[PATH_MAX]; + +#ifdef _WIN32 + auto failed = !PathCanonicalizeW( canonical_path, path.c_str() ); +#else + auto failed = realpath( path.c_str(), canonical_path ) == nullptr; +#endif + + if ( failed ) { + delete[] canonical_path; + return string(); } - std::string canonical_string{canonical_path}; - free(canonical_path); + + string canonical_string{ canonical_path }; + delete[] canonical_path; return canonical_string; } +#endif // ndef GUI + +bool FSLib::isDirectory( const string &path ) { + stat_t path_stat; + +#ifdef _WIN32 + _wstat( path.c_str(), &path_stat ); +#else + stat( path.c_str(), &path_stat ); #endif -bool FSLib::isDirectory(const std::string &path) { - struct stat path_stat; - stat( path.c_str(), &path_stat ); - return S_ISDIR(path_stat.st_mode); + return S_ISDIR( path_stat.st_mode ); } -bool FSLib::rename(const std::string &file_a, const std::string &file_b) { +bool FSLib::rename( const string &file_a, const string &file_b ) { +#ifdef _WIN32 + return MoveFileExW( file_a.c_str(), file_b.c_str(), + MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING ); +#else return ::rename( file_a.c_str(), file_b.c_str() ) == 0; +#endif } FSLib::Directory::iterator FSLib::Directory::begin() { - return Iterator(dir_path); + return Iterator( *this ); } FSLib::Directory::const_iterator FSLib::Directory::begin() const { - return Iterator(dir_path); + return Iterator( *this ); } FSLib::Directory::const_iterator FSLib::Directory::cbegin() const { @@ -46,44 +93,82 @@ FSLib::Directory::const_iterator FSLib::Directory::cbegin() const { } FSLib::Directory::iterator FSLib::Directory::end() { - return Iterator(dir_path, nullptr); +#ifdef _WIN32 + return Iterator( true ); +#else + return Iterator( *this, nullptr ); +#endif } FSLib::Directory::const_iterator FSLib::Directory::end() const { - return Iterator(dir_path, nullptr); +#ifdef _WIN32 + return Iterator( true ); +#else + return Iterator( *this, nullptr ); +#endif } FSLib::Directory::const_iterator FSLib::Directory::cend() const { return end(); } -const char *FSLib::Directory::path() const { +const char_t *FSLib::Directory::path() const { return dir_path.c_str(); } -char const *FSLib::Directory::Iterator::operator*() const { +char_t const *FSLib::Directory::Iterator::operator*() const { +#ifdef _WIN32 + return data.cFileName; +#else return current_entry->d_name; +#endif } +#ifdef _WIN32 + FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() { - if( current_entry == nullptr ) + if ( ended == true ) return *this; - current_entry = readdir(d); - if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) ) + // skip . and .. + if ( FindNextFileW( hFind, &data ) == 0 ) { + ended = true; + } else if ( !wcscmp( data.cFileName, L"." ) || + !wcscmp( data.cFileName, L".." ) ) { + return operator++(); + } + return *this; +} + +#else + +FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() { + if ( current_entry == nullptr ) + return *this; + current_entry = readdir( d ); + // skip . and .. + if ( current_entry != nullptr && + ( !strcmp( current_entry->d_name, "." ) || + !strcmp( current_entry->d_name, ".." ) ) ) return operator++(); return *this; } -FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++(int) { - Iterator ret(*this); +#endif + +FSLib::Directory::Iterator FSLib::Directory::Iterator::operator++( int ) { + Iterator ret( *this ); operator++(); return ret; } -bool FSLib::Directory::Iterator::operator==(const Iterator &i_other) const { +bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const { +#ifdef _WIN32 + return i_other.ended == ended; +#else return i_other.current_entry == current_entry; +#endif } -bool FSLib::Directory::Iterator::operator!=(const Iterator &i_other) const { - return i_other.current_entry != current_entry; +bool FSLib::Directory::Iterator::operator!=( const Iterator &i_other ) const { + return !( i_other == *this ); } diff --git a/filesystem.hpp b/filesystem.hpp index 4cac677..8db819b 100644 --- a/filesystem.hpp +++ b/filesystem.hpp @@ -1,80 +1,144 @@ #ifndef FSLIB_H #define FSLIB_H -#include -#include #include +#include + +#ifdef _WIN32 + +#include + +#else + +#include -namespace FSLib{ -#ifndef GUI - bool exists(const std::string &path); - std::string canonical(const std::string &path); #endif - bool isDirectory(const std::string &path); - bool rename(const std::string &file_a, const std::string &file_b); - class Directory { +// set apropriate data types for each operating system +#ifdef _WIN32 + +using string = std::wstring; +using char_t = wchar_t; + +#else + +using string = std::string; +using char_t = char; + +#endif + +// windows version stolen from +// http://www.martinbroadhurst.com/list-the-files-in-a-directory-in-c.html + +namespace FSLib { + +#ifndef GUI +bool exists( const string &path ); +string canonical( const string &path ); +#endif + +bool isDirectory( const string &path ); +bool rename( const string &file_a, const string &file_b ); + +class Directory { +public: + Directory( const string &path_ ) : dir_path( path_ ) { +#ifdef _WIN32 + // need to append \\* for windows to search files in directory + dir_path.append( L"\\*" ); +#endif + } + Directory() = delete; + Directory( const Directory &d ) = default; + Directory( Directory &&d ) = default; + + class Iterator { public: - Directory(const std::string &path_) : dir_path(path_) {} - Directory() = delete; - Directory(const Directory &d) = default; - Directory(Directory &&d) = default; - - class Iterator { - public: - Iterator( const Directory &d_ ) : d(opendir(d_.path())) { - current_entry = readdir(d); - if( current_entry != nullptr && ( !strcmp(current_entry->d_name, ".") || !strcmp(current_entry->d_name, "..") ) ) - ++(*this); +#ifdef _WIN32 + Iterator( const Directory &d_ ) { + hFind = FindFirstFileW( d_.path(), &data ); + if ( hFind != INVALID_HANDLE_VALUE ) { + if ( !wcscmp( data.cFileName, L"." ) || + !wcscmp( data.cFileName, L".." ) ) { + ++( *this ); + } + } else { + ended = true; } + } + // this is definitely not a good way to create the "end" iterator + // but it was the only way I thought of with my limited knowledge of + // windows.h + Iterator( bool ended_ ) : ended( ended_ ) {} - Iterator( const Directory &d_, const struct dirent *current_entry_) : d(opendir(d_.path())), current_entry(current_entry_) {} + ~Iterator() { + if ( hFind ) + FindClose( hFind ); + } +#else + Iterator( const Directory &d_ ) : d( opendir( d_.path() ) ) { + current_entry = readdir( d ); + if ( current_entry != nullptr && + ( !strcmp( current_entry->d_name, "." ) || + !strcmp( current_entry->d_name, ".." ) ) ) + ++( *this ); + } - Iterator() = delete; + Iterator( const Directory &d_, const struct dirent *current_entry_ ) + : d( opendir( d_.path() ) ), current_entry( current_entry_ ) {} - Iterator(const Iterator &i) = default; + ~Iterator() { + closedir( d ); + } +#endif - Iterator(Iterator &&i) = default; + Iterator() = delete; - ~Iterator() { - closedir(d); - } + Iterator( const Iterator &i ) = default; - char const *operator*() const; + Iterator( Iterator &&i ) = default; - Iterator &operator++(); + char_t const *operator*() const; - Iterator operator++(int); + Iterator &operator++(); - bool operator==(const Iterator &i_other) const; + Iterator operator++( int ); - bool operator!=(const Iterator &i_other) const; + bool operator==( const Iterator &i_other ) const; - private: - DIR *d; - const struct dirent *current_entry; - }; - - using iterator = Iterator; - using const_iterator = Iterator; - - iterator begin(); - - const_iterator begin() const; - - const_iterator cbegin() const; - - iterator end(); - - const_iterator end() const; - - const_iterator cend() const; - - const char *path() const; + bool operator!=( const Iterator &i_other ) const; private: - std::string dir_path; +#ifndef _WIN32 + DIR *d; + const struct dirent *current_entry; +#else + HANDLE hFind; + WIN32_FIND_DATA data; + bool ended{ false }; +#endif }; -} //end FSLib + + using iterator = Iterator; + using const_iterator = Iterator; + + iterator begin(); + + const_iterator begin() const; + + const_iterator cbegin() const; + + iterator end(); + + const_iterator end() const; + + const_iterator cend() const; + + const char_t *path() const; + +private: + string dir_path; +}; +} // namespace FSLib #endif diff --git a/functions.cpp b/functions.cpp index 8797349..a2eea1b 100644 --- a/functions.cpp +++ b/functions.cpp @@ -1,258 +1,417 @@ -#include "functions.hpp" -#include "filesystem.hpp" #include +#include +#include #include #include +#include + +#ifdef _WIN32 + +#include +#include +#include + +#else #ifndef GUI -#include #include +#include #include #include #else -#include #include +#include -#endif +#endif // GUI + +#endif // _WIN32 + +#include "filesystem.hpp" +#include "functions.hpp" + +#ifdef _WIN32 + +#define cout std::wcout +#define cerr std::wcerr +#define cin std::wcin + +constexpr const char_t *dir_divider = L"\\"; + +#else + +#define TEXT( a ) a + +#define cout std::cout +#define cerr std::cerr +#define cin std::cin + +constexpr const char_t *dir_divider = "/"; + +#endif // _WIN32 #ifndef GUI -constexpr std::array languages{ - "en", "English", "sv", "Svenska", "no", "Norsk", "da", "Dansk", "fi", "Suomeksi", - "nl", "Nederlands", "de", "Deutsch", "it", "Italiano", "es", "Español", "fr", "Français", - "pl", "Polski", "hu", "Magyar", "el", "Greek", "tr", "Turkish", "ru", "Russian", - "he", "Hebrew", "ja", "Japanese", "pt", "Portuguese", "zh", "Chinese", "cs", "Czech", - "sl", "Slovenian", "hr", "Croatian", "ko","Korea" +constexpr std::array< const char_t *, 46 > languages{ + TEXT( "en" ), TEXT( "English" ), TEXT( "sv" ), TEXT( "Svenska" ), + TEXT( "no" ), TEXT( "Norsk" ), TEXT( "da" ), TEXT( "Dansk" ), + TEXT( "fi" ), TEXT( "Suomeksi" ), TEXT( "nl" ), TEXT( "Nederlands" ), + TEXT( "de" ), TEXT( "Deutsch" ), TEXT( "it" ), TEXT( "Italiano" ), + TEXT( "es" ), TEXT( "Español" ), TEXT( "fr" ), TEXT( "Français" ), + TEXT( "pl" ), TEXT( "Polski" ), TEXT( "hu" ), TEXT( "Magyar" ), + TEXT( "el" ), TEXT( "Greek" ), TEXT( "tr" ), TEXT( "Turkish" ), + TEXT( "ru" ), TEXT( "Russian" ), TEXT( "he" ), TEXT( "Hebrew" ), + TEXT( "ja" ), TEXT( "Japanese" ), TEXT( "pt" ), TEXT( "Portuguese" ), + TEXT( "zh" ), TEXT( "Chinese" ), TEXT( "cs" ), TEXT( "Czech" ), + TEXT( "sl" ), TEXT( "Slovenian" ), TEXT( "hr" ), TEXT( "Croatian" ), + TEXT( "ko" ), TEXT( "Korea" ) }; -#endif +#endif // not GUI + +#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 ); +} + +#endif // _WIN32 // encode url so it's valid even with UTF-8 characters -std::string encodeUrl( const std::string &url ) { - //stolen from here - https://stackoverflow.com/questions/154536/encode-decode-urls-in-c - std::ostringstream encoded; - encoded.fill('0'); +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( auto &x : url ) { - if( std::isalnum(x) || x == '-' || x == '_' || x == '.' || x == '~' ) { + for ( const auto &x : url_c ) { + if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' || + x == '_' || x == '.' || x == '~' ) { encoded << x; continue; } - encoded << std::uppercase << '%' << std::setw(2); - encoded << int(static_cast(x)) << std::nouppercase; + encoded << std::uppercase << '%' << std::setw( 2 ); + encoded << int( static_cast< unsigned char >( x ) ) << std::nouppercase; } return encoded.str(); } // return true if filename has specified season // set ep_pos to position where episode number starts -bool searchSpecificSeason(const char *const p, size_t &ep_pos, const std::string &number) { +bool searchSpecificSeason( const char_t *const path, size_t &ep_pos, + const string &number ) { size_t cur_pos{}; - bool found_season{false}; - while( p[cur_pos] != '\0' ) { - if( (p[cur_pos] == 's' || p[cur_pos] == 'S') && isdigit(p[cur_pos+1]) ) { + // search for S[0-9]+E[0-9]+ + while ( path[cur_pos] != '\0' ) { + if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) && + iswdigit( path[cur_pos + 1] ) ) { cur_pos++; - while( p[cur_pos] == '0' ) + while ( path[cur_pos] == '0' ) cur_pos++; - size_t offset{}; - while( offset < number.size() && p[cur_pos + offset] == number[offset] ) - offset++; - cur_pos += offset; - if( offset != number.size() ) + // make sure season's number is the same as provided in argument + // `number` +#ifdef _WIN32 + if ( wcsncmp( path + cur_pos, number.c_str(), number.size() ) ) +#else + if ( strncmp( path + cur_pos, number.c_str(), number.size() ) ) +#endif continue; - if( (p[cur_pos] == 'e' || p[cur_pos] == 'E') && isdigit(p[cur_pos+1]) ) { - found_season = true; + cur_pos += number.size(); + if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) && + iswdigit( path[cur_pos + 1] ) ) { ep_pos = cur_pos + 1; - break; + return true; } } cur_pos++; } - return found_season; + return false; } -bool searchSpecificSeason(const char *const p, const std::string &number) { +bool searchSpecificSeason( const char_t *const p, const string &number ) { size_t tmp; - return searchSpecificSeason(p, tmp, number); + return searchSpecificSeason( p, tmp, number ); } -bool searchSeason(const char *const p, size_t &season_pos) { +// return true if file contains S[0-9]+E[0-9]+ nad set +// season_pos to start of season number +bool searchSeason( const char_t *const path, size_t &season_pos ) { size_t cur_pos{}; - bool found_season{false}; - while( p[cur_pos] != '\0' ) { - if( (p[cur_pos] == 's' || p[cur_pos] == 'S') && isdigit(p[cur_pos+1]) ) { + while ( path[cur_pos] != '\0' ) { + if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) && + iswdigit( path[cur_pos + 1] ) ) { cur_pos++; - season_pos = cur_pos; // after ++ because we want the first pos to point to season's number - while( isdigit(p[cur_pos]) ) + 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( (p[cur_pos] == 'e' || p[cur_pos] == 'E') && isdigit(p[cur_pos+1]) ) { - found_season = true; - break; + if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) && + iswdigit( path[cur_pos + 1] ) ) { + return true; } } cur_pos++; } - return found_season; + return false; } -bool searchSeason(const char *const p) { +bool searchSeason( const char_t *const path ) { size_t tmp{}; - return searchSeason(p, tmp); + return searchSeason( path, tmp ); } -void iterateFS(std::map> &seasons, const std::string &path) { - size_t season_pos{std::string::npos}; // season_pos - position of first digit of the season - for( const auto p: FSLib::Directory(path) ) { - if(FSLib::isDirectory(path + "/" + p)) { - iterateFS(seasons, path + "/" + p); +void iterateFS( std::map< int, std::set< string > > &seasons, + const string &path ) { + // season_pos - position of first digit of the season + size_t season_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( searchSeason(p, season_pos) ) - seasons[atoi(p+season_pos)].insert(path + "/" + p); + // if file is a correct format, add it to file list + // for its season + if ( searchSeason( p, season_pos ) ) + seasons[std::stoi( p + season_pos )].insert( + path + dir_divider + p ); } } #ifndef GUI +// following functions are only needed for CLI version -void findSeason(std::set &files, int season, const std::string &path) { - auto number = std::to_string(season); +// find all files for provided season in `path` and store it in `files` +void findSeason( std::set< string > &files, int season, const string &path ) { +#ifdef _WIN32 + auto number = std::to_wstring( season ); +#else + auto number = std::to_string( season ); +#endif - for( const auto p: FSLib::Directory(path) ) { - if(FSLib::isDirectory(path + "/" + p)) { - findSeason(files, season, path + "/" + p); + for ( const auto p : FSLib::Directory( path ) ) { + // if p is directory, iterate through it + if ( FSLib::isDirectory( path + dir_divider + p ) ) { + findSeason( files, season, path + dir_divider + p ); continue; } - if( searchSpecificSeason(p, number) ) - files.insert(path + "/" + p); + if ( searchSpecificSeason( p, number ) ) + files.insert( path + dir_divider + p ); } } -void findSeasons(std::map> &seasons, const std::string &path, const std::set &season_numbers) { - size_t season_pos{std::string::npos}; // season_pos - position of first digit of the season - for( const auto p: FSLib::Directory(path) ) { - if(FSLib::isDirectory(path + "/" + p)) { - findSeasons(seasons, path + "/" + p, season_numbers); +// find all files that comply with the S[0-9]+E[0-9]+ pattern +// and their season is in season_numbers and store tem in `seasons` +void findSeasons( std::map< int, std::set< string > > &seasons, + const string &path, const std::set< int > &season_numbers ) { + // season_pos - position of first digit of the season + size_t season_pos{ string::npos }; + for ( const auto p : FSLib::Directory( path ) ) { + // if p is directory, iterate through it + if ( FSLib::isDirectory( path + dir_divider + p ) ) { + findSeasons( seasons, path + dir_divider + p, season_numbers ); continue; } - if( searchSeason(p, season_pos) ) { - auto num = atoi(p+season_pos); - if( season_numbers.find(num) != season_numbers.end() ) - seasons[num].insert(path + "/" + p); + if ( searchSeason( p, season_pos ) ) { + auto num = std::stoi( p + season_pos ); + if ( season_numbers.find( num ) != season_numbers.end() ) + seasons[num].insert( path + dir_divider + p ); } } } -std::string getDefUrl( std::string &show, const std::string &language, Curl &c ) { - std::replace(show.begin(), show.end(), ' ', '+'); - auto source_code = c.execute("https://www.thetvdb.com/search?q=" + encodeUrl(show) + "&l=" + language); +string getDefUrl( string &show, const string &language, Curl &c ) { + std::replace( show.begin(), show.end(), ' ', '+' ); + + string base_url = TEXT( "https://www.thetvdb.com" ); + +#ifdef _WIN32 + string source_code = utf8_to_wstring( + c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) + + encodeUrl( show ) + TEXT( "&l=" ) + language ) ); +#else + string source_code = + c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) + + encodeUrl( show ) + TEXT( "&l=" ) + language ); +#endif + size_t order{}, pos{}; - std::vector> urls; - while( true ) { - pos = source_code.find("/ser", pos); - if( pos != std::string::npos ) { - auto end = source_code.find(">", pos); + std::vector< std::pair< string, string > > urls; + + // find all possible shows + while ( true ) { + pos = source_code.find( TEXT( "/ser" ), pos ); + if ( pos != string::npos ) { + auto end = source_code.find( TEXT( ">" ), pos ); end--; - auto end2 = source_code.find("<", end + 2); - urls.emplace_back(source_code.substr(end + 2, end2 - end - 2), source_code.substr(pos, end - pos)); - std::cout << ++order << ". " << urls.back().first << std::endl; + auto end2 = source_code.find( TEXT( "<" ), end + 2 ); + // store shows in urls, first is name, second is url + urls.emplace_back( source_code.substr( end + 2, end2 - end - 2 ), + source_code.substr( pos, end - pos ) ); + cout << ++order << ". " << urls.back().first << std::endl; pos = end2; } else { break; } } - std::cout << "Which TV Show is the right one? "; - std::cin >> pos; - std::cin.clear(); - std::cin.ignore(1, '\n'); - show = urls[pos-1].first; - return "https://www.thetvdb.com" + urls[pos-1].second; + cout << "Which TV Show is the right one? "; + cin >> pos; + cin.clear(); + cin.ignore( 1, '\n' ); + show = urls[pos - 1].first; + return base_url + urls[pos - 1].second; } void printHelp() { - std::cout << "usage: tv_rename [--help] [--show show name] [--season season number]" << std::endl; - std::cout << " [--correct-path] [--show-path show path] [--trust]" << std::endl; - std::cout << " [--linux] [--lang language] [--print-langs]" << std::endl; - std::cout << std::endl << "Rename TV episodes" << std::endl << std::endl << "optional arguments:" << std::endl; - std::cout << " -h, --help\t\tshow this help message and exit" << std::endl; - std::cout << " --show show name, -s show name" << std::endl; - std::cout << "\t\t\tTV show from which you want episode names (needs to be" << std::endl; - std::cout << "\t\t\tin quotation marks if it has more than one word)" << std::endl; - std::cout << " --season season number, -n season number" << std::endl; - std::cout << "\t\t\tSeason number/s (if multiple seasons, put them in" << std::endl; - std::cout << "\t\t\tquotation marks and seperate by one space)" << std::endl; - std::cout << "\t\t\tor 'all' for all seasons in selected subdirectory" << std::endl; - std::cout << " --show-path show path, -p show path" << std::endl; - std::cout << "\t\t\tPath of the directory with episodes" << std::endl; - std::cout << " --correct-path, -c\tThis is the correct path, stop asking me!" << std::endl; - std::cout << " --name-pattern pattern" << std::endl; - std::cout << "\t\t\tPattern to which change the file name. Possible sequences are:" << std::endl; - std::cout << "\t\t\t\t\%filename - original filename (without filetype extension)" << std::endl; - std::cout << "\t\t\t\t\%show - show name from thetvdb" << std::endl; - std::cout << "\t\t\t\t\%epname - episode name from thetvdb" << std::endl; - std::cout << "\t\t\t\t\%season - season number" << std::endl; - std::cout << "\t\t\t\t\tpossible to specify leading 0 like this: \%2season (number means how many leading zeros)" << std::endl; - std::cout << "\t\t\t\t\%episode - episode number" << std::endl; - std::cout << "\t\t\t\t\tpossible to specify leading 0 like this: \%2episode (number means how many leading zeros)" << std::endl; - std::cout << "\t\t\tDefault pattern is \"$filename - $epname\"" << std::endl; - std::cout << " --trust, -t\t\tDon't ask whether the names are correct" << std::endl; - std::cout << " --linux, -x\t\tDon't replace characters characters that are illegal in Windows" << std::endl; - std::cout << " --lang language, -l language" << std::endl; - std::cout << "\t\t\tSelect which language the episode names shoud be in" << std::endl; - std::cout << " --print-langs\t\tPring available language" << std::endl; + cout << "usage: tv_rename [--help] [--show show name] [--season season " + "number]" + << std::endl; + cout + << " [--correct-path] [--show-path show path] [--trust]" + << std::endl; + cout << " [--linux] [--lang language] [--print-langs]" + << std::endl; + cout << std::endl + << "Rename TV episodes" << std::endl + << std::endl + << "optional arguments:" << std::endl; + cout << " -h, --help\t\tshow this help message and exit" << std::endl; + cout << " --show show name, -s show name" << std::endl; + cout << "\t\t\tTV show from which you want episode names (needs to be" + << std::endl; + cout << "\t\t\tin quotation marks if it has more than one word)" + << std::endl; + cout << " --season season number, -n season number" << std::endl; + cout << "\t\t\tSeason number/s (if multiple seasons, put them in" + << std::endl; + cout << "\t\t\tquotation marks and seperate by one space)" << std::endl; + cout << "\t\t\tor 'all' for all seasons in selected subdirectory" + << std::endl; + cout << " --show-path show path, -p show path" << std::endl; + cout << "\t\t\tPath of the directory with episodes" << std::endl; + cout << " --correct-path, -c\tThis is the correct path, stop asking me!" + << std::endl; + cout << " --name-pattern pattern" << std::endl; + cout << "\t\t\tPattern to which change the file name. Possible sequences " + "are:" + << std::endl; + cout << "\t\t\t\t%filename - original filename (without filetype extension)" + << std::endl; + cout << "\t\t\t\t%show - show name from thetvdb" << std::endl; + cout << "\t\t\t\t%epname - episode name from thetvdb" << std::endl; + cout << "\t\t\t\t%season - season number" << std::endl; + cout << "\t\t\t\ttpossible to specify leading 0 like this: %2season " + "(number means how many leading zeros)" + << std::endl; + cout << "\t\t\t\t%episode - episode number" << std::endl; + cout << "\t\t\t\t\tpossible to specify leading 0 like this: %2episode " + "(number means how many leading zeros)" + << std::endl; + cout << "\t\t\tDefault pattern is \"$filename - $epname\"" << std::endl; + cout << " --trust, -t\t\tDon't ask whether the names are correct" + << std::endl; + cout << " --linux, -x\t\tDon't replace characters characters that are " + "illegal in Windows" + << std::endl; + cout << " --lang language, -l language" << std::endl; + cout << "\t\t\tSelect which language the episode names shoud be in" + << std::endl; + cout << " --print-langs\t\tPring available language" << std::endl; } -void parseSeasonNumbers(std::set &seasons_num, const char *argument) { - size_t pos{0}; +// 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(!isdigit(argument[pos]) && argument[pos] != '\0') + while ( !iswdigit( argument[pos] ) && argument[pos] != '\0' ) pos++; - if( argument[pos] == '\0' ) { + if ( argument[pos] == '\0' ) { seasons_num.clear(); return; } int temp; - std::istringstream iss(argument + pos); - while(iss >> temp) { - seasons_num.insert(temp); + +#ifdef _WIN32 + std::wstringstream iss( argument + pos ); +#else + std::stringstream iss( argument + pos ); +#endif + + while ( iss >> temp ) { + seasons_num.insert( temp ); } } +// print possible language codes and their corresponding language void printLangs() { - for( size_t i = 0; i < languages.size(); i += 2 ) { - std::cout << languages[i] << " - " << languages[i+1] << std::endl; + for ( size_t i = 0; i < languages.size(); i += 2 ) { + cout << languages[i] << " - " << languages[i + 1] << std::endl; } } -bool findLanguage( const char *language ) { - for( size_t i = 0; i < languages.size(); i += 2 ) { - if( !strcmp(language, languages[i]) ) +// make sure language is a valide language code +bool findLanguage( const char_t *language ) { + for ( size_t i = 0; i < languages.size(); i += 2 ) { +#ifdef _WIN32 + if ( !wcscmp( language, languages[i] ) ) +#else + if ( !strcmp( language, languages[i] ) ) +#endif return true; } return false; } #else +// functions that are needed for GUI but not for CLI -std::vector> getPossibleShows( std::string show, const std::string &language, Curl &c ) { - std::replace(show.begin(), show.end(), ' ', '+'); - auto source_code = c.execute("https://www.thetvdb.com/search?q=" + encodeUrl(show) + "&l=" + language); +// get possible shows for search query in `show` +std::vector< std::pair< string, string > > +getPossibleShows( string show, const string &language, Curl &c ) { + std::replace( show.begin(), show.end(), ' ', '+' ); +#ifdef _WIN32 + auto source_code = utf8_to_wstring( + c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) + + encodeUrl( show ) + TEXT( "&l=" ) + language ) ); +#else + auto source_code = + c.execute( TEXT( "https://www.thetvdb.com/search?q=" ) + + encodeUrl( show ) + TEXT( "&l=" ) + language ); +#endif size_t pos{}; - std::vector> urls; - while( true ) { - pos = source_code.find("/ser", pos); - if( pos != std::string::npos ) { - auto end = source_code.find(">", pos); - auto end2 = source_code.find("<", end+1); + std::vector< std::pair< string, string > > urls; + while ( true ) { + pos = source_code.find( TEXT( "/ser" ), pos ); + if ( pos != string::npos ) { + auto end = source_code.find( TEXT( ">" ), pos ); + auto end2 = source_code.find( TEXT( "<" ), end + 1 ); end--; - urls.emplace_back(source_code.substr(end+2, end2 - (end+2)), source_code.substr(pos, end - pos)); + urls.emplace_back( + source_code.substr( end + 2, end2 - ( end + 2 ) ), + source_code.substr( pos, end - pos ) ); pos = end + 2; } else { break; @@ -261,13 +420,17 @@ std::vector> getPossibleShows( std::string s return urls; } -std::string userHome() { - uid_t user_uid; +#ifndef _WIN32 + +// 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 ) @@ -276,63 +439,94 @@ std::string userHome() { return user_passwd->pw_dir; } +#else + +// 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; + } + return L""; +} + #endif -std::string compilePattern(const std::string &pattern, int season, int episode, const std::string &filename, const std::string &episodeName, const std::string &showName) { - std::string output; +#endif // ndef GUI - for( size_t i = 0; i < pattern.size(); i++ ) { - if( pattern[i] == '%' ) { - // if current character is % check if a pattern follows, otherwise put % - // check for numbers right after % indicating size of zero padding for numbers - auto pos = pattern.find_first_not_of("0123456789", i+1); +// 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; - if( pattern.find( "season", pos - 1 ) == pos ) { +#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::atoi( pattern.c_str() + i + 1 ); + auto leading = std::stoi( pattern.c_str() + i + 1 ); // move i to the last char of 'season' i = pos + 5; - auto season_num = std::to_string(season); // get number of zeros to be put before the season number leading -= season_num.size(); - if( leading < 0 ) + if ( leading < 0 ) leading = 0; // add padded season to output - output += std::string(leading, '0') + season_num; - } else if ( pattern.find( "season", i ) == i + 1 ) { - // if season isn't after numbers, just put season number 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 += std::to_string(season); - } else if ( pattern.find( "episode", pos - 1 ) == pos ) { + 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::atoi( pattern.c_str() + i + 1 ); + auto leading = std::stoi( pattern.c_str() + i + 1 ); i = pos + 6; - auto ep_num = std::to_string(episode); leading -= ep_num.size(); - if( leading < 0 ) + if ( leading < 0 ) leading = 0; - output += std::string(leading, '0') + ep_num; - } else if ( pattern.find( "episode", i ) == i + 1 ) { - // if episode isn't after number, just put the episode number to output + 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 += std::to_string(episode); - } else if ( pattern.find( "epname", i ) == i + 1 ) { + output += ep_num; + } else if ( pattern.find( TEXT( "epname" ), i ) == i + 1 ) { // episode name from thetvdb i += 6; output += episodeName; - } else if ( pattern.find( "show", i ) == i + 1 ) { + } else if ( pattern.find( TEXT( "show" ), i ) == i + 1 ) { // show name from thetvdb i += 4; output += showName; - } else if ( pattern.find( "filename", i ) == i + 1 ) { + } else if ( pattern.find( TEXT( "filename" ), i ) == i + 1 ) { // original file name i += 8; output += filename; @@ -342,10 +536,10 @@ std::string compilePattern(const std::string &pattern, int season, int episode, } } else if ( pattern[i] == '\\' ) { // possibility to escape % - if( pattern[i+1] == '%' ) { + if ( pattern[i + 1] == '%' ) { output += '%'; i++; - } else if ( pattern[i+1] == '\\' ) { + } else if ( pattern[i + 1] == '\\' ) { output += '\\'; i++; } else { @@ -359,4 +553,3 @@ std::string compilePattern(const std::string &pattern, int season, int episode, return output; } - diff --git a/functions.hpp b/functions.hpp index a635c3b..d059aed 100644 --- a/functions.hpp +++ b/functions.hpp @@ -1,10 +1,10 @@ -#ifndef GETPAGE_H -#define GETPAGE_H +#ifndef TV_FUNCTIONS_H +#define TV_FUNCTIONS_H -#include -#include +#include #include -#include "network.hpp" +#include +#include #ifdef GUI @@ -12,34 +12,62 @@ #endif -#ifndef GUI +#include "network.hpp" -std::string getDefUrl( std::string &show, const std::string &language, Curl &c ); -void findSeason(std::set &files, int season, const std::string &path); -void findSeasons(std::map> &seasons, const std::string &path, const std::set &season_numbers); -void printHelp(); +#ifdef _WIN32 -bool searchSpecificSeason(const char *const p, const std::string &number); -bool searchSeason(const char *const p, size_t &season_pos); -bool searchSeason(const char *const p); - -void parseSeasonNumbers(std::set &seasons_num, const char *argument); -void printLangs(); -bool findLanguage( const char *language ); - -std::string encodeUrl( const std::string &url ); +using string = std::wstring; +using char_t = wchar_t; #else -std::vector> getPossibleShows( std::string show, const std::string &language, Curl &c ); -std::string userHome(); +using string = std::string; +using char_t = char; #endif -void iterateFS(std::map> &seasons, const std::string &path); +#ifdef _WIN32 -bool searchSpecificSeason(const char *const p, size_t &ep_pos, const std::string &number); - -std::string compilePattern(const std::string &pattern, int season, int episode, const std::string &filename, const std::string &episodeName, const std::string &showName); +std::wstring utf8_to_wstring( const std::string &utf8 ); + +#endif + +#ifndef GUI +// CLI functions + +string getDefUrl( string &show, const string &language, Curl &c ); +void findSeason( std::set< string > &files, int season, const string &path ); +void findSeasons( std::map< int, std::set< string > > &seasons, + const string &path, const std::set< int > &season_numbers ); +void printHelp(); + +bool searchSpecificSeason( const char_t *const path, const string &number ); +bool searchSeason( const char_t *const path, size_t &season_pos ); +bool searchSeason( const char_t *const path ); + +void parseSeasonNumbers( std::set< int > &seasons_num, const char_t *argument ); +void printLangs(); +bool findLanguage( const char_t *language ); + +string encodeUrl( const string &url ); + +#else +// GUI functions + +std::vector< std::pair< string, string > > +getPossibleShows( string show, const string &language, Curl &c ); +string userHome(); + +#endif + +void iterateFS( std::map< int, std::set< string > > &seasons, + const string &path ); + +bool searchSpecificSeason( const char_t *const path, size_t &ep_pos, + const string &number ); + +string compilePattern( const string &pattern, int season, int episode, + const string &filename, const string &episodeName, + const string &showName ); #endif diff --git a/gui.cpp b/gui.cpp index bc8603c..2a78acc 100644 --- a/gui.cpp +++ b/gui.cpp @@ -2,10 +2,11 @@ #include "mainwindow.hpp" -int main(int argc, char **argv) { - auto app = Gtk::Application::create(argc, argv, "org.idonthaveanorganization.tvrename"); +int main( int argc, char **argv ) { + auto app = Gtk::Application::create( + argc, argv, "org.idonthaveanorganization.tvrename" ); - MainWindow mw(app); + MainWindow mw( app ); - return app->run(mw); + return app->run( mw ); } diff --git a/main.cpp b/main.cpp index cbf4794..f3fcdac 100644 --- a/main.cpp +++ b/main.cpp @@ -1,137 +1,261 @@ -#include #include +#include + +#ifdef _WIN32 + +#include +#include +#include + +#else + +#include + +#endif + #include "filesystem.hpp" #include "functions.hpp" #include "tv_rename.hpp" -int parseCommandLine(std::string &show, std::set &seasons_num, std::string &path, bool &change_dir, std::string &language, std::string &pattern, bool &linux, bool &trust, int argc, char **argv) { +#ifdef _WIN32 + +using char_t = wchar_t; +using string = std::wstring; + +#define cerr std::wcerr +#define cout std::wcout +#define cin std::wcin + +#else + +using char_t = char; +using string = std::string; + +#define cerr std::cerr +#define cout std::cout +#define cin std::cin + +#define TEXT( a ) a + +#endif + +int handleArgument( char_t c, string &show, std::set< int > &seasons_num, + bool &change_dir, string &path, bool &trust, bool &linux, + string &language, string &pattern, char_t *optional, + int &i ) { + switch ( c ) { + case 's': + show = optional; + i++; + break; + case 'n': + parseSeasonNumbers( seasons_num, optional ); + i++; + break; + case 'c': + change_dir = false; + break; + case 'p': + path = string( optional ); + i++; + // if path provided, assume it's correct + change_dir = false; + break; + case 't': + trust = true; + break; + case 'x': + linux = true; + break; + case 'l': + if ( findLanguage( optional ) ) { + language = optional; + } else { + cerr << "Invalid language choice" << std::endl; + printLangs(); + return -1; + } + i++; + break; + case '0': + printLangs(); + return 1; + case 'h': + printHelp(); + return 1; + case '1': + pattern = optional; + i++; + break; + default: + return -1; + } + return 0; +} + +#ifdef _WIN32 + +string getOptions( const char_t *option ) { + if ( option[1] != '-' ) + return option + 1; + 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"--show-path" ) ) + return L"p"; + else if ( !wcscmp( option, L"--trust" ) ) + return L"t"; + else if ( !wcscmp( option, L"--linux" ) ) + return L"x"; + else if ( !wcscmp( option, L"--lang" ) ) + return L"l"; + 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"--help" ) ) + return L"h"; + return L""; +} + +// there's no getopt for windows, so just use wcscmp +int parseCommandLine( string &show, std::set< int > &seasons_num, string &path, + bool &change_dir, string &language, string &pattern, + bool &linux, bool &trust, const int argc, + char_t **argv ) { + for ( auto i = 1; i < argc; i++ ) { + auto options = getOptions( argv[i] ); + char_t *optional = ( i < argc - 1 ) ? argv[i + 1] : nullptr; + for ( const auto &x : options ) { + auto res = + handleArgument( x, show, seasons_num, change_dir, path, trust, + linux, language, 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, + bool &change_dir, string &language, string &pattern, + bool &linux, bool &trust, 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' }, - {"show-path", required_argument, 0, 'p' }, - {"trust", no_argument, 0, 't' }, - {"linux", no_argument, 0, 'x' }, - {"lang", required_argument, 0, 'l' }, - {"print-langs", no_argument, 0, '0' }, - {"name-pattern", required_argument, 0, '1' }, - {"help", no_argument, 0, 'h' } + { "show", required_argument, 0, 's' }, + { "season", required_argument, 0, 'n' }, + { "correct-path", no_argument, 0, 'c' }, + { "show-path", required_argument, 0, 'p' }, + { "trust", no_argument, 0, 't' }, + { "linux", no_argument, 0, 'x' }, + { "lang", required_argument, 0, 'l' }, + { "print-langs", no_argument, 0, '0' }, + { "name-pattern", required_argument, 0, '1' }, + { "help", no_argument, 0, 'h' } }; - while(1) { - int option_index{0}; - auto c = getopt_long(argc, argv, "s:n:cp:txl:01:h", long_options, &option_index); - if( c == -1 ) + 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:01:h", long_options, + &option_index ); + if ( c == -1 ) break; - switch(c) { - case 's': - show = optarg; - break; - case 'n': - parseSeasonNumbers(seasons_num, optarg); - break; - case 'c': - change_dir = false; - break; - case 'p': - path = std::string(optarg); - // if path provided, assume it's correct - change_dir = false; - break; - case 't': - trust = true; - break; - case 'x': - linux = true; - break; - case 'l': - if( findLanguage(optarg) ) { - language = optarg; - } else { - std::cerr << "Invalid language choice" << std::endl; - printLangs(); - return -1; - } - break; - case '0': - printLangs(); - return 1; - case 'h': - printHelp(); - return 1; - case '1': - pattern = optarg; - break; - default: - return -1; - } + auto res = handleArgument( c, show, seasons_num, change_dir, path, + trust, linux, language, pattern, optarg, i ); + if ( res != 0 ) + return res; } return 0; } -int main(int argc, char** argv) { - std::string show{}; - std::set seasons_num; - std::string path{"."}; - bool change_dir{true}; - std::string language{"en"}; - bool linux{false}; - bool trust{false}; - std::string pattern{"%filename - %epname"}; +#endif + +// 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_U16TEXT ); +#endif + string show{}; + std::set< int > seasons_num{}; + bool change_dir{ true }; + bool linux{ false }; + bool trust{ false }; + string path{ TEXT( "." ) }; + string language{ TEXT( "en" ) }; + string pattern{ TEXT( "%filename - %epname" ) }; Curl c; { - auto tmp = parseCommandLine(show, seasons_num, path, change_dir, language, pattern, linux, trust, argc, argv); - if( tmp == -1 ) + auto tmp = + parseCommandLine( show, seasons_num, path, change_dir, language, + pattern, linux, trust, argc, argv ); + if ( tmp == -1 ) return 1; else if ( tmp == 1 ) return 0; } - if( !FSLib::isDirectory(path) ) + if ( !FSLib::isDirectory( path ) ) change_dir = true; - while( change_dir ) { - if( !FSLib::isDirectory(path) ) { - std::cout << "This directory doesn't exist, please insert a correct path: " << std::endl; - std::getline(std::cin, path); + while ( change_dir ) { + if ( !FSLib::isDirectory( path ) ) { + cout << "This directory doesn't exist, please insert a correct " + "path: " + << std::endl; + std::getline( cin, path ); continue; } - std::cout << "Is this the right directory? " << FSLib::canonical(path) << std::endl; - std::string response; - std::cin >> response; - std::cin.ignore(1,'\n'); - std::cin.clear(); + cout << "Is this the right directory? " << FSLib::canonical( path ) + << std::endl; + string response; + cin >> response; + cin.ignore( 1, '\n' ); + cin.clear(); if ( response[0] == 'y' || response[0] == 'Y' ) { change_dir = false; } else { - std::cout << "Insert correct path:" << std::endl; - std::getline(std::cin, path); + cout << "Insert correct path:" << std::endl; + std::getline( cin, path ); } } - if( show.empty() ) { - auto pos = show.find_last_of('/'); - if( pos != std::string::npos ) - show = show.substr(++pos); - std::cout << "Is this the right show name? " << show << std::endl; - std::string response; - std::cin >> response; - std::cin.ignore(1, '\n'); - std::cin.clear(); - if( response[0] != 'y' && response[0] != 'Y' ) { - std::cout << "Insert the correct show name: " << std::endl; - std::getline(std::cin, show); + if ( show.empty() ) { + auto pos = show.find_last_of( '/' ); + if ( pos != string::npos ) + show = show.substr( ++pos ); + cout << "Is this the right show name? " << show << std::endl; + string response; + cin >> response; + cin.ignore( 1, '\n' ); + cin.clear(); + if ( response[0] != 'y' && response[0] != 'Y' ) { + cout << "Insert the correct show name: " << std::endl; + std::getline( cin, show ); } } - if( seasons_num.size() == 1 ) { - singleSeason(path, show, *seasons_num.begin(), "", language, pattern, linux, trust, c); + if ( seasons_num.size() == 1 ) { + singleSeason( path, show, *seasons_num.begin(), string(), language, + pattern, linux, trust, c ); } else if ( seasons_num.size() != 0 ) { - multipleSeasons(path, show, seasons_num, language, pattern, linux, trust, c); + multipleSeasons( path, show, seasons_num, language, pattern, linux, + trust, c ); } else { - allSeasons(path, show, language, pattern, linux, trust, c); + allSeasons( path, show, language, pattern, linux, trust, c ); } } - diff --git a/mainwindow.cpp b/mainwindow.cpp index 6453f55..5384c86 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,36 +1,38 @@ -#include "mainwindow.hpp" -#include "functions.hpp" -#include "filesystem.hpp" -#include "tv_rename.hpp" -#include +#include #include +#include #include #include #include -#include -constexpr std::array languages{ - "en", "English", "sv", "Svenska", "no", "Norsk", "da", "Dansk", "fi", "Suomeksi", - "nl", "Nederlands", "de", "Deutsch", "it", "Italiano", "es", "Español", "fr", "Français", - "pl", "Polski", "hu", "Magyar", "el", "Greek", "tr", "Turkish", "ru", "Russian", - "he", "Hebrew", "ja", "Japanese", "pt", "Portuguese", "zh", "Chinese", "cs", "Czech", - "sl", "Slovenian", "hr", "Croatian", "ko","Korea" +#include "filesystem.hpp" +#include "functions.hpp" +#include "mainwindow.hpp" +#include "tv_rename.hpp" + +constexpr std::array< const char *, 46 > languages{ + "en", "English", "sv", "Svenska", "no", "Norsk", "da", "Dansk", + "fi", "Suomeksi", "nl", "Nederlands", "de", "Deutsch", "it", "Italiano", + "es", "Español", "fr", "Français", "pl", "Polski", "hu", "Magyar", + "el", "Greek", "tr", "Turkish", "ru", "Russian", "he", "Hebrew", + "ja", "Japanese", "pt", "Portuguese", "zh", "Chinese", "cs", "Czech", + "sl", "Slovenian", "hr", "Croatian", "ko", "Korea" }; void MainWindow::chooseFile() { // create a dialog for choosing directory - Gtk::FileChooserDialog dialog("Select a directory", - Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); - dialog.set_transient_for(*this); + Gtk::FileChooserDialog dialog( "Select a directory", + Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER ); + dialog.set_transient_for( *this ); // add cancel and select buttons - dialog.add_button("_Cancel", Gtk::RESPONSE_CANCEL); - dialog.add_button("Select", Gtk::RESPONSE_OK); + dialog.add_button( "_Cancel", Gtk::RESPONSE_CANCEL ); + dialog.add_button( "Select", Gtk::RESPONSE_OK ); auto result = dialog.run(); - switch(result) { + switch ( result ) { case Gtk::RESPONSE_OK: - m_entry_dir.set_text(dialog.get_filename()); + m_entry_dir.set_text( dialog.get_filename() ); std::cout << dialog.get_filename() << std::endl; break; case Gtk::RESPONSE_CANCEL: @@ -48,74 +50,80 @@ void MainWindow::quit() { } void MainWindow::patternHelp() { - Gtk::MessageDialog dialog(*this, "Pattern escape sequences"); - dialog.set_secondary_text( "%filename - original filename (without type extension)\n" - "%show - show name from thetvdb\n" - "%epname - episode name from thetvdb\n" - "%season - season number\n" - "%episode - episode number\n" - "Both season number and episode number can be padded with zeros, just add width of padding" - " right after %, like this: %2season.\n" - "Default pattern is \"%filename - %epname\", you might want to change this to" - " \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - %epname\"" - ); + Gtk::MessageDialog dialog( *this, "Pattern escape sequences" ); + dialog.set_secondary_text( + "%filename - original filename (without type extension)\n" + "%show - show name from thetvdb\n" + "%epname - episode name from thetvdb\n" + "%season - season number\n" + "%episode - episode number\n" + "Both season number and episode number can be padded with zeros, just " + "add width of padding" + " right after %, like this: %2season.\n" + "Default pattern is \"%filename - %epname\", you might want to change " + "this to" + " \"S%2seasonE%2episode - %epname\" or \"%show - S%2seasonE%2episode - " + "%epname\"" ); dialog.run(); } void MainWindow::process() { - // check required fields are filled out - if( m_entry_show.get_text().empty() ) { - Gtk::MessageDialog dialog(*this, "Show field is empty"); + // check required field is filled out + if ( m_entry_show.get_text().empty() ) { + Gtk::MessageDialog dialog( *this, "Show field is empty" ); dialog.run(); return; } // language code - language_code = (*m_combo_language.get_active())[m_columns_language.m_col_code]; + language_code = + ( *m_combo_language.get_active() )[m_columns_language.m_col_code]; // fill up m_combo_possible with possible tv shows - auto possible_shows = getPossibleShows( std::string(m_entry_show.get_text()), language_code, c ); + auto possible_shows = getPossibleShows( + std::string( m_entry_show.get_text() ), language_code, c ); // if no possible shows were found, tell the user - if( possible_shows.size() == 0 ) { - Gtk::MessageDialog dialog(*this, "No results found for given show name"); + if ( possible_shows.size() == 0 ) { + Gtk::MessageDialog dialog( *this, + "No results found for given show name" ); dialog.run(); return; } - //show widgets + // show widgets m_label_possible.show(); m_button_get_names.show(); m_combo_possible.show(); // fill up combo box with results from thetvdb - auto model = Gtk::ListStore::create(m_columns_url); + auto model = Gtk::ListStore::create( m_columns_url ); - m_combo_possible.set_model(model); + m_combo_possible.set_model( model ); - auto row = *(model->append()); + auto row = *( model->append() ); row[m_columns_url.m_col_show] = possible_shows[0].first; row[m_columns_url.m_col_url] = possible_shows[0].second; - m_combo_possible.set_active(row); + m_combo_possible.set_active( row ); - for( size_t i = 1; i < possible_shows.size(); i++ ) { - auto row = *(model->append()); + for ( size_t i = 1; i < possible_shows.size(); i++ ) { + auto row = *( model->append() ); row[m_columns_url.m_col_show] = possible_shows[i].first; row[m_columns_url.m_col_url] = possible_shows[i].second; } } void MainWindow::getNames() { - // check required fields are filled out - if( m_entry_dir.get_text().empty() ) { - Gtk::MessageDialog dialog(*this, "Directory field is empty"); + // check required field is filled out + if ( m_entry_dir.get_text().empty() ) { + Gtk::MessageDialog dialog( *this, "Directory field is empty" ); dialog.run(); return; } // check directory exists - if( !FSLib::isDirectory(m_entry_dir.get_text()) ) { - Gtk::MessageDialog dialog(*this, "Directory doesn't exist"); + if ( !FSLib::isDirectory( m_entry_dir.get_text() ) ) { + Gtk::MessageDialog dialog( *this, "Directory doesn't exist" ); dialog.run(); return; } @@ -124,22 +132,23 @@ void MainWindow::getNames() { selected.clear(); files.clear(); - - std::vector options; + + std::vector< int > options; // get all files in path and seperate them in map `files` by season - iterateFS(files, path); + iterateFS( files, path ); - for( auto &x : files ) { - options.push_back(x.first); + for ( auto &x : files ) { + options.push_back( x.first ); } // create a window with possible seasons to rename // store selected seasons in `selected` - sw = new SeasonWindow(options, selected); - sw->signal_hide().connect(sigc::mem_fun(*this, &MainWindow::finishedSelection)); + sw = new SeasonWindow( options, selected ); + sw->signal_hide().connect( + sigc::mem_fun( *this, &MainWindow::finishedSelection ) ); - app->add_window(*sw); + app->add_window( *sw ); sw->show(); } @@ -147,113 +156,123 @@ void MainWindow::getNames() { * orig - original filenames * renamed - renamed filenames (sorted in the same order as `orig`) */ -void renameFiles(const std::vector>> &renamed) { - for(auto renamed_it = renamed.begin(); renamed_it != renamed.end(); ++renamed_it) { - std::cout << renamed_it->first << "/" << renamed_it->second.first << " --> " - << renamed_it->first << "/" << renamed_it->second.second << std::endl; - FSLib::rename(renamed_it->first + "/" + renamed_it->second.first - , renamed_it->first + "/" + renamed_it->second.second); +void renameFiles( + const std::vector< + std::pair< std::string, std::pair< std::string, std::string > > > + &renamed ) { + for ( auto renamed_it = renamed.begin(); renamed_it != renamed.end(); + ++renamed_it ) { + std::cout << renamed_it->first << "/" << renamed_it->second.first + << " --> " << renamed_it->first << "/" + << renamed_it->second.second << std::endl; + FSLib::rename( renamed_it->first + "/" + renamed_it->second.first, + renamed_it->first + "/" + renamed_it->second.second ); } } void MainWindow::finishedSelection() { // remove created SeasonWindow and delete it from memory - app->remove_window(*sw); + app->remove_window( *sw ); delete sw; auto iter = m_combo_possible.get_active(); // debug output - std::cout << (*iter)[m_columns_url.m_col_show] << " " << language_code << std::endl; + std::cout << ( *iter )[m_columns_url.m_col_show] << " " << language_code + << std::endl; - std::string url = static_cast((*iter)[m_columns_url.m_col_url]); + std::string url = + static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_url] ); // shouldn't ever happen, but just to be sure - if( url.empty() ) + if ( url.empty() ) return; std::cout << "https://www.thetvdb.com" << url << std::endl; std::string input_pattern = m_entry_pattern.get_text(); - if( input_pattern != default_pattern ) { - std::ofstream file(userHome() + "/.cache/tv_rename_pattern"); - if( file ) { + // store pattern to cache if it's different from default + if ( input_pattern != default_pattern ) { + std::ofstream file( userHome() + "/.cache/tv_rename_pattern" ); + if ( file ) { file << input_pattern; } } - for( auto &x : selected ) { + for ( auto &x : selected ) { // get renamed files for given season - auto renamed_files = getRenamedFiles( static_cast((*iter)[m_columns_url.m_col_show]), x, - "https://www.thetvdb.com" + url, - language_code, - (input_pattern.empty() ? default_pattern : input_pattern), - !m_check_linux.get_active(), c, files[x] ); + auto renamed_files = getRenamedFiles( + static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_show] ), + x, "https://www.thetvdb.com" + url, language_code, + ( input_pattern.empty() ? default_pattern : input_pattern ), + !m_check_linux.get_active(), c, files[x] ); - if( renamed_files.empty() ) + if ( renamed_files.empty() ) continue; // if trust checkbox is ticked, rename files - if( m_check_trust.get_active() ) { - renameFiles(renamed_files); + if ( m_check_trust.get_active() ) { + renameFiles( renamed_files ); continue; } // create a custom dialog box with textview of new episode names - Gtk::Dialog dialog("Rename confirmation", *this); + Gtk::Dialog dialog( "Rename confirmation", *this ); auto content = dialog.get_content_area(); Gtk::TextView tx; - content->pack_start(tx); - tx.set_editable(false); - dialog.add_button("_No", Gtk::RESPONSE_CANCEL); - dialog.add_button("_Yes", Gtk::RESPONSE_OK); + content->pack_start( tx ); + tx.set_editable( false ); + dialog.add_button( "_No", Gtk::RESPONSE_CANCEL ); + dialog.add_button( "_Yes", Gtk::RESPONSE_OK ); tx.show(); auto buff = tx.get_buffer(); - buff->place_cursor(buff->begin()); - buff->insert_at_cursor(renamed_files[0].second.first.c_str()); - buff->insert_at_cursor(" --> "); - buff->insert_at_cursor(renamed_files[0].second.second.c_str()); + buff->place_cursor( buff->begin() ); + buff->insert_at_cursor( renamed_files[0].second.first.c_str() ); + buff->insert_at_cursor( " --> " ); + buff->insert_at_cursor( renamed_files[0].second.second.c_str() ); - for( size_t i = 1; i < renamed_files.size(); i++ ) { - buff->insert_at_cursor("\n"); - buff->insert_at_cursor(renamed_files[i].second.first.c_str()); - buff->insert_at_cursor(" --> "); - buff->insert_at_cursor(renamed_files[i].second.second.c_str()); + for ( size_t i = 1; i < renamed_files.size(); i++ ) { + buff->insert_at_cursor( "\n" ); + buff->insert_at_cursor( renamed_files[i].second.first.c_str() ); + buff->insert_at_cursor( " --> " ); + buff->insert_at_cursor( renamed_files[i].second.second.c_str() ); } auto response = dialog.run(); // if user clicked "Yes" in dialog, rename files - switch(response) { + switch ( response ) { case Gtk::RESPONSE_OK: - renameFiles(renamed_files); + renameFiles( renamed_files ); default: break; } } } -MainWindow::MainWindow(const Glib::RefPtr &ptr) : app(ptr) { - set_title("TV Rename"); +MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ) + : app( ptr ) { + set_title( "TV Rename" ); - set_default_size(400, 310); - set_resizable(false); + set_default_size( 400, 310 ); + set_resizable( false ); { - std::ifstream file(userHome() + "/.cache/tv_rename_pattern"); - if( file ) { + // if cached pattern exists, load that instead of default + std::ifstream file( userHome() + "/.cache/tv_rename_pattern" ); + if ( file ) { std::getline( file, default_pattern ); } else { default_pattern = "%filename - %epname"; } } + add( m_layout ); - add(m_layout); - + // set widgets' location m_layout.put( m_label_show, 5, 5 ); m_layout.put( m_label_language, 190, 5 ); m_layout.put( m_entry_show, 5, 25 ); @@ -293,43 +312,53 @@ MainWindow::MainWindow(const Glib::RefPtr &ptr) : app(ptr) { m_combo_possible.set_size_request( 200 ); m_entry_show.set_size_request( 170, 30 ); - m_entry_dir.set_size_request( 170, 30 ); + m_entry_dir.set_size_request( 170, 30 ); - m_button_dir.set_size_request( 80, 30 ); - m_button_quit.set_size_request( 80, 30 ); - m_button_process.set_size_request( 80, 30 ); + m_button_dir.set_size_request( 80, 30 ); + m_button_quit.set_size_request( 80, 30 ); + m_button_process.set_size_request( 80, 30 ); m_button_get_names.set_size_request( 80, 30 ); // set default pattern - m_entry_pattern.set_text(default_pattern); + m_entry_pattern.set_text( default_pattern ); // put languages in combo box { - auto model = Gtk::ListStore::create(m_columns_language); - m_combo_language.set_model(model); - auto row = *(model->append()); + auto model = Gtk::ListStore::create( m_columns_language ); + m_combo_language.set_model( model ); + + auto row = *( model->append() ); row[m_columns_language.m_col_code] = "en"; row[m_columns_language.m_col_language] = "English"; - m_combo_language.set_active(row); - for(size_t i = 2; i < languages.size(); i += 2) { - row = *(model->append()); + + m_combo_language.set_active( row ); + + for ( size_t i = 2; i < languages.size(); i += 2 ) { + row = *( model->append() ); row[m_columns_language.m_col_code] = languages[i]; - row[m_columns_language.m_col_language] = languages[i+1]; + row[m_columns_language.m_col_language] = languages[i + 1]; } } // set column to be shown in comboboxes - m_combo_language.pack_start(m_columns_language.m_col_language); - m_combo_possible.pack_start(m_columns_url.m_col_show); + m_combo_language.pack_start( m_columns_language.m_col_language ); + m_combo_possible.pack_start( m_columns_url.m_col_show ); // set signals - m_button_dir.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::chooseFile)); - m_button_quit.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::quit)); - m_button_process.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::process)); - m_button_get_names.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::getNames)); - m_entry_show.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process)); - m_entry_dir.signal_activate().connect(sigc::mem_fun(*this, &MainWindow::process)); - m_button_pattern.signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::patternHelp)); + m_button_dir.signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::chooseFile ) ); + m_button_quit.signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::quit ) ); + m_button_process.signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::process ) ); + m_button_get_names.signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::getNames ) ); + m_entry_show.signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::process ) ); + m_entry_dir.signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::process ) ); + m_button_pattern.signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::patternHelp ) ); // show everything except possible shows and items related to them m_layout.show(); @@ -342,11 +371,10 @@ MainWindow::MainWindow(const Glib::RefPtr &ptr) : app(ptr) { m_button_quit.show(); m_button_dir.show(); m_check_linux.show(); - m_check_linux.set_active(true); + m_check_linux.set_active( true ); m_check_trust.show(); m_button_pattern.show(); m_entry_pattern.show(); m_label_pattern.show(); m_label_dir.show(); } - diff --git a/mainwindow.hpp b/mainwindow.hpp index bb9c827..92e99c3 100644 --- a/mainwindow.hpp +++ b/mainwindow.hpp @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include #include #include @@ -15,7 +15,7 @@ class MainWindow : public Gtk::Window { public: - MainWindow(const Glib::RefPtr &ptr); + MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ); virtual ~MainWindow() = default; private: @@ -55,37 +55,36 @@ protected: class LanguageColumns : public Gtk::TreeModel::ColumnRecord { public: - LanguageColumns() { - add(m_col_code); - add(m_col_language); + add( m_col_code ); + add( m_col_language ); } - Gtk::TreeModelColumn m_col_code; - Gtk::TreeModelColumn m_col_language; + Gtk::TreeModelColumn< std::string > m_col_code; + Gtk::TreeModelColumn< std::string > m_col_language; }; - class UrlColumns: public Gtk::TreeModel::ColumnRecord { + class UrlColumns : public Gtk::TreeModel::ColumnRecord { public: UrlColumns() { - add(m_col_url); - add(m_col_show); + add( m_col_url ); + add( m_col_show ); } - Gtk::TreeModelColumn m_col_url; - Gtk::TreeModelColumn m_col_show; + Gtk::TreeModelColumn< Glib::ustring > m_col_url; + Gtk::TreeModelColumn< Glib::ustring > m_col_show; }; LanguageColumns m_columns_language; UrlColumns m_columns_url; - Glib::RefPtr app; + Glib::RefPtr< Gtk::Application > app; SeasonWindow *sw; - std::vector selected; - std::map> files; + std::vector< int > selected; + std::map< int, std::set< std::string > > files; std::string path; std::string language_code; std::string default_pattern; }; -#endif // GTKMM_EXAMPLE_HELLOWORLD_H +#endif // GTKMM_MAIN_WINDOW diff --git a/network.cpp b/network.cpp index 3b2038d..74ee373 100644 --- a/network.cpp +++ b/network.cpp @@ -1,32 +1,83 @@ #include #include "network.hpp" -size_t writeCallback( void *contents, size_t size, size_t nmemb, void *target ) { - *static_cast(target) += std::string(static_cast(contents), size*nmemb); +#ifdef _WIN32 + +// shamelessly stolen from http://www.cplusplus.com/forum/windows/109799/ + +Curl::Curl() { + connect = InternetOpen( L"WinInet/1.0", INTERNET_OPEN_TYPE_PRECONFIG, + nullptr, nullptr, 0 ); + if ( !connect ) + std::wcerr << "Something went wrong while connecting" << std::endl; +} + +Curl::~Curl() { + if ( connect ) + InternetCloseHandle( connect ); +} + +std::string Curl::execute( const std::wstring &url ) { + std::string source{}; + source.reserve( 10000 ); + + HINTERNET openAddress = InternetOpenUrl( + connect, url.c_str(), nullptr, 0, + INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_KEEP_CONNECTION, 0 ); + + if ( !openAddress ) { + unsigned long errorNum = GetLastError(); + std::wcout << "Failed to open URL" << std::endl + << "Error No: " << errorNum << std::endl; + return ""; + } + + char dataReceived[4096]; + unsigned long numberOfBytesRead = 0; + while ( InternetReadFile( openAddress, dataReceived, 4096, + &numberOfBytesRead ) && + numberOfBytesRead ) { + source.append( dataReceived, numberOfBytesRead ); + } + + InternetCloseHandle( openAddress ); + return source; +} + +#else + +size_t writeCallback( void *contents, size_t size, size_t nmemb, + void *target ) { + *static_cast< std::string * >( target ) += + std::string( static_cast< char * >( contents ), size * nmemb ); return size * nmemb; } Curl::Curl() { - curl_global_init(CURL_GLOBAL_ALL); + curl_global_init( CURL_GLOBAL_ALL ); curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, writeCallback); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + curl_easy_setopt( curl_handle, CURLOPT_WRITEFUNCTION, writeCallback ); + curl_easy_setopt( curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0" ); } Curl::~Curl() { - curl_easy_cleanup(curl_handle); + curl_easy_cleanup( curl_handle ); curl_global_cleanup(); } -std::string Curl::execute(const std::string &url) { +std::string Curl::execute( const std::string &url ) { std::string source; - source.reserve(100000); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, static_cast(&source)); - curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); - auto res = curl_easy_perform(curl_handle); - if(res != CURLE_OK) { - std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl; + source.reserve( 100000 ); + curl_easy_setopt( curl_handle, CURLOPT_WRITEDATA, + static_cast< void * >( &source ) ); + curl_easy_setopt( curl_handle, CURLOPT_URL, url.c_str() ); + auto res = curl_easy_perform( curl_handle ); + if ( res != CURLE_OK ) { + std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror( res ) + << std::endl; return ""; } return source; } + +#endif diff --git a/network.hpp b/network.hpp index 47ed15b..41ec21b 100644 --- a/network.hpp +++ b/network.hpp @@ -1,16 +1,34 @@ #ifndef NETWORK_HPP #define NETWORK_HPP +#ifdef _WIN32 + +#include +#include + +using string = std::wstring; + +#else + #include #include +using string = std::string; + +#endif + class Curl { public: Curl(); ~Curl(); - std::string execute( const std::string &url ); + std::string execute( const string &url ); + private: +#ifdef _WIN32 + HINTERNET connect = nullptr; +#else CURL *curl_handle; +#endif }; #endif diff --git a/resource.h b/resource.h new file mode 100644 index 0000000..4a441c6 --- /dev/null +++ b/resource.h @@ -0,0 +1,50 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by tv_rename_gui.rc +// +#define IDC_MYICON 2 +#define IDD_TV_RENAME_GUI_DIALOG 102 +#define IDS_APP_TITLE 103 +#define IDI_TV_RENAME_GUI 107 +#define IDI_SMALL 108 +#define IDC_TV_RENAME_GUI 109 +#define IDR_MAINFRAME 128 +#define IDD_MAIN 129 +#define IDD_SEASONS 130 +#define IDD_HELP 131 +#define IDD_DIALOG1 132 +#define IDD_PREVIEW 132 +#define IDC_COMBO1 1000 +#define IDLANG 1000 +#define IDOK_MAIN 1001 +#define ID 1002 +#define IDEND 1002 +#define IDALL 1003 +#define IDC_BUTTON2 1004 +#define IDNONE 1004 +#define IDDIRB 1004 +#define IDSHOWIN 1006 +#define ID_STATIC 1007 +#define IDLANGUAGE 1009 +#define IDDIR 1010 +#define IDPATTERN 1011 +#define IDPROCESS 1013 +#define IDTRUST 1015 +#define IDSHOWS 1016 +#define IDSEASONS 1017 +#define IDC_STATIC6 1019 +#define ID_STATIC6 1019 +#define IDTEXT 1020 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 133 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1021 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/seasonwindow.cpp b/seasonwindow.cpp index 27e39b8..f473d3b 100644 --- a/seasonwindow.cpp +++ b/seasonwindow.cpp @@ -5,9 +5,9 @@ void SeasonWindow::confirm() { // go through all checkbuttons and save numbers // of checked boxes into returned vector // then quit - for( auto &x : m_checks ) { - if( x.get_active() ) { - returned.push_back(std::stoi(x.get_label())); + for ( auto &x : m_checks ) { + if ( x.get_active() ) { + returned.push_back( std::stoi( x.get_label() ) ); } } hide(); @@ -15,34 +15,36 @@ void SeasonWindow::confirm() { void SeasonWindow::select_all() { // set all check boxes to checked - for( auto &x : m_checks ) { - x.set_active(true); + for ( auto &x : m_checks ) { + x.set_active( true ); } } void SeasonWindow::select_none() { // set all check boxes to unchecked - for( auto &x : m_checks ) { - x.set_active(false); + for ( auto &x : m_checks ) { + x.set_active( false ); } } -SeasonWindow::SeasonWindow(const std::vector &seasons, std::vector &_returned) : returned(_returned) { - set_title("Choose seasons"); +SeasonWindow::SeasonWindow( const std::vector< int > &seasons, + std::vector< int > &_returned ) + : returned( _returned ) { + set_title( "Choose seasons" ); - set_default_size(250, 250); - set_resizable(false); + set_default_size( 250, 250 ); + set_resizable( false ); - add(m_layout); + add( m_layout ); - size_t x{5}, y{25}; + size_t x{ 5 }, y{ 25 }; // create a check box for each season - for( auto &s : seasons ) { - m_checks.emplace_back(std::to_string(s)); - m_layout.put(m_checks.back(), x, y); + for ( auto &s : seasons ) { + m_checks.emplace_back( std::to_string( s ) ); + m_layout.put( m_checks.back(), x, y ); - if( x == 185 ) { + if ( x == 185 ) { x = 5; y += 20; } else { @@ -50,24 +52,27 @@ SeasonWindow::SeasonWindow(const std::vector &seasons, std::vector &_r } } - m_layout.put(m_label, 5, 5); - m_label.set_label("Select seasons:"); + m_layout.put( m_label, 5, 5 ); + m_label.set_label( "Select seasons:" ); - m_layout.put(m_confirm, 165, 215); - m_layout.put(m_all, 130, 175); - m_layout.put(m_none, 5, 175); + m_layout.put( m_confirm, 165, 215 ); + m_layout.put( m_all, 130, 175 ); + m_layout.put( m_none, 5, 175 ); - m_confirm.set_size_request(80,30); - m_all.set_size_request(80,30); - m_none.set_size_request(80,30); + m_confirm.set_size_request( 80, 30 ); + m_all.set_size_request( 80, 30 ); + m_none.set_size_request( 80, 30 ); - m_confirm.set_label("Select"); - m_all.set_label("Select All"); - m_none.set_label("Unselect All"); + m_confirm.set_label( "Select" ); + m_all.set_label( "Select All" ); + m_none.set_label( "Unselect All" ); - m_confirm.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::confirm)); - m_all.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_all)); - m_none.signal_clicked().connect(sigc::mem_fun(*this, &SeasonWindow::select_none)); + m_confirm.signal_clicked().connect( + sigc::mem_fun( *this, &SeasonWindow::confirm ) ); + m_all.signal_clicked().connect( + sigc::mem_fun( *this, &SeasonWindow::select_all ) ); + m_none.signal_clicked().connect( + sigc::mem_fun( *this, &SeasonWindow::select_none ) ); show_all_children(); } diff --git a/seasonwindow.hpp b/seasonwindow.hpp index df98244..4b7de80 100644 --- a/seasonwindow.hpp +++ b/seasonwindow.hpp @@ -3,13 +3,14 @@ #include #include -#include #include +#include #include class SeasonWindow : public Gtk::Window { public: - SeasonWindow(const std::vector &seasons, std::vector &_returned); + SeasonWindow( const std::vector< int > &seasons, + std::vector< int > &_returned ); virtual ~SeasonWindow() = default; private: @@ -26,9 +27,9 @@ protected: Gtk::Layout m_layout; - std::vector m_checks; + std::vector< Gtk::CheckButton > m_checks; - std::vector &returned; + std::vector< int > &returned; }; #endif diff --git a/small.ico b/small.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3ec03bd617f32e58128fa977fd6ac9605124f4b GIT binary patch literal 46227 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 -#include "functions.hpp" -#include "tv_rename.hpp" +#include + +#ifdef _WIN32 + +#include +#include +#include +#include +#include + +#endif #ifndef GUI @@ -11,24 +20,53 @@ #endif -std::vector parseEpisodeNames( const std::string &season_code, const std::string &language) { - std::vector episodes; +#include "functions.hpp" +#include "tv_rename.hpp" + +#ifdef _WIN32 + +constexpr const char_t *dir_divider = L"\\"; + +#define cout std::wcout +#define cerr std::wcerr +#define cin std::wcin + +#else + +constexpr const char_t *dir_divider = "/"; + +#define cout std::cout +#define cerr std::cerr +#define cin std::cin + +#define TEXT( a ) a + +#endif + +// get names for all episodes for a given season +std::vector< string > parseEpisodeNames( const string &season_code, + const string &language ) { + std::vector< string > episodes; size_t pos = 0; - while( true ) { - pos = season_code.find("ge=\"" + language + "\" ", pos); - if( pos != std::string::npos ) { - if( language == "en" ) + while ( true ) { + pos = + season_code.find( TEXT( "ge=\"" ) + language + TEXT( "\" " ), pos ); + if ( pos != string::npos ) { + if ( language == TEXT( "en" ) ) pos += 17; else pos += 29; - while( isspace(season_code[pos]) ) + while ( iswspace( season_code[pos] ) ) pos++; - auto end = season_code.find("<", pos); + + auto end = season_code.find( TEXT( "<" ), pos ); end--; - while( isspace(season_code[end]) ) + + while ( iswspace( season_code[end] ) ) end--; + end++; - episodes.push_back(season_code.substr(pos, end - pos)); + episodes.push_back( season_code.substr( pos, end - pos ) ); } else { break; } @@ -36,96 +74,130 @@ std::vector parseEpisodeNames( const std::string &season_code, cons return episodes; } -std::vector>> getRenamedFiles( const std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, Curl &c, const std::set &files ) { - url += "/seasons/" + std::to_string(season); - //get source code of season's page - auto season_code = c.execute(url); - //remove newlines - season_code.erase(std::remove_if(season_code.begin(), season_code.end(), [](char x) {return x == '\r' || x == '\n';}), season_code.end()); - //first 900 chars or so are useless to us - season_code = season_code.substr(900); - //get only the episode names - auto pos = season_code.find("\"translations\""); - if( pos != std::string::npos ) { - season_code = season_code.substr(pos); - pos = season_code.find(""); - if( pos != std::string::npos ) - season_code = season_code.substr(0,pos); +std::vector< std::pair< string, std::pair< string, string > > > +getRenamedFiles( const string &show, int season, string url, + const string &language, const string &pattern, + const bool &linux, Curl &c, const std::set< string > &files ) { +#ifdef _WIN32 + auto season_num = std::to_wstring( season ); +#else + auto season_num = std::to_string( season ); +#endif + + url += TEXT( "/seasons/" ); + url += season_num; + + // get source code of season's page + auto season_code = c.execute( url ); + + // remove newlines + season_code.erase( + std::remove_if( season_code.begin(), season_code.end(), + []( char x ) { return x == '\r' || x == '\n'; } ), + season_code.end() ); + + // first 900 chars or so are useless to us + season_code = season_code.substr( 900 ); + + // get only the episode names + auto pos = season_code.find( "\"translations\"" ); + + if ( pos != string::npos ) { + season_code = season_code.substr( pos ); + pos = season_code.find( "" ); + if ( pos != string::npos ) + season_code = season_code.substr( 0, pos ); else return {}; } else { return {}; } - auto episodes = parseEpisodeNames(season_code, language); +#ifdef _WIN32 + auto episodes = + parseEpisodeNames( utf8_to_wstring( season_code ), language ); +#else + auto episodes = parseEpisodeNames( season_code, language ); +#endif - if( episodes.empty() ) + if ( episodes.empty() ) return {}; - std::vector>> renamed_files; - - if( files.empty() ) + if ( files.empty() ) return {}; - for( const auto &x : files ) { - auto last = x.find_last_of("/"); - std::string og_name; - std::string dir; - if( last == std::string::npos ) { + std::vector< std::pair< string, std::pair< string, string > > > + renamed_files; + + for ( const auto &x : files ) { + auto last = x.find_last_of( dir_divider ); + string og_name; + string dir; + if ( last == string::npos ) { og_name = x; - dir = "."; + dir = TEXT( "." ); } else { - og_name = x.substr(last+1); - dir = x.substr(0, last); + og_name = x.substr( last + 1 ); + dir = x.substr( 0, last ); } unsigned long num; // get file's episode number - if( searchSpecificSeason(x.c_str(), pos, std::to_string(season)) ) { - num = atoi(x.c_str()+pos); + if ( searchSpecificSeason( x.c_str(), pos, season_num ) ) { + num = std::stoi( x.c_str() + pos ); } else { continue; } + num -= 1; - if( num < episodes.size() ) { - auto pos = og_name.find_last_of('.'); + if ( num < episodes.size() ) { + auto pos = og_name.find_last_of( TEXT( "." ) ); // get desired filename - auto name = compilePattern(pattern, season, num+1, og_name.substr(0, pos), episodes[num], show) + og_name.substr(pos); + auto name = compilePattern( pattern, season, num + 1, + og_name.substr( 0, pos ), episodes[num], + show ) + + og_name.substr( pos ); // replace '/' with '|' - for( size_t i = 0; i < name.size(); i++ ) { - if( name[i] == '/' ) { + for ( size_t i = 0; i < name.size(); i++ ) { + if ( name[i] == '/' ) { name[i] = '|'; } } // replace characters illegal in windows if desired - if( !linux ) { - name.erase(std::remove_if(name.begin(), name.end(), [](char x) {return x == '?' || x == '"' || x == '\\' || x == '*';}), name.end()); - size_t max{name.size()}; - for( size_t i = 0; i < max ; i++ ) { - if( name[i] == '|' ) { + if ( !linux ) { + name.erase( std::remove_if( name.begin(), name.end(), + []( char_t x ) { + return x == '?' || x == '"' || + x == '\\' || x == '*'; + } ), + name.end() ); + size_t max{ name.size() }; + for ( size_t i = 0; i < max; i++ ) { + if ( name[i] == '|' ) { name[i] = '-'; - }else if( name[i] == '<' ) { + } else if ( name[i] == '<' ) { name[i] = 'i'; - name.insert(i+1, "s less than"); + name.insert( i + 1, TEXT( "s less than" ) ); max += 11; } else if ( name[i] == '>' ) { name[i] = 'i'; - name.insert(i+1, "s more than"); + name.insert( i + 1, TEXT( "s more than" ) ); max += 11; } else if ( name[i] == ':' ) { name[i] = ' '; - name.insert(i+1, 1, '-'); + name.insert( i + 1, 1, '-' ); max++; } } - for( size_t i = 0; i < max; i++ ) { - if ( name[i] == ' ' && name[i+1] == ' ' ) { - name.erase(i, 1); + for ( size_t i = 0; i < max; i++ ) { + if ( name[i] == ' ' && name[i + 1] == ' ' ) { + name.erase( i, 1 ); max--; i--; } } } - renamed_files.emplace_back(dir, std::pair(og_name, name)); + renamed_files.emplace_back( + dir, std::pair< string, string >( og_name, name ) ); } } return renamed_files; @@ -133,62 +205,80 @@ std::vector>> getRena #ifndef GUI -void singleSeason( const std::string &path, std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c, std::set const *files_ptr) { - if( url.empty() ) - url = getDefUrl(show, language, c); +void singleSeason( const string &path, string &show, int season, string url, + const string &language, const string &pattern, + const bool &linux, const bool &trust, Curl &c, + std::set< string > const *files_ptr ) { + if ( url.empty() ) + url = getDefUrl( show, language, c ); - std::set *found_files = nullptr; + std::set< string > *found_files = nullptr; - if( files_ptr == nullptr ) { - found_files = new std::set; - findSeason(*found_files, season, path); + if ( files_ptr == nullptr ) { + found_files = new std::set< string >; + findSeason( *found_files, season, path ); files_ptr = found_files; } - auto renamed_files = getRenamedFiles(show, season, std::move(url), language, pattern, linux, c, *files_ptr); + auto renamed_files = + getRenamedFiles( show, season, std::move( url ), language, pattern, + linux, c, *files_ptr ); - for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { - std::cout << renamed->second.first << " --> " << renamed->second.second << std::endl; + for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); + ++renamed ) { + cout << renamed->second.first << " --> " << renamed->second.second + << std::endl; } - if( !trust ) { - std::cout << "Does this seem ok? (y/n) "; - std::string response; - std::cin >> response; - std::cin.clear(); - std::cin.ignore(1, '\n'); - if( response[0] != 'y' && response[0] != 'Y' ) + if ( !trust ) { + cout << "Does this seem ok? (y/n) "; + string response; + cin >> response; + cin.clear(); + cin.ignore( 1, '\n' ); + if ( response[0] != 'y' && response[0] != 'Y' ) return; } - for(auto renamed = renamed_files.begin(); renamed != renamed_files.end(); ++renamed) { - FSLib::rename(renamed->first + "/" + renamed->second.first, renamed->first + "/" + renamed->second.second); + for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); + ++renamed ) { + FSLib::rename( renamed->first + dir_divider + renamed->second.first, + renamed->first + dir_divider + renamed->second.second ); } - if( found_files != nullptr ) { + if ( found_files != nullptr ) { delete found_files; } } -void multipleSeasons( const std::string &path, std::string &show, const std::map> &seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) { - auto url = getDefUrl(show, language, c); - for( const auto &x : seasons ) { - singleSeason( path, show, x.first, url, language, pattern, linux, trust, c, &x.second); +void multipleSeasons( const string &path, string &show, + const std::map< int, std::set< string > > &seasons, + const string &language, const string &pattern, + const bool &linux, const bool &trust, Curl &c ) { + auto url = getDefUrl( show, language, c ); + for ( const auto &x : seasons ) { + singleSeason( path, show, x.first, url, language, pattern, linux, trust, + c, &x.second ); } } -void multipleSeasons( const std::string &path, std::string &show, const std::set seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) { - std::map> season_map; - findSeasons(season_map, path, seasons); - multipleSeasons(path, show, season_map, language, pattern, linux, trust, c); +void multipleSeasons( const string &path, string &show, + const std::set< int > seasons, const string &language, + const string &pattern, const bool &linux, + const bool &trust, Curl &c ) { + std::map< int, std::set< string > > season_map; + findSeasons( season_map, path, seasons ); + multipleSeasons( path, show, season_map, language, pattern, linux, trust, + c ); } -void allSeasons( const std::string &path, std::string &show, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c) { - std::map> seasons; - //get all season number from this directory and subdirectories - iterateFS(seasons, path); - multipleSeasons( path, show, seasons, language, pattern, linux, trust, c); +void allSeasons( const string &path, string &show, const string &language, + const string &pattern, const bool &linux, const bool &trust, + Curl &c ) { + std::map< int, std::set< string > > seasons; + // get all season number from this directory and subdirectories + iterateFS( seasons, path ); + multipleSeasons( path, show, seasons, language, pattern, linux, trust, c ); } #endif - diff --git a/tv_rename.hpp b/tv_rename.hpp index 9e25e27..121b866 100644 --- a/tv_rename.hpp +++ b/tv_rename.hpp @@ -2,19 +2,45 @@ #define TV_RENAME_HPP #include -#include "network.hpp" #ifdef GUI - #include +#endif -std::vector>> getRenamedFiles( const std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, Curl &c, const std::set &files ); +#include "network.hpp" + +#ifdef _WIN32 + +using string = std::wstring; +using char_t = wchar_t; #else -void singleSeason( const std::string &path, std::string &show, int season, std::string url, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c, std::set const *files=nullptr); -void multipleSeasons( const std::string &path, std::string &show, const std::set seasons, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c); -void allSeasons( const std::string &path, std::string &show, const std::string &language, const std::string &pattern, const bool &linux, const bool &trust, Curl &c); +using string = std::string; +using char_t = char; + +#endif + +#ifdef GUI + +std::vector< std::pair< string, std::pair< string, string > > > +getRenamedFiles( const string &show, int season, string url, + const string &language, const string &pattern, + const bool &linux, Curl &c, const std::set< string > &files ); + +#else + +void singleSeason( const string &path, string &show, int season, string url, + const string &language, const string &pattern, + const bool &linux, const bool &trust, Curl &c, + std::set< string > const *files = nullptr ); +void multipleSeasons( const string &path, string &show, + const std::set< int > seasons, const string &language, + const string &pattern, const bool &linux, + const bool &trust, Curl &c ); +void allSeasons( const string &path, string &show, const string &language, + const string &pattern, const bool &linux, const bool &trust, + Curl &c ); #endif diff --git a/tv_rename_gui.cpp b/tv_rename_gui.cpp new file mode 100644 index 0000000..0d51827 --- /dev/null +++ b/tv_rename_gui.cpp @@ -0,0 +1,360 @@ +// many thanks to https://www.winprog.org/tutorial from where I got a lot of +// code for this + +#include +#include +#include +#include +#include +#include + +#include "filesystem.hpp" +#include "functions.hpp" +#include "resource.h" +#include "tv_rename.hpp" + +constexpr std::array< const wchar_t *, 46 > languages{ + L"en", L"English", L"sv", L"Svenska", L"no", L"Norsk", + L"da", L"Dansk", L"fi", L"Suomeksi", L"nl", L"Nederlands", + L"de", L"Deutsch", L"it", L"Italiano", L"es", L"Español", + L"fr", L"Français", L"pl", L"Polski", L"hu", L"Magyar", + L"el", L"Greek", L"tr", L"Turkish", L"ru", L"Russian", + L"he", L"Hebrew", L"ja", L"Japanese", L"pt", L"Portuguese", + L"zh", L"Chinese", L"cs", L"Czech", L"sl", L"Slovenian", + L"hr", L"Croatian", L"ko", L"Korea" +}; + +std::vector< int > options; +std::vector< int > checkboxes; +std::vector< int > checked; +Curl c; + +wchar_t lang_code[3] = L"en"; +std::wstring default_pattern = L"%filename - %epname"; + +std::map< int, std::set< string > > files; +std::vector< std::pair< string, string > > possible_shows; +std::vector< std::pair< string, std::pair< string, string > > > + *current_renamed_files{ nullptr }; + +BOOL CALLBACK HelpBox( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { + switch ( message ) { + case WM_COMMAND: + switch ( LOWORD( wParam ) ) { + case IDOK: + EndDialog( hwnd, 0 ); + default: + break; + } + break; + case WM_CLOSE: + EndDialog( hwnd, 0 ); + break; + default: + return FALSE; + } + return TRUE; +} + +BOOL CALLBACK SeasonsBox( HWND hwnd, UINT message, WPARAM wParam, + LPARAM lParam ) { + switch ( message ) { + case WM_INITDIALOG: { + checked.clear(); + checkboxes.clear(); + int left{ 15 }, top{ 30 }; + for ( int i = 0; i < options.size(); i++ ) { + if ( checkboxes.empty() ) { + checkboxes.push_back( 2000 ); + } else { + checkboxes.push_back( checkboxes.back() + 1 ); + } + auto hCheckBox = CreateWindowEx( + 0, L"Button", std::to_wstring( options[i] ).c_str(), + WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX, left, top, 60, 20, + hwnd, ( HMENU )checkboxes.back(), GetModuleHandle( NULL ), + NULL ); + if ( hCheckBox == NULL ) { + MessageBox( hwnd, L"Could not create checkbox", L"Error", + MB_OK | MB_ICONERROR ); + return TRUE; + } + auto hfDefault = GetStockObject( DEFAULT_GUI_FONT ); + SendMessage( hCheckBox, WM_SETFONT, ( WPARAM )hfDefault, + MAKELPARAM( FALSE, 0 ) ); + if ( left == 195 ) { + left = 15; + top += 20; + } else { + left += 60; + } + } + } + return TRUE; + case WM_COMMAND: + switch ( LOWORD( wParam ) ) { + case IDOK: + for ( int i = 0; i < checkboxes.size(); i++ ) { + if ( SendDlgItemMessage( hwnd, checkboxes[i], BM_GETCHECK, 0, + 0 ) ) + checked.push_back( options[i] ); + } + EndDialog( hwnd, 0 ); + break; + case IDALL: + for ( auto &x : checkboxes ) { + SendDlgItemMessage( hwnd, x, BM_SETCHECK, BST_CHECKED, 0 ); + } + break; + case IDNONE: + for ( auto &x : checkboxes ) { + SendDlgItemMessage( hwnd, x, BM_SETCHECK, BST_UNCHECKED, 0 ); + } + break; + default: + break; + } + break; + case WM_CLOSE: + EndDialog( hwnd, 0 ); + break; + default: + return FALSE; + } + return TRUE; +} + +// stolen from +// https://www.codeproject.com/Articles/2604/Browse-Folder-dialog-search-folder-and-all-sub-fol +std::wstring getDir() { + wchar_t path[MAX_PATH]; + BROWSEINFO bi{}; + bi.lpszTitle = L"Choose a folder"; + LPITEMIDLIST pidl = SHBrowseForFolder( &bi ); + if ( pidl != 0 ) { + SHGetPathFromIDList( pidl, path ); + IMalloc *imalloc = 0; + if ( SUCCEEDED( SHGetMalloc( &imalloc ) ) ) { + imalloc->Free( pidl ); + imalloc->Release(); + } + } else { + path[0] = '\0'; + } + return path; +} + +void renameFiles( + const std::vector< std::pair< string, std::pair< string, string > > > + &renamed ) { + for ( auto renamed_it = renamed.begin(); renamed_it != renamed.end(); + ++renamed_it ) { + FSLib::rename( renamed_it->first + L"\\" + renamed_it->second.first, + renamed_it->first + L"\\" + renamed_it->second.second ); + } +} + +BOOL CALLBACK PreviewBox( HWND hwnd, UINT message, WPARAM wParam, + LPARAM lParam ) { + switch ( message ) { + case WM_INITDIALOG: { + std::wstring text{}; + for ( auto renamed_it = current_renamed_files->begin(); + renamed_it != current_renamed_files->end(); ++renamed_it ) { + text += renamed_it->second.first + L" --> " + + renamed_it->second.second + L"\r\n"; + } + SetDlgItemText( hwnd, IDTEXT, text.c_str() ); + } + return TRUE; + case WM_COMMAND: + switch ( LOWORD( wParam ) ) { + case IDYES: { + renameFiles( *current_renamed_files ); + EndDialog( hwnd, IDYES ); + } break; + case IDNO: + EndDialog( hwnd, IDNO ); + default: + break; + } + break; + case WM_CLOSE: + EndDialog( hwnd, 0 ); + break; + default: + return FALSE; + } + return TRUE; +} + +void finishedSelection( HWND hwnd ) { + auto index = SendDlgItemMessage( hwnd, IDSHOWS, CB_GETCURSEL, 0, 0 ); + auto url = possible_shows[index].second; + auto show = possible_shows[index].first; + wchar_t input_pattern[100]; + + GetDlgItemText( hwnd, IDPATTERN, input_pattern, 100 ); + + if ( input_pattern != default_pattern ) { + std::wofstream file( userHome() + L"\\tv_rename\\pattern" ); + if ( file ) { + file << input_pattern; + } + } + + for ( auto &x : checked ) { + auto renamed_files = getRenamedFiles( + show, x, L"https://www.thetvdb.com" + url, lang_code, + ( input_pattern[0] == '\0' ? default_pattern : input_pattern ), + false, c, files[x] ); + if ( renamed_files.empty() ) + continue; + if ( SendDlgItemMessage( hwnd, IDTRUST, BM_GETCHECK, 0, 0 ) ) { + renameFiles( renamed_files ); + continue; + } + + current_renamed_files = &renamed_files; + DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_PREVIEW ), + hwnd, PreviewBox ); + } + if ( SendDlgItemMessage( hwnd, IDTRUST, BM_GETCHECK, 0, 0 ) ) { + MessageBox( hwnd, L"Finished renaming files", L"Info", + MB_OK | MB_ICONINFORMATION ); + } +} + +void getNames( HWND hwnd ) { + wchar_t path[MAX_PATH]; + GetDlgItemText( hwnd, IDDIR, path, MAX_PATH ); + + if ( wcslen( path ) == 0 ) { + MessageBox( hwnd, L"Folder field is empty", L"Error", + MB_OK | MB_ICONERROR ); + return; + } + if ( !FSLib::isDirectory( path ) ) { + MessageBox( hwnd, L"Folder doesn't exist", L"Error", + MB_OK | MB_ICONERROR ); + return; + } + + checked.clear(); + checkboxes.clear(); + options.clear(); + files.clear(); + + iterateFS( files, path ); + for ( auto &x : files ) { + options.push_back( x.first ); + } + + DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_SEASONS ), hwnd, + SeasonsBox ); + finishedSelection( hwnd ); +} + +void process( HWND hwnd ) { + wchar_t show[100]; + GetDlgItemText( hwnd, IDSHOWIN, show, 100 ); + + if ( wcslen( show ) == 0 ) { + MessageBox( hwnd, L"Show field is empty", L"Error", + MB_OK | MB_ICONERROR ); + return; + } + + wchar_t language[20]; + GetDlgItemText( hwnd, IDLANG, language, 20 ); + + for ( int i = 1; i < languages.size(); i += 2 ) { + if ( !wcscmp( languages[i], language ) ) { + wcscpy_s( lang_code, languages[i - 1] ); + break; + } + } + + possible_shows = getPossibleShows( show, lang_code, c ); + if ( possible_shows.size() == 0 ) { + MessageBox( hwnd, L"No results found for given show name", L"Error", + MB_OK | MB_ICONERROR ); + return; + } + + ShowWindow( GetDlgItem( hwnd, ID_STATIC6 ), SW_SHOW ); + ShowWindow( GetDlgItem( hwnd, IDSHOWS ), SW_SHOW ); + ShowWindow( GetDlgItem( hwnd, IDSEASONS ), SW_SHOW ); + + SendDlgItemMessage( hwnd, IDSHOWS, CB_RESETCONTENT, 0, 0 ); + for ( int i = 0; i < possible_shows.size(); i++ ) { + SendDlgItemMessage( hwnd, IDSHOWS, CB_ADDSTRING, 0, + ( LPARAM )possible_shows[i].first.c_str() ); + } + SendDlgItemMessage( hwnd, IDSHOWS, CB_SETCURSEL, 0, 0 ); +} + +void readDefaultPattern( const string &base_dir ) { + std::wifstream file( base_dir + L"\\pattern" ); + if ( file ) { + std::getline( file, default_pattern ); + } +} + +BOOL CALLBACK MainBox( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { + switch ( message ) { + case WM_INITDIALOG: { + for ( int i = 1; i < languages.size(); i += 2 ) { + SendDlgItemMessage( hwnd, IDLANG, CB_ADDSTRING, 0, + ( LPARAM )languages[i] ); + } + SendDlgItemMessage( hwnd, IDLANG, CB_SETCURSEL, 5, 0 ); + + auto appdata = userHome() + L"\\tv_rename"; + if ( !FSLib::isDirectory( appdata ) ) { + if ( CreateDirectory( appdata.c_str(), NULL ) ) { + readDefaultPattern( appdata ); + } + } else { + readDefaultPattern( appdata ); + } + + SetDlgItemText( hwnd, IDPATTERN, default_pattern.c_str() ); + } + return TRUE; + case WM_COMMAND: + switch ( LOWORD( wParam ) ) { + case IDPROCESS: { + process( hwnd ); + } break; + case IDEND: + EndDialog( hwnd, IDOK_MAIN ); + break; + case IDSEASONS: + getNames( hwnd ); + break; + case IDDIRB: + SetDlgItemText( hwnd, IDDIR, getDir().c_str() ); + break; + case IDHELP: + DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_HELP ), + hwnd, HelpBox ); + break; + default: + break; + } + break; + case WM_CLOSE: + EndDialog( hwnd, 0 ); + break; + default: + return FALSE; + } + return TRUE; +} + +int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow ) { + return DialogBox( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDD_MAIN ), + NULL, MainBox ); +} diff --git a/tv_rename_gui.ico b/tv_rename_gui.ico new file mode 100644 index 0000000000000000000000000000000000000000..b3ec03bd617f32e58128fa977fd6ac9605124f4b GIT binary patch literal 46227 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