diff --git a/Makefile b/Makefile index cc61c5c..33955ac 100644 --- a/Makefile +++ b/Makefile @@ -60,12 +60,12 @@ progress.o: progress.cpp .PHONY: gui gui: tv_rename_gui -tv_rename_gui: gui.cpp mainwindow.cpp seasonwindow.cpp network.o\ +tv_rename_gui: gui.cpp mainwindow.cpp seasonwindow.cpp databasewindow.cpp network.o\ functions_gui.o filesystem_u_gui.o tv_rename_gui.o - $(CXX) $(CFLAGS) -o tv_rename_gui gui.cpp mainwindow.cpp seasonwindow.cpp\ + $(CXX) $(CFLAGS) -o tv_rename_gui gui.cpp mainwindow.cpp seasonwindow.cpp databasewindow.cpp\ network.o functions_gui.o filesystem_u_gui.o\ tv_rename_gui.o `pkg-config gtkmm-3.0 --cflags --libs`\ - -lcurl -DGUI + -lcurl -lsqlite3 -DGUI filesystem_u_gui.o: unix/filesystem.cpp $(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u_gui.o -DGUI diff --git a/databasewindow.cpp b/databasewindow.cpp new file mode 100644 index 0000000..7923030 --- /dev/null +++ b/databasewindow.cpp @@ -0,0 +1,98 @@ +#include +#include + +#include "databasewindow.hpp" +#include "functions.hpp" + +DatabaseWindow::~DatabaseWindow() { + delete m_button_save; + delete m_button_remove; + delete m_tree_database; + std::cout << "Deleting" << std::endl; +} + +DatabaseWindow::DatabaseWindow( bool _linux, Curl &_c ) : c(_c), linux(_linux) { + set_title( "Database" ); + + set_default_size( 550, 350 ); + set_resizable( false ); + + auto *layout = new Gtk::Layout(); + add( *layout ); + + m_scrolled_window->set_size_request( 550, 300 ); + m_scrolled_window->add( *m_tree_database ); + + // set widgets' location + layout->put( *m_scrolled_window, 0, 0 ); + layout->put( *m_button_save, 380, 310 ); + layout->put( *m_button_remove, 470, 310 ); + + // set button texts + m_button_save->set_label( "Save" ); + m_button_remove->set_label( "Delete" ); + + // set dimensions + m_button_save->set_size_request( 80, 30 ); + m_button_remove->set_size_request( 80, 30 ); + + // put languages in combo box + m_model = Gtk::ListStore::create( m_columns_database ); + m_tree_database->set_model( m_model ); + + for( auto &x : dbGetShows() ) { + if( x["SHOW"] == "pattern" ) + continue; + auto row = *( m_model->append() ); + row[m_columns_database.m_col_id] = std::stoi(x["ID"]); + row[m_columns_database.m_col_show] = x["SHOW"]; + row[m_columns_database.m_col_path] = x["PATH"]; + row[m_columns_database.m_col_lang] = x["LANGUAGE"]; + row[m_columns_database.m_col_url] = x["URL"]; + } + + m_tree_database->append_column_editable( "Show", m_columns_database.m_col_show ); + m_tree_database->append_column_editable( "Path", m_columns_database.m_col_path ); + m_tree_database->append_column_editable( "Language", m_columns_database.m_col_lang ); + m_tree_database->append_column_editable( "URL", m_columns_database.m_col_url ); + + m_button_save->signal_clicked().connect( + sigc::mem_fun( *this, &DatabaseWindow::save ) ); + m_button_remove->signal_clicked().connect( + sigc::mem_fun( *this, &DatabaseWindow::remove ) ); + m_model->signal_row_changed().connect( + sigc::mem_fun( *this, &DatabaseWindow::changed ) ); + + // show everything + layout->show(); + layout->show_all_children(); +} + +void DatabaseWindow::save() { + for( auto &x : m_model->children() ) { + auto index = static_cast ( x[m_columns_database.m_col_id] ); + if( changed_rows.find(index) != changed_rows.end() ) { + auto index = static_cast< size_t > ( x[m_columns_database.m_col_id] ); + auto show = static_cast< std::string > ( x[m_columns_database.m_col_show] ); + auto path = static_cast< std::string > ( x[m_columns_database.m_col_path] ); + auto lang = static_cast< std::string > ( x[m_columns_database.m_col_lang] ); + auto url = static_cast< std::string > ( x[m_columns_database.m_col_url] ); + std::cout << index << " " << show << " " << path << " " << lang << " " << url << std::endl; + changeDB( index, path, lang, url, c ); + refreshSingleDB( index, linux, c ); + } + } + cleanDB(); + changed_rows.clear(); +} + +void DatabaseWindow::remove() { + auto selected = m_tree_database->get_selection()->get_selected(); + std::cout << static_cast< size_t > ( (*selected)[m_columns_database.m_col_id] ) << std::endl; + removeFromDB( static_cast< std::string > ( (*selected)[m_columns_database.m_col_path] ) ); + m_model->erase(selected); +} + +void DatabaseWindow::changed( const Gtk::TreeModel::Path &/*UNUSED*/, const Gtk::TreeModel::iterator &row ) { + changed_rows.insert( static_cast< size_t > ( (*row)[m_columns_database.m_col_id] ) ); +} diff --git a/databasewindow.hpp b/databasewindow.hpp new file mode 100644 index 0000000..4f5b4e2 --- /dev/null +++ b/databasewindow.hpp @@ -0,0 +1,62 @@ +#ifndef GTKMM_DATABASE_WINDOW +#define GTKMM_DATABASE_WINDOW + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "network.hpp" +#include "seasonwindow.hpp" + +class DatabaseWindow : public Gtk::Window { +public: + DatabaseWindow() = delete; + DatabaseWindow( bool _linux, Curl &_c); + virtual ~DatabaseWindow(); + +private: + void save(); + void remove(); + void changed( const Gtk::TreeModel::Path &/*UNUSED*/, const Gtk::TreeModel::iterator &row ); + + std::unordered_set< size_t > changed_rows; + +protected: + Gtk::Button *m_button_save = new Gtk::Button(); + Gtk::Button *m_button_remove = new Gtk::Button(); + + Gtk::TreeView *m_tree_database = new Gtk::TreeView(); + Gtk::ScrolledWindow *m_scrolled_window = new Gtk::ScrolledWindow(); + + class DatabaseColumns : public Gtk::TreeModel::ColumnRecord { + public: + DatabaseColumns() { + add(m_col_id); + add(m_col_show); + add(m_col_path); + add(m_col_lang); + add(m_col_url); + } + Gtk::TreeModelColumn< size_t > m_col_id; + Gtk::TreeModelColumn< std::string > m_col_show; + Gtk::TreeModelColumn< std::string > m_col_path; + Gtk::TreeModelColumn< std::string > m_col_lang; + Gtk::TreeModelColumn< std::string > m_col_url; + }; + + DatabaseColumns m_columns_database; + Glib::RefPtr< Gtk::ListStore > m_model; + + Curl &c; + bool linux; +}; + +#endif // GTKMM_MAIN_WINDOW diff --git a/filesystem.hpp b/filesystem.hpp index 177156e..a12aaea 100644 --- a/filesystem.hpp +++ b/filesystem.hpp @@ -32,13 +32,10 @@ using char_t = char; 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 ); +string canonical( const string &path ); class Directory { public: diff --git a/functions.cpp b/functions.cpp index 81a6892..1a282a3 100644 --- a/functions.cpp +++ b/functions.cpp @@ -202,8 +202,47 @@ void iterateFS( std::map< int, std::set< string > > &seasons, } } -#ifndef GUI -// following functions are only needed for CLI version +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< 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( 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; + } + } + 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; +} // find all files for provided season in `path` and store it in `files` void findSeason( std::set< string > &files, int season, const string &path ) { @@ -246,47 +285,8 @@ void findSeasons( std::map< int, std::set< string > > &seasons, } } -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< 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( 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; - } - } - 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; -} +#ifndef GUI +// following functions are only needed for CLI version void printHelp() { cout << "Usage:" << std::endl; @@ -579,8 +579,6 @@ string compilePattern( const string &pattern, int season, int episode, return output; } -#ifndef GUI - #ifdef WIN32 std::wstring getDBName() { @@ -609,7 +607,7 @@ string sanitize( const string &str ) { return ret; } -void prepareDB() { +void prepareDB( const std::string &_pattern ) { SQLite::Database db{}; try { db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE ); @@ -624,18 +622,34 @@ void prepareDB() { db.exec( "CREATE TABLE IF NOT EXISTS EPISODES (SHOWID INTEGER NOT NULL," "PATH TEXT NOT NULL UNIQUE, FOREIGN KEY(SHOWID) " "REFERENCES SHOWS(ID));" ); - cout << "Insert name pattern for database:" << std::endl; - string pattern; - std::getline( cin, pattern ); + const string *pattern; + if( _pattern.empty() ) { + cout << "Insert name pattern for database:" << std::endl; + auto *p = new string; + std::getline( cin, *p ); + pattern = p; + } else { + pattern = &_pattern; + } db.exec( TEXT( "INSERT INTO SHOWS ( URL, SHOW, PATH, LANGUAGE ) " - "VALUES ( 'pattern', 'pattern', '" ) + - sanitize( pattern ) + TEXT( "', 'pattern' );" ) ); + "VALUES ( 'pattern', 'pattern', '" ) + sanitize( *pattern ) + + TEXT( "', 'pattern' );" ) ); + if( pattern != &_pattern ) { + delete pattern; + } } +#ifndef GUI void addToDB( string &show, const string &path, const string &language, bool linux, Curl &c ) { if ( !FSLib::exists( getDBName() ) ) prepareDB(); +#else +void addToDB( string &show, const string &path, const string &language, + const string &url, const string &pattern, bool linux, Curl &c ) { + if ( !FSLib::exists( getDBName() ) ) + prepareDB( pattern ); +#endif SQLite::Database db{}; auto absolute = FSLib::canonical( path ); try { @@ -644,17 +658,21 @@ void addToDB( string &show, const string &path, const string &language, cerr << "Can't open database, make sure it exists" << std::endl; throw e; } +#ifndef GUI auto url = getDefUrl( show, language, c ); +#endif string show_id{}; - string pattern{}; db.exec( TEXT( "INSERT OR IGNORE INTO SHOWS ( URL, SHOW, PATH, LANGUAGE ) " "VALUES ( '" ) + sanitize( url ) + TEXT( "', '" ) + sanitize( show ) + TEXT( "', '" ) + sanitize( absolute ) + TEXT( "', '" ) + sanitize( language ) + TEXT( "' );" ) ); show_id = std::to_string( db.lastRowID() ); +#ifndef GUI + string pattern{}; db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ), pattern ); +#endif std::map< int, std::set< string > > seasons; // get all seasons and episodes @@ -665,14 +683,20 @@ void addToDB( string &show, const string &path, const string &language, #ifdef WIN32 cout << std::endl; #endif +#ifndef GUI ProgressBar::print( 0 ); +#endif for ( const auto &x : seasons ) { singleSeason( absolute, show, x.first, url, language, pattern, linux, true, c, &x.second, false ); i++; +#ifndef GUI ProgressBar::print( ( i * 100 ) / size ); +#endif } +#ifndef GUI cout << std::endl; +#endif seasons.clear(); iterateFS( seasons, absolute ); size = seasons.size(); @@ -682,7 +706,9 @@ void addToDB( string &show, const string &path, const string &language, #ifdef WIN32 cout << std::endl; #endif +#ifndef GUI ProgressBar::print( 0 ); +#endif for ( auto &season : seasons ) { for ( auto &episode : season.second ) { db.exec( TEXT( "INSERT OR IGNORE INTO EPISODES ( SHOWID, PATH ) " @@ -691,9 +717,13 @@ void addToDB( string &show, const string &path, const string &language, TEXT( "' );" ) ); } i++; +#ifndef GUI ProgressBar::print( ( i * 100 ) / size ); +#endif } +#ifndef GUI cout << std::endl; +#endif } void cleanUpLine() { @@ -756,11 +786,15 @@ void refreshDB( bool linux, Curl &c ) { #ifdef WIN32 cout << std::endl; #endif +#ifndef GUI ProgressBar::print( 0 ); +#endif std::map< int, std::set< string > > seasons; // get all season number from this directory and subdirectories iterateFS( seasons, show[TEXT( "PATH" )] ); +#ifndef GUI auto size = seasons.size(); +#endif size_t i{}; for ( const auto &x : seasons ) { singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], @@ -768,10 +802,14 @@ void refreshDB( bool linux, Curl &c ) { show[TEXT( "LANGUAGE" )], pattern, linux, true, c, &x.second, false ); i++; +#ifndef GUI ProgressBar::print( ( i * 100 ) / size ); +#endif } +#ifndef GUI ProgressBar::print( 100 ); cout << std::endl; +#endif seasons.clear(); iterateFS( seasons, show[TEXT( "PATH" )] ); for ( auto &season : seasons ) { @@ -831,9 +869,13 @@ void updateDB( bool linux, Curl &c ) { } } } +#ifndef GUI ProgressBar::print( 0 ); +#endif if ( !new_eps.empty() ) { +#ifndef GUI auto size = new_eps.size(); +#endif size_t i{}; for ( const auto &x : new_eps ) { singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], @@ -841,7 +883,9 @@ void updateDB( bool linux, Curl &c ) { show[TEXT( "LANGUAGE" )], pattern, linux, true, c, &x.second, false ); i++; +#ifndef GUI ProgressBar::print( ( i * 100 ) / size ); +#endif } seasons.clear(); iterateFS( seasons, show[TEXT( "PATH" )] ); @@ -854,8 +898,10 @@ void updateDB( bool linux, Curl &c ) { } } } +#ifndef GUI ProgressBar::print( 100 ); cout << std::endl; +#endif } } @@ -889,7 +935,7 @@ void cleanDB() { pattern ); for ( auto &show : shows ) { - if ( !FSLib::exists( show[TEXT( "PATH" )] ) ) { + if ( FSLib::exists( show[TEXT( "PATH" )] ) ) { continue; } std::unordered_set< string > episodes; @@ -930,4 +976,115 @@ void removeFromDB( const string &path ) { db.exec( TEXT( "DELETE FROM SHOWS WHERE ID == " ) + show_id + TEXT( ";" ) ); } + +std::vector< std::unordered_map< std::string, std::string > > dbGetShows() { + SQLite::Database db{}; + try { + db.open( getDBName(), SQLite::OPEN_READWRITE ); + } catch ( std::exception &e ) { + cerr << "Can't open database, make sure it exists" << std::endl; + throw e; + } + std::vector< std::unordered_map< std::string, std::string > > ret; + db.exec( TEXT( "SELECT * FROM SHOWS;"), ret ); + return ret; +} + +void changeDB( size_t index, const string &path, const string &language, + const string &url, Curl &c ) { + SQLite::Database db{}; + auto absolute = FSLib::canonical( path ); + try { + db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE ); + } catch ( std::exception &e ) { + cerr << "Can't open database, make sure it exists" << std::endl; + throw e; + } +#ifndef WIN32 + string show_id = std::to_string(index); +#else + string show_id = std::to_wstring(index); #endif +#ifdef _WIN32 + string source_code = utf8_to_wstring( + c.execute( url ); +#else + string source_code = + c.execute( url ); +#endif + auto pos = source_code.find( "series_title" ); + pos = source_code.find( ' ', pos ); + pos = source_code.find_first_not_of( " ", pos ); + auto end = source_code.find( '<', pos ); + end--; + while( source_code[end] == ' ' ) + end--; + end++; + auto real_show = source_code.substr(pos, end - pos); + db.exec( TEXT( "UPDATE SHOWS SET URL = '" ) + sanitize( url ) + TEXT("', SHOW = '") + sanitize(real_show) + TEXT("', PATH = '") + sanitize(absolute) + TEXT("', LANGUAGE = '") + + sanitize( language ) + TEXT( "' WHERE ID == " + show_id + ";" ) ); +} + +void refreshSingleDB( const size_t &index, bool linux, Curl &c ) { + std::vector< std::unordered_map< string, string > > shows; + string show_id = std::to_string( index ); + SQLite::Database db{}; + try { + db.open( getDBName(), SQLite::OPEN_READWRITE ); + } catch ( std::exception &e ) { + cerr << "Can't open database, make sure it exists" << std::endl; + throw e; + } + db.exec( "DELETE FROM EPISODES WHERE SHOWID == " + show_id + ";" ); + db.exec( TEXT( "SELECT URL, SHOW, PATH, LANGUAGE FROM SHOWS WHERE ID == " + show_id + ";" ), shows ); + + std::unordered_map< string, string > &show = shows[0]; + + string pattern{}; + db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ), + pattern ); + + cout << "Refreshing " << show[TEXT( "SHOW" )] << std::endl << std::endl << std::endl; +#ifdef WIN32 + cout << std::endl; +#endif + if ( FSLib::exists( show[TEXT( "PATH" )] ) ) { +#ifdef WIN32 + cout << std::endl; +#endif +#ifndef GUI + ProgressBar::print( 0 ); +#endif + std::map< int, std::set< string > > seasons; + // get all season number from this directory and subdirectories + iterateFS( seasons, show[TEXT( "PATH" )] ); +#ifndef GUI + auto size = seasons.size(); +#endif + size_t i{}; + for ( const auto &x : seasons ) { + singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )], + x.first, show[TEXT( "URL" )], + show[TEXT( "LANGUAGE" )], pattern, linux, true, c, + &x.second, false ); + i++; +#ifndef GUI + ProgressBar::print( ( i * 100 ) / size ); +#endif + } +#ifndef GUI + ProgressBar::print( 100 ); + cout << std::endl; +#endif + seasons.clear(); + iterateFS( seasons, show[TEXT( "PATH" )] ); + for ( auto &season : seasons ) { + for ( auto &episode : season.second ) { + db.exec( TEXT( "INSERT INTO EPISODES ( SHOWID, PATH ) " + "VALUES ( " ) + + show_id + TEXT( ", '" ) + + sanitize( episode ) + TEXT( "' );" ) ); + } + } + } +} diff --git a/functions.hpp b/functions.hpp index 84c31d9..9436e18 100644 --- a/functions.hpp +++ b/functions.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef GUI @@ -28,13 +29,13 @@ using char_t = char; #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 ); + +#ifndef GUI +// CLI functions void printHelp(); bool searchSpecificSeason( const char_t *const path, const string &number ); @@ -57,16 +58,23 @@ getPossibleShows( string show, const string &language, Curl &c ); string userHome(); +void prepareDB( const std::string &_pattern = "" ); #ifndef GUI -void prepareDB(); void addToDB( string &show, const string &path, const string &language, bool linux, Curl &c ); +#else +void addToDB( string &show, const string &path, const string &language, + const string &url, const string &pattern, bool linux, Curl &c ); +std::vector< std::unordered_map< std::string, std::string > > dbGetShows(); +#endif void removeFromDB( const string &path ); void changeDBPattern( const string &pattern ); void refreshDB( bool linux, Curl &c ); void updateDB( bool linux, Curl &c ); void cleanDB(); -#endif +void changeDB( size_t index, const string &path, const string &language, + const string &url, Curl &c); +void refreshSingleDB( const size_t &index, bool linux, Curl &c ); void iterateFS( std::map< int, std::set< string > > &seasons, const string &path ); diff --git a/mainwindow.cpp b/mainwindow.cpp index 5384c86..bf388d2 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -2,12 +2,14 @@ #include #include #include +#include #include #include #include "filesystem.hpp" #include "functions.hpp" #include "mainwindow.hpp" +#include "databasewindow.hpp" #include "tv_rename.hpp" constexpr std::array< const char *, 46 > languages{ @@ -32,7 +34,7 @@ void MainWindow::chooseFile() { 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: @@ -69,7 +71,7 @@ void MainWindow::patternHelp() { void MainWindow::process() { // check required field is filled out - if ( m_entry_show.get_text().empty() ) { + if ( m_entry_show->get_text().empty() ) { Gtk::MessageDialog dialog( *this, "Show field is empty" ); dialog.run(); return; @@ -77,11 +79,11 @@ void MainWindow::process() { // language code language_code = - ( *m_combo_language.get_active() )[m_columns_language.m_col_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 ); + std::string( m_entry_show->get_text() ), language_code, c ); // if no possible shows were found, tell the user if ( possible_shows.size() == 0 ) { @@ -92,20 +94,21 @@ void MainWindow::process() { } // show widgets - m_label_possible.show(); - m_button_get_names.show(); - m_combo_possible.show(); + m_label_possible->show(); + m_button_rename->show(); + m_button_db_add->show(); + m_combo_possible->show(); // fill up combo box with results from thetvdb auto model = Gtk::ListStore::create( m_columns_url ); - m_combo_possible.set_model( model ); + m_combo_possible->set_model( model ); 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() ); @@ -116,19 +119,19 @@ void MainWindow::process() { void MainWindow::getNames() { // check required field is filled out - if ( m_entry_dir.get_text().empty() ) { + 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() ) ) { + if ( !FSLib::isDirectory( m_entry_dir->get_text() ) ) { Gtk::MessageDialog dialog( *this, "Directory doesn't exist" ); dialog.run(); return; } - path = m_entry_dir.get_text(); + path = m_entry_dir->get_text(); selected.clear(); files.clear(); @@ -144,7 +147,7 @@ void MainWindow::getNames() { // create a window with possible seasons to rename // store selected seasons in `selected` - sw = new SeasonWindow( options, selected ); + sw.reset( new SeasonWindow( options, selected ) ); sw->signal_hide().connect( sigc::mem_fun( *this, &MainWindow::finishedSelection ) ); @@ -173,9 +176,8 @@ void renameFiles( void MainWindow::finishedSelection() { // remove created SeasonWindow and delete it from memory app->remove_window( *sw ); - delete sw; - auto iter = m_combo_possible.get_active(); + auto iter = m_combo_possible->get_active(); // debug output std::cout << ( *iter )[m_columns_url.m_col_show] << " " << language_code @@ -190,7 +192,7 @@ void MainWindow::finishedSelection() { std::cout << "https://www.thetvdb.com" << url << std::endl; - std::string input_pattern = m_entry_pattern.get_text(); + std::string input_pattern = m_entry_pattern->get_text(); // store pattern to cache if it's different from default if ( input_pattern != default_pattern ) { @@ -206,29 +208,36 @@ void MainWindow::finishedSelection() { 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] ); + !m_check_linux->get_active(), c, files[x] ); if ( renamed_files.empty() ) continue; // if trust checkbox is ticked, rename files - if ( m_check_trust.get_active() ) { + 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 ); - auto content = dialog.get_content_area(); + std::unique_ptr dialog( new Gtk::Dialog( "Rename confirmation", *this ) ); + dialog->set_default_size( 550, 350 ); + dialog->set_resizable( false ); + 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 ); - tx.show(); + std::unique_ptr sw( new Gtk::ScrolledWindow ); + std::unique_ptr tx( new Gtk::TextView ); + content->pack_start( *sw ); + sw->add( *tx ); + tx->set_editable( false ); + tx->set_cursor_visible( false ); - auto buff = tx.get_buffer(); + dialog->add_button( "_No", Gtk::RESPONSE_CANCEL ); + dialog->add_button( "_Yes", Gtk::RESPONSE_OK ); + sw->show(); + 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( " --> " ); @@ -241,7 +250,7 @@ void MainWindow::finishedSelection() { buff->insert_at_cursor( renamed_files[i].second.second.c_str() ); } - auto response = dialog.run(); + auto response = dialog->run(); // if user clicked "Yes" in dialog, rename files switch ( response ) { @@ -253,11 +262,29 @@ void MainWindow::finishedSelection() { } } +MainWindow::~MainWindow() { + auto children = get_children(); + size_t max = children.size(); + size_t index{}; + while( index < max ) { + if( auto *p = dynamic_cast(children[index]) ) { + auto temp = p->get_children(); + children.insert( children.end(), temp.begin(), temp.end() ); + max = children.size(); + } + index++; + } + std::cout << children.size() << std::endl; + for( int i = max - 1; i >= 0; i-- ) { + delete children[i]; + } +} + MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ) : app( ptr ) { set_title( "TV Rename" ); - set_default_size( 400, 310 ); + set_default_size( 400, 345 ); set_resizable( false ); { @@ -270,68 +297,136 @@ MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ) } } - add( m_layout ); + auto *box = new Gtk::Box(Gtk::ORIENTATION_VERTICAL); + auto *menu = new Gtk::MenuBar(); + auto *layout = new Gtk::Layout(); + + add( *box ); + box->pack_start(*menu, false, false); + box->pack_start(*layout, true, true); + + auto *item = new Gtk::MenuItem(); + auto *submenu = new Gtk::Menu(); + + menu->append(*item); + + // File menu + item->set_label("File"); + item->set_submenu(*submenu); + + // Exit item for File menu + item = new Gtk::MenuItem(); + + item->set_label("Exit"); + item->signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::quit ) ); + + + submenu->append(*item); + + // Database menu + item = new Gtk::MenuItem(); + submenu = new Gtk::Menu(); + + item->set_label("Database"); + item->set_submenu(*submenu); + menu->append(*item); + + // Update database + item = new Gtk::MenuItem(); + item->set_label("Update database"); + item->signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::dbUpdate ) ); + + submenu->append(*item); + + // Refresh database + item = new Gtk::MenuItem(); + item->set_label("Refresh database"); + item->signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::dbRefresh ) ); + + submenu->append(*item); + + // Clean database + item = new Gtk::MenuItem(); + item->set_label("Clean database"); + item->signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::dbClean ) ); + + submenu->append(*item); + + // Manage database + item = new Gtk::MenuItem(); + item->set_label("Manage database"); + item->signal_activate().connect( + sigc::mem_fun( *this, &MainWindow::dbManage ) ); + + submenu->append(*item); // 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 ); - m_layout.put( m_combo_language, 190, 25 ); - m_layout.put( m_label_dir, 5, 60 ); - m_layout.put( m_entry_dir, 5, 80 ); - m_layout.put( m_button_dir, 190, 80 ); - m_layout.put( m_label_pattern, 5, 115 ); - m_layout.put( m_entry_pattern, 5, 135 ); - m_layout.put( m_button_pattern, 190, 135 ); - m_layout.put( m_check_linux, 95, 169 ); - m_layout.put( m_button_process, 5, 173 ); - m_layout.put( m_check_trust, 95, 187 ); - m_layout.put( m_label_possible, 5, 210 ); - m_layout.put( m_combo_possible, 5, 230 ); - m_layout.put( m_button_get_names, 5, 265 ); - m_layout.put( m_button_quit, 315, 275 ); + layout->put( *m_label_show, 5, 5 ); + layout->put( *m_label_language, 190, 5 ); + layout->put( *m_entry_show, 5, 25 ); + layout->put( *m_combo_language, 190, 25 ); + layout->put( *m_label_dir, 5, 60 ); + layout->put( *m_entry_dir, 5, 80 ); + layout->put( *m_button_dir, 190, 80 ); + layout->put( *m_label_pattern, 5, 115 ); + layout->put( *m_entry_pattern, 5, 135 ); + layout->put( *m_button_pattern, 190, 135 ); + layout->put( *m_check_linux, 95, 169 ); + layout->put( *m_button_process, 5, 173 ); + layout->put( *m_check_trust, 95, 187 ); + layout->put( *m_label_possible, 5, 210 ); + layout->put( *m_combo_possible, 5, 230 ); + layout->put( *m_button_rename, 5, 270 ); + layout->put( *m_button_db_add, 95, 270 ); + layout->put( *m_button_quit, 315, 280 ); // set button texts - m_button_process.set_label( "Process" ); - m_button_get_names.set_label( "Get names" ); - m_button_quit.set_label( "Quit" ); - m_button_dir.set_label( "Choose directory" ); - m_button_pattern.set_label( "Pattern help" ); - m_check_linux.set_label( "Replace windows-illegal characters" ); - m_check_trust.set_label( "Don't ask for rename confirmation" ); + m_button_process->set_label( "Process" ); + m_button_rename->set_label( "Rename" ); + m_button_db_add->set_label( "Add to database" ); + m_button_quit->set_label( "Quit" ); + m_button_dir->set_label( "Choose directory" ); + m_button_pattern->set_label( "Pattern help" ); + m_check_linux->set_label( "Replace windows-illegal characters" ); + m_check_trust->set_label( "Don't ask for rename confirmation" ); // set label texts - m_label_show.set_label( "Show:" ); - m_label_language.set_label( "Language:" ); - m_label_possible.set_label( "Possible shows:" ); - m_label_dir.set_label( "Directory:" ); - m_label_pattern.set_label( "Pattern:" ); + m_label_show->set_label( "Show:" ); + m_label_language->set_label( "Language:" ); + m_label_possible->set_label( "Possible shows:" ); + m_label_dir->set_label( "Directory:" ); + m_label_pattern->set_label( "Pattern:" ); // set dimensions - m_combo_language.set_size_request( 120 ); - m_combo_possible.set_size_request( 200 ); + m_combo_language->set_size_request( 120 ); + 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_show->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_get_names.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_rename->set_size_request( 80, 30 ); + m_button_db_add->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 ); + 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 ); + m_combo_language->set_active( row ); for ( size_t i = 2; i < languages.size(); i += 2 ) { row = *( model->append() ); @@ -341,40 +436,98 @@ MainWindow::MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ) } // 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( + m_button_dir->signal_clicked().connect( sigc::mem_fun( *this, &MainWindow::chooseFile ) ); - m_button_quit.signal_clicked().connect( + m_button_quit->signal_clicked().connect( sigc::mem_fun( *this, &MainWindow::quit ) ); - m_button_process.signal_clicked().connect( + m_button_process->signal_clicked().connect( sigc::mem_fun( *this, &MainWindow::process ) ); - m_button_get_names.signal_clicked().connect( + m_button_rename->signal_clicked().connect( sigc::mem_fun( *this, &MainWindow::getNames ) ); - m_entry_show.signal_activate().connect( + m_button_db_add->signal_clicked().connect( + sigc::mem_fun( *this, &MainWindow::dbAdd ) ); + m_entry_show->signal_activate().connect( sigc::mem_fun( *this, &MainWindow::process ) ); - m_entry_dir.signal_activate().connect( + m_entry_dir->signal_activate().connect( sigc::mem_fun( *this, &MainWindow::process ) ); - m_button_pattern.signal_clicked().connect( + 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(); - m_label_show.show(); - m_label_language.show(); - m_entry_show.show(); - m_entry_dir.show(); - m_combo_language.show(); - m_button_process.show(); - m_button_quit.show(); - m_button_dir.show(); - m_check_linux.show(); - 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(); + box->show(); + menu->show(); + menu->show_all_children(); + layout->show(); + m_label_show->show(); + m_label_language->show(); + m_entry_show->show(); + m_entry_dir->show(); + m_combo_language->show(); + m_button_process->show(); + m_button_quit->show(); + m_button_dir->show(); + m_check_linux->show(); + 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(); +} + +void MainWindow::dbUpdate() { + updateDB( !m_check_linux->get_active(), c ); +} + +void MainWindow::dbClean() { + cleanDB(); +} + +void MainWindow::dbRefresh() { + refreshDB( !m_check_linux->get_active(), c ); +} + +void MainWindow::dbAdd() { + // 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" ); + dialog.run(); + return; + } + + auto iter = m_combo_possible->get_active(); + std::string input_pattern = m_entry_pattern->get_text(); + + std::string show = static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_show] ); + std::string language_code = static_cast< Glib::ustring >( ( *m_combo_language->get_active() )[m_columns_language.m_col_code] ); + std::string url = "https://www.thetvdb.com" + static_cast< Glib::ustring >( ( *iter )[m_columns_url.m_col_url] ); + + std::cout << show << " " << language_code << " " << url << std::endl; + + addToDB( show, m_entry_dir->get_text(), language_code, url, + m_entry_pattern->get_text(), !m_check_linux->get_active(), c ); +} + +void MainWindow::dbManage() { + auto *dbWindow = new DatabaseWindow( !m_check_linux->get_active(), c ); + app->add_window( *dbWindow ); + auto app_ptr = app; + + dbWindow->signal_hide().connect( + [dbWindow, app_ptr](){ + app_ptr->remove_window( *dbWindow ); + delete dbWindow; + }); + + dbWindow->show(); } diff --git a/mainwindow.hpp b/mainwindow.hpp index 92e99c3..712f10d 100644 --- a/mainwindow.hpp +++ b/mainwindow.hpp @@ -1,14 +1,19 @@ #ifndef GTKMM_MAIN_WINDOW #define GTKMM_MAIN_WINDOW +#include #include #include #include +#include #include #include #include +#include +#include #include #include +#include #include "network.hpp" #include "seasonwindow.hpp" @@ -16,7 +21,7 @@ class MainWindow : public Gtk::Window { public: MainWindow( const Glib::RefPtr< Gtk::Application > &ptr ); - virtual ~MainWindow() = default; + virtual ~MainWindow(); private: void quit(); @@ -25,31 +30,35 @@ private: void finishedSelection(); void chooseFile(); void patternHelp(); + void dbUpdate(); + void dbClean(); + void dbRefresh(); + void dbAdd(); + void dbManage(); protected: - Gtk::Button m_button_dir; - Gtk::Button m_button_get_names; - Gtk::Button m_button_quit; - Gtk::Button m_button_process; - Gtk::Button m_button_pattern; + Gtk::Button *m_button_dir = new Gtk::Button(); + Gtk::Button *m_button_rename = new Gtk::Button(); + Gtk::Button *m_button_db_add = new Gtk::Button(); + Gtk::Button *m_button_quit = new Gtk::Button(); + Gtk::Button *m_button_process = new Gtk::Button(); + Gtk::Button *m_button_pattern = new Gtk::Button(); - Gtk::CheckButton m_check_linux; - Gtk::CheckButton m_check_trust; + Gtk::CheckButton *m_check_linux = new Gtk::CheckButton(); + Gtk::CheckButton *m_check_trust = new Gtk::CheckButton(); - Gtk::ComboBox m_combo_language; - Gtk::ComboBox m_combo_possible; + Gtk::ComboBox *m_combo_language = new Gtk::ComboBox(); + Gtk::ComboBox *m_combo_possible = new Gtk::ComboBox(); - Gtk::Entry m_entry_show; - Gtk::Entry m_entry_dir; - Gtk::Entry m_entry_pattern; + Gtk::Entry *m_entry_show = new Gtk::Entry(); + Gtk::Entry *m_entry_dir = new Gtk::Entry(); + Gtk::Entry *m_entry_pattern = new Gtk::Entry(); - Gtk::Label m_label_language; - Gtk::Label m_label_possible; - Gtk::Label m_label_show; - Gtk::Label m_label_dir; - Gtk::Label m_label_pattern; - - Gtk::Layout m_layout; + Gtk::Label *m_label_language = new Gtk::Label(); + Gtk::Label *m_label_possible = new Gtk::Label(); + Gtk::Label *m_label_show = new Gtk::Label(); + Gtk::Label *m_label_dir = new Gtk::Label(); + Gtk::Label *m_label_pattern = new Gtk::Label(); Curl c; @@ -59,7 +68,6 @@ protected: add( m_col_code ); add( m_col_language ); } - Gtk::TreeModelColumn< std::string > m_col_code; Gtk::TreeModelColumn< std::string > m_col_language; }; @@ -79,7 +87,7 @@ protected: Glib::RefPtr< Gtk::Application > app; - SeasonWindow *sw; + std::unique_ptr sw{nullptr}; std::vector< int > selected; std::map< int, std::set< std::string > > files; std::string path; diff --git a/tv_rename.cpp b/tv_rename.cpp index 3003083..0863884 100644 --- a/tv_rename.cpp +++ b/tv_rename.cpp @@ -11,9 +11,10 @@ #endif +#include "filesystem.hpp" + #ifndef GUI -#include "filesystem.hpp" #include #include #include @@ -203,8 +204,6 @@ getRenamedFiles( const string &show, int season, string url, return renamed_files; } -#ifndef GUI - 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, @@ -253,6 +252,8 @@ void singleSeason( const string &path, string &show, int season, string url, } } +#ifndef GUI + void multipleSeasons( const string &path, string &show, const std::map< int, std::set< string > > &seasons, const string &language, const string &pattern, diff --git a/tv_rename.hpp b/tv_rename.hpp index 7f1d8eb..616b894 100644 --- a/tv_rename.hpp +++ b/tv_rename.hpp @@ -21,6 +21,12 @@ using char_t = char; #endif +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, + bool print = true ); + #ifdef GUI std::vector< std::pair< string, std::pair< string, string > > > @@ -30,11 +36,6 @@ getRenamedFiles( const string &show, int season, string url, #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, - bool print = true ); void multipleSeasons( const string &path, string &show, const std::set< int > seasons, const string &language, const string &pattern, const bool &linux, diff --git a/unix/filesystem.cpp b/unix/filesystem.cpp index a748830..43df3e0 100644 --- a/unix/filesystem.cpp +++ b/unix/filesystem.cpp @@ -20,8 +20,6 @@ FSLib::Directory::Iterator::~Iterator() { closedir( d ); } -#ifndef GUI // these functions aren't needed in GUI - bool FSLib::exists( const string &path ) { struct stat path_stat; return stat( path.c_str(), &path_stat ) == 0; @@ -43,8 +41,6 @@ string FSLib::canonical( const string &path ) { return canonical_string; } -#endif // ndef GUI - bool FSLib::isDirectory( const string &path ) { struct stat path_stat; diff --git a/windows/filesystem.cpp b/windows/filesystem.cpp index 87f15ca..1c217c1 100644 --- a/windows/filesystem.cpp +++ b/windows/filesystem.cpp @@ -29,8 +29,6 @@ FSLib::Directory::Iterator::~Iterator() { // windows.h FSLib::Directory::Iterator::Iterator( bool ended_ ) : ended( ended_ ) {} -#ifndef GUI // these functions aren't needed in GUI - bool FSLib::exists( const string &path ) { struct _stat path_stat; return _wstat( path.c_str(), &path_stat ) == 0; @@ -62,8 +60,6 @@ string FSLib::canonical( const string &path ) { return canonical_string; } -#endif // ndef GUI - bool FSLib::isDirectory( const string &path ) { struct _stat path_stat;