Initial support of database

This commit is contained in:
zvon 2019-06-04 21:54:00 +02:00
parent d36d7a385d
commit d1e82d4a94
12 changed files with 235532 additions and 57 deletions

View File

@ -38,8 +38,9 @@ uninstall_gui:
rm $(ICONDIR)/scalable/apps/tv_rename.svg rm $(ICONDIR)/scalable/apps/tv_rename.svg
gtk-update-icon-cache -f $(ICONDIR) gtk-update-icon-cache -f $(ICONDIR)
tv_rename: functions.o filesystem_u.o network.o tv_rename.o main.cpp tv_rename: functions.o filesystem_u.o network.o tv_rename.o progress.o main.cpp
$(CXX) $(CFLAGS) -o tv_rename main.cpp tv_rename.o functions.o filesystem_u.o network.o -lcurl $(CXX) $(CFLAGS) -o tv_rename main.cpp tv_rename.o functions.o\
filesystem_u.o network.o progress.o -lcurl -lsqlite3
filesystem_u.o: unix/filesystem.cpp filesystem_u.o: unix/filesystem.cpp
$(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u.o $(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u.o
@ -53,11 +54,18 @@ network.o: network.cpp
tv_rename.o: tv_rename.cpp tv_rename.o: tv_rename.cpp
$(CXX) $(CFLAGS) -c tv_rename.cpp $(CXX) $(CFLAGS) -c tv_rename.cpp
progress.o: progress.cpp
$(CXX) $(CFLAGS) -c progress.cpp
.PHONY: gui .PHONY: gui
gui: tv_rename_gui gui: tv_rename_gui
tv_rename_gui: gui.cpp mainwindow.cpp seasonwindow.cpp network.o functions_gui.o filesystem_u_gui.o tv_rename_gui.o tv_rename_gui: gui.cpp mainwindow.cpp seasonwindow.cpp network.o\
$(CXX) $(CFLAGS) -o tv_rename_gui gui.cpp mainwindow.cpp seasonwindow.cpp network.o functions_gui.o filesystem_u_gui.o tv_rename_gui.o `pkg-config gtkmm-3.0 --cflags --libs` -lcurl -DGUI functions_gui.o filesystem_u_gui.o tv_rename_gui.o
$(CXX) $(CFLAGS) -o tv_rename_gui gui.cpp mainwindow.cpp seasonwindow.cpp\
network.o functions_gui.o filesystem_u_gui.o\
tv_rename_gui.o `pkg-config gtkmm-3.0 --cflags --libs`\
-lcurl -DGUI
filesystem_u_gui.o: unix/filesystem.cpp filesystem_u_gui.o: unix/filesystem.cpp
$(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u_gui.o -DGUI $(CXX) $(CFLAGS) -c unix/filesystem.cpp -o filesystem_u_gui.o -DGUI
@ -72,14 +80,22 @@ tv_rename_gui.o: tv_rename.cpp
.PHONY: windows .PHONY: windows
windows: tv_rename.exe windows: tv_rename.exe
tv_rename.exe: tv_rename.cpp functions.cpp windows/filesystem.cpp network.cpp main.cpp tv_rename.exe: tv_rename.cpp functions.cpp windows/filesystem.cpp network.cpp\
$(CXX) -MD -EHsc -Fe"tv_rename" tv_rename.cpp windows/filesystem.cpp functions.cpp network.cpp main.cpp -D_WIN32 -DUNICODE -link wininet.lib shlwapi.lib progress.cpp sqlite3.c main.cpp
$(CXX) -MD -EHsc -Fe"tv_rename" tv_rename.cpp windows/filesystem.cpp\
functions.cpp network.cpp progress.cpp sqlite3.c main.cpp\
-D_WIN32 -DUNICODE -link wininet.lib shlwapi.lib ole32.lib\
shell32.lib user32.lib
.PHONY: windows_gui .PHONY: windows_gui
windows_gui: tv_rename_gui.exe windows_gui: tv_rename_gui.exe
tv_rename_gui.exe: tv_rename_gui.res tv_rename_gui.cpp tv_rename.cpp windows/filesystem.cpp functions.cpp network.cpp tv_rename_gui.exe: tv_rename_gui.res tv_rename_gui.cpp tv_rename.cpp\
$(CXX) -MD -EHsc -Fe"tv_rename_gui" tv_rename_gui.cpp tv_rename.cpp windows/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 windows/filesystem.cpp functions.cpp network.cpp
$(CXX) -MD -EHsc -Fe"tv_rename_gui" tv_rename_gui.cpp tv_rename.cpp\
windows/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 tv_rename_gui.res: tv_rename_gui.rc
rc tv_rename_gui.rc rc tv_rename_gui.rc

View File

@ -2,34 +2,38 @@
#include <array> #include <array>
#include <cctype> #include <cctype>
#include <iomanip> #include <iomanip>
#include <map>
#include <sstream> #include <sstream>
#include <unordered_set>
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
#include <codecvt> #include <codecvt>
#include <iostream>
#include <shlobj.h> #include <shlobj.h>
#else // UNIX #else // UNIX
#include <pwd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#ifndef GUI #ifndef GUI
#include <iostream> #include <iostream>
#include <stdlib.h> #include <stdlib.h>
#include <vector> #include <vector>
#else // UNIX and GUI
#include <pwd.h>
#include <unistd.h>
#endif // GUI #endif // GUI
#endif // UNIX #endif // UNIX
#include "filesystem.hpp" #include "filesystem.hpp"
#include "functions.hpp" #include "functions.hpp"
#include "progress.hpp"
#include "sqlitepp.hpp"
#include "tv_rename.hpp"
#ifdef _WIN32 #ifdef _WIN32
@ -101,7 +105,7 @@ string encodeUrl( const string &url ) {
encoded << std::hex; encoded << std::hex;
for ( const auto &x : url_c ) { for ( const auto &x : url_c ) {
if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' || if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' ||
x == '_' || x == '.' || x == '~' ) { x == '_' || x == '.' || x == '~' || x == '+' ) {
encoded << x; encoded << x;
continue; continue;
} }
@ -243,6 +247,8 @@ void findSeasons( std::map< int, std::set< string > > &seasons,
} }
string getDefUrl( string &show, const string &language, Curl &c ) { string getDefUrl( string &show, const string &language, Curl &c ) {
std::replace( show.begin(), show.end(), ' ', '+' );
string base_url = TEXT( "https://www.thetvdb.com" ); string base_url = TEXT( "https://www.thetvdb.com" );
#ifdef _WIN32 #ifdef _WIN32
@ -337,6 +343,23 @@ void printHelp() {
cout << " -l, --lang <string> Select which language the episode" cout << " -l, --lang <string> Select which language the episode"
<< " names shoud be in" << std::endl; << " names shoud be in" << std::endl;
cout << " --print-langs Pring available languages" << std::endl; cout << " --print-langs Pring available languages" << std::endl;
cout << std::endl;
cout << "DATABASE OPTIONS" << std::endl;
cout << " --db-add Add path to the database"
<< std::endl;
cout << " --db-refresh Refresh episode names for all paths"
<< " in the database" << std::endl;
cout << " --db-update Check all paths in the database,"
<< std::endl;
cout << " if they contain new files,"
<< " rename them" << std::endl;
cout << " --db-name-pattern <string> Change name pattern used for files"
<< std::endl
<< " managed by database" << std::endl;
cout << " --db-clean Remove deleted files from the"
<< " database" << std::endl;
cout << " --db-remove Remove path from the database"
<< std::endl;
} }
// parse command line argument --seasons (e.g. '1 2 3 4 5') // parse command line argument --seasons (e.g. '1 2 3 4 5')
@ -393,6 +416,7 @@ bool findLanguage( const char_t *language ) {
std::vector< std::pair< string, string > > std::vector< std::pair< string, string > >
getPossibleShows( string show, const string &language, Curl &c ) { getPossibleShows( string show, const string &language, Curl &c ) {
// encode show name so it can be resolved as url // encode show name so it can be resolved as url
std::replace( show.begin(), show.end(), ' ', '+' );
show = encodeUrl( show ); show = encodeUrl( show );
#ifdef _WIN32 #ifdef _WIN32
auto source_code = utf8_to_wstring( auto source_code = utf8_to_wstring(
@ -421,6 +445,8 @@ getPossibleShows( string show, const string &language, Curl &c ) {
return urls; return urls;
} }
#endif // ndef GUI
#ifndef _WIN32 #ifndef _WIN32
// get user's home directory // get user's home directory
@ -456,8 +482,6 @@ string userHome() {
#endif // UNIX #endif // UNIX
#endif // ndef GUI
// create file name based on given pattern // create file name based on given pattern
string compilePattern( const string &pattern, int season, int episode, string compilePattern( const string &pattern, int season, int episode,
const string &filename, const string &episodeName, const string &filename, const string &episodeName,
@ -554,3 +578,358 @@ string compilePattern( const string &pattern, int season, int episode,
return output; return output;
} }
#ifndef GUI
#ifdef WIN32
std::wstring getDBName() {
return userHome() + L"\\tv_rename\\database.db";
}
#else
std::string getDBName() {
return userHome() + "/.cache/tv_rename.db";
}
#endif
string sanitize( const string &str ) {
string ret;
size_t prev_pos{};
size_t pos = str.find_first_of( '\'' );
while ( pos != string::npos ) {
ret += str.substr( prev_pos, pos - prev_pos );
prev_pos = pos + 1;
pos = str.find_first_of( '\'', prev_pos );
ret += TEXT( "\'\'" );
}
ret += str.substr( prev_pos, pos );
return ret;
}
void prepareDB() {
SQLite::Database db{};
try {
db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE );
} catch ( std::exception &e ) {
cerr << "Couldn't create database, make sure you have"
<< " write permission for " << getDBName() << std::endl;
throw e;
}
db.exec( "CREATE TABLE IF NOT EXISTS SHOWS (ID INTEGER NOT NULL "
"PRIMARY KEY AUTOINCREMENT, URL TEXT NOT NULL, SHOW TEXT NOT NULL,"
"PATH TEXT NOT NULL UNIQUE, LANGUAGE TEXT NOT NULL);" );
db.exec( "CREATE TABLE IF NOT EXISTS EPISODES (SHOWID INTEGER NOT NULL,"
"PATH TEXT NOT NULL UNIQUE, FOREIGN KEY(SHOWID) "
"REFERENCES SHOWS(ID));" );
cout << "Insert name pattern for database:" << std::endl;
string pattern;
std::getline( cin, pattern );
db.exec( TEXT( "INSERT INTO SHOWS ( URL, SHOW, PATH, LANGUAGE ) "
"VALUES ( 'pattern', 'pattern', '" ) +
sanitize( pattern ) + TEXT( "', 'pattern' );" ) );
}
void addToDB( string &show, const string &path, const string &language,
bool linux, Curl &c ) {
if ( !FSLib::exists( getDBName() ) )
prepareDB();
SQLite::Database db{};
auto absolute = FSLib::canonical( path );
try {
db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE );
} catch ( std::exception &e ) {
cerr << "Can't open database, make sure it exists" << std::endl;
throw e;
}
auto url = getDefUrl( show, language, c );
string season_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( "' );" ) );
db.exec( TEXT( "SELECT ID FROM SHOWS WHERE PATH == '" ) +
sanitize( absolute ) + TEXT( "';" ),
season_id );
db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ),
pattern );
std::map< int, std::set< string > > seasons;
// get all seasons and episodes
iterateFS( seasons, absolute );
auto size = seasons.size();
size_t i = 0;
cout << "Renaming" << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
ProgressBar::print( 0 );
for ( const auto &x : seasons ) {
singleSeason( absolute, show, x.first, url, language, pattern, linux,
true, c, &x.second, false );
i++;
ProgressBar::print( ( i * 100 ) / size );
}
cout << std::endl;
seasons.clear();
iterateFS( seasons, absolute );
size = seasons.size();
i = 0;
cout << "Adding to database" << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
ProgressBar::print( 0 );
for ( auto &season : seasons ) {
for ( auto &episode : season.second ) {
db.exec( TEXT( "INSERT OR IGNORE INTO EPISODES ( SHOWID, PATH ) "
"VALUES ( " ) +
season_id + TEXT( ", '" ) + sanitize( episode ) +
TEXT( "' );" ) );
}
i++;
ProgressBar::print( ( i * 100 ) / size );
}
cout << std::endl;
}
void cleanUpLine() {
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
int width;
GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi );
width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
static HANDLE h = NULL;
if ( !h )
h = GetStdHandle( STD_OUTPUT_HANDLE );
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo( h, &info );
COORD c = { 0, info.dwCursorPosition.Y - 3 };
SetConsoleCursorPosition( h, c );
#else
struct winsize w;
ioctl( 0, TIOCGWINSZ, &w );
auto width = w.ws_col;
cout << "\x1b[2A";
#endif
cout << string( width, ' ' ) << std::endl << std::endl;
#ifdef WIN32
SetConsoleCursorPosition( h, c );
#else
cout << "\x1b[2A";
#endif
}
void refreshDB( bool linux, Curl &c ) {
std::vector< std::unordered_map< string, string > > shows;
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;" );
db.exec( TEXT( "SELECT ID, URL, SHOW, PATH, LANGUAGE FROM SHOWS WHERE URL "
"!= 'pattern';" ),
shows );
string pattern{};
db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ),
pattern );
cout << "Refreshing database" << std::endl << std::endl << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
for ( auto &show : shows ) {
if ( FSLib::exists( show[TEXT( "PATH" )] ) ) {
cleanUpLine();
cout << "Refreshing " << show[TEXT( "SHOW" )] << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
ProgressBar::print( 0 );
std::map< int, std::set< string > > seasons;
// get all season number from this directory and subdirectories
iterateFS( seasons, show[TEXT( "PATH" )] );
auto size = seasons.size();
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++;
ProgressBar::print( ( i * 100 ) / size );
}
ProgressBar::print( 100 );
cout << std::endl;
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[TEXT( "ID" )] + TEXT( ", '" ) +
sanitize( episode ) + TEXT( "' );" ) );
}
}
}
}
}
void updateDB( bool linux, Curl &c ) {
std::vector< std::unordered_map< string, string > > shows;
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( TEXT( "SELECT ID, URL, SHOW, PATH, LANGUAGE FROM SHOWS WHERE URL "
"!= 'pattern';" ),
shows );
string pattern{};
db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ),
pattern );
cout << "Updating database" << std::endl << std::endl << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
for ( auto &show : shows ) {
if ( !FSLib::exists( show[TEXT( "PATH" )] ) ) {
continue;
}
cleanUpLine();
cout << "Updating " << show[TEXT( "SHOW" )] << std::endl;
#ifdef WIN32
cout << std::endl;
#endif
std::unordered_set< string > episodes;
db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) +
show[TEXT( "ID" )] + TEXT( ";" ),
episodes );
std::map< int, std::set< string > > seasons;
std::map< int, std::set< string > > new_eps;
// get all season number from this directory and subdirectories
iterateFS( seasons, show[TEXT( "PATH" )] );
for ( const auto &x : seasons ) {
for ( const auto &episode : x.second ) {
if ( episodes.find( episode ) == episodes.end() ) {
new_eps[x.first].insert( episode );
}
}
}
ProgressBar::print( 0 );
if ( !new_eps.empty() ) {
auto size = new_eps.size();
size_t i{};
for ( const auto &x : new_eps ) {
singleSeason( show[TEXT( "PATH" )], show[TEXT( "SHOW" )],
x.first, show[TEXT( "URL" )],
show[TEXT( "LANGUAGE" )], pattern, linux, true, c,
&x.second, false );
i++;
ProgressBar::print( ( i * 100 ) / size );
}
seasons.clear();
iterateFS( seasons, show[TEXT( "PATH" )] );
for ( auto &season : seasons ) {
for ( auto &episode : season.second ) {
db.exec( TEXT( "INSERT OR IGNORE INTO EPISODES ( SHOWID, "
"PATH ) VALUES ( " ) +
show[TEXT( "ID" )] + TEXT( ", '" ) +
sanitize( episode ) + TEXT( "' );" ) );
}
}
}
ProgressBar::print( 100 );
cout << std::endl;
}
}
void changeDBPattern( const string &pattern ) {
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( TEXT( "UPDATE SHOWS SET PATH = '" ) + pattern +
TEXT( "' WHERE URL == 'pattern';" ) );
}
void cleanDB() {
std::vector< std::unordered_map< string, string > > shows;
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( TEXT( "SELECT ID, URL, SHOW, PATH, LANGUAGE FROM SHOWS WHERE URL "
"!= 'pattern';" ),
shows );
string pattern{};
db.exec( TEXT( "SELECT PATH FROM SHOWS WHERE URL == 'pattern';" ),
pattern );
for ( auto &show : shows ) {
if ( !FSLib::exists( show[TEXT( "PATH" )] ) ) {
continue;
}
std::unordered_set< string > episodes;
db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) +
show[TEXT( "ID" )] + TEXT( ";" ),
episodes );
for ( const auto &episode : episodes ) {
if ( !FSLib::exists( episode ) ) {
db.exec( TEXT( "DELETE FROM EPISODES WHERE PATH == '" ) +
sanitize( episode ) + TEXT( "';" ) );
}
}
episodes.clear();
db.exec( TEXT( "SELECT PATH FROM EPISODES WHERE SHOWID == " ) +
show[TEXT( "ID" )] + TEXT( ";" ),
episodes );
if ( episodes.empty() ) {
db.exec( TEXT( "DELETE FROM SHOWS WHERE ID == " ) +
show[TEXT( "ID" )] + TEXT( ";" ) );
}
}
}
void removeFromDB( const string &path ) {
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;
}
string show_id{};
db.exec( TEXT( "SELECT ID FROM SHOWS WHERE PATH == '" ) + sanitize( path ) +
TEXT( "';" ),
show_id );
db.exec( TEXT( "DELETE FROM EPISODES WHERE SHOWID == " ) + show_id +
TEXT( ";" ) );
db.exec( TEXT( "DELETE FROM SHOWS WHERE ID == " ) + show_id + TEXT( ";" ) );
}
#endif

View File

@ -52,10 +52,22 @@ string encodeUrl( const string &url );
std::vector< std::pair< string, string > > std::vector< std::pair< string, string > >
getPossibleShows( string show, const string &language, Curl &c ); getPossibleShows( string show, const string &language, Curl &c );
string userHome();
#endif // GUI #endif // GUI
string userHome();
#ifndef GUI
void prepareDB();
void addToDB( string &show, const string &path, const string &language,
bool linux, Curl &c );
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 iterateFS( std::map< int, std::set< string > > &seasons, void iterateFS( std::map< int, std::set< string > > &seasons,
const string &path ); const string &path );

129
main.cpp
View File

@ -39,10 +39,17 @@ using string = std::string;
#endif #endif
constexpr size_t DB_ADD = 0x0001;
constexpr size_t DB_REFRESH = 0x0002;
constexpr size_t DB_UPDATE = 0x0004;
constexpr size_t DB_CLEAN = 0x0008;
constexpr size_t DB_REMOVE = 0x0010;
constexpr size_t DB_PATTERN = 0x0020;
int handleArgument( char_t c, string &show, std::set< int > &seasons_num, int handleArgument( char_t c, string &show, std::set< int > &seasons_num,
bool &change_dir, string &path, bool &trust, bool &linux, bool &change_dir, string &path, bool &trust, bool &linux,
string &language, string &pattern, char_t *optional, string &language, string &pattern, size_t &db_flags,
int &i ) { string &db_pattern, char_t *optional, int &i ) {
switch ( c ) { switch ( c ) {
case 's': case 's':
show = optional; show = optional;
@ -56,7 +63,7 @@ int handleArgument( char_t c, string &show, std::set< int > &seasons_num,
change_dir = false; change_dir = false;
break; break;
case 'p': case 'p':
path = string( optional ); path = optional;
i++; i++;
// if path provided, assume it's correct // if path provided, assume it's correct
change_dir = false; change_dir = false;
@ -87,6 +94,26 @@ int handleArgument( char_t c, string &show, std::set< int > &seasons_num,
pattern = optional; pattern = optional;
i++; i++;
break; break;
case 'a':
db_flags |= DB_ADD;
break;
case 'r':
db_flags |= DB_REFRESH;
break;
case 'u':
db_flags |= DB_UPDATE;
break;
case 'd':
db_pattern = optional;
db_flags |= DB_PATTERN;
i++;
break;
case '2':
db_flags |= DB_CLEAN;
break;
case '3':
db_flags |= DB_REMOVE;
break;
default: default:
return -1; return -1;
} }
@ -118,14 +145,26 @@ string getOptions( const char_t *option ) {
return L"1"; return L"1";
else if ( !wcscmp( option, L"--help" ) ) else if ( !wcscmp( option, L"--help" ) )
return L"h"; return L"h";
else if ( !wcscmp( option, L"--db-add" ) )
return L"a";
else if ( !wcscmp( option, L"--db-refresh" ) )
return L"r";
else if ( !wcscmp( option, L"--db-update" ) )
return L"u";
else if ( !wcscmp( option, L"--db-name-pattern" ) )
return L"d";
else if ( !wcscmp( option, L"--db-clean" ) )
return L"2";
else if ( !wcscmp( option, L"--db-remove" ) )
return L"3";
return L""; return L"";
} }
// there's no getopt for windows, so just use wcscmp // there's no getopt for windows, so just use wcscmp
int parseCommandLine( string &show, std::set< int > &seasons_num, string &path, int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
bool &change_dir, string &language, string &pattern, bool &change_dir, string &language, string &pattern,
bool &linux, bool &trust, const int argc, bool &linux, bool &trust, size_t &db_flags,
char_t **argv ) { string &db_pattern, const int argc, char_t **argv ) {
string options{}; string options{};
char_t *optional; char_t *optional;
for ( auto i = 1; i < argc; i++ ) { for ( auto i = 1; i < argc; i++ ) {
@ -137,9 +176,9 @@ int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
optional = ( i < argc - 1 ) ? argv[i + 1] : nullptr; optional = ( i < argc - 1 ) ? argv[i + 1] : nullptr;
} }
for ( const auto &x : options ) { for ( const auto &x : options ) {
auto res = auto res = handleArgument( x, show, seasons_num, change_dir, path,
handleArgument( x, show, seasons_num, change_dir, path, trust, trust, linux, language, pattern,
linux, language, pattern, optional, i ); db_flags, db_pattern, optional, i );
if ( res != 0 ) if ( res != 0 )
return res; return res;
} }
@ -152,17 +191,24 @@ int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
// parse command line arguments using getopt // parse command line arguments using getopt
int parseCommandLine( string &show, std::set< int > &seasons_num, string &path, int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
bool &change_dir, string &language, string &pattern, bool &change_dir, string &language, string &pattern,
bool &linux, bool &trust, int argc, char **argv ) { bool &linux, bool &trust, size_t &db_flags,
string &db_pattern, int argc, char **argv ) {
static struct option long_options[] = { static struct option long_options[] = {
{ "show", required_argument, 0, 's' }, { "show", required_argument, 0, 's' },
{ "season", required_argument, 0, 'n' }, { "season", required_argument, 0, 'n' },
{ "correct-path", no_argument, 0, 'c' }, { "correct-path", no_argument, 0, 'c' },
{ "trust", no_argument, 0, 't' }, { "trust", no_argument, 0, 't' },
{ "linux", no_argument, 0, 'x' }, { "linux", no_argument, 0, 'x' },
{ "lang", required_argument, 0, 'l' }, { "lang", required_argument, 0, 'l' },
{ "print-langs", no_argument, 0, '0' }, { "print-langs", no_argument, 0, '0' },
{ "name-pattern", required_argument, 0, '1' }, { "name-pattern", required_argument, 0, '1' },
{ "help", no_argument, 0, 'h' }, { "db-add", no_argument, 0, 'a' },
{ "db-refresh", no_argument, 0, 'r' },
{ "db-update", no_argument, 0, 'u' },
{ "db-name-pattern", required_argument, 0, 'd' },
{ "db-clean", no_argument, 0, '2' },
{ "db-remove", required_argument, 0, '3' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 } { 0, 0, 0, 0 }
}; };
@ -170,12 +216,13 @@ int parseCommandLine( string &show, std::set< int > &seasons_num, string &path,
while ( 1 ) { while ( 1 ) {
int option_index{ 0 }; int option_index{ 0 };
auto c = getopt_long( argc, argv, "s:n:cp:txl:01:h", long_options, auto c = getopt_long( argc, argv, "s:n:cp:txl:01:arud23h", long_options,
&option_index ); &option_index );
if ( c == -1 ) if ( c == -1 )
break; break;
auto res = handleArgument( c, show, seasons_num, change_dir, path, auto res = handleArgument( c, show, seasons_num, change_dir, path,
trust, linux, language, pattern, optarg, i ); trust, linux, language, pattern, db_flags,
db_pattern, optarg, i );
if ( res != 0 ) if ( res != 0 )
return res; return res;
} }
@ -207,15 +254,17 @@ int main
bool change_dir{ true }; bool change_dir{ true };
bool linux{ false }; bool linux{ false };
bool trust{ false }; bool trust{ false };
size_t db_flags{};
string path{ TEXT( "." ) }; string path{ TEXT( "." ) };
string language{ TEXT( "en" ) }; string language{ TEXT( "en" ) };
string pattern{ TEXT( "%filename - %epname" ) }; string pattern{ TEXT( "%filename - %epname" ) };
string db_pattern{};
Curl c; Curl c;
{ {
auto tmp = auto tmp = parseCommandLine( show, seasons_num, path, change_dir,
parseCommandLine( show, seasons_num, path, change_dir, language, language, pattern, linux, trust, db_flags,
pattern, linux, trust, argc, argv ); db_pattern, argc, argv );
if ( tmp == -1 ) if ( tmp == -1 )
return 1; return 1;
else if ( tmp == 1 ) else if ( tmp == 1 )
@ -241,6 +290,40 @@ int main
change_dir = true; change_dir = true;
} }
if ( !db_pattern.empty() ) {
changeDBPattern( db_pattern );
}
if ( db_flags & DB_REMOVE && db_flags & DB_ADD ) {
cerr << "You can't remove and add at the same time" << std::endl;
return 1;
}
if ( db_flags & DB_REMOVE ) {
removeFromDB( FSLib::canonical( path ) );
}
if ( db_flags & DB_ADD ) {
addToDB( show, path, language, linux, c );
cout << "Added to database" << std::endl;
}
if ( db_flags & DB_REFRESH ) {
refreshDB( linux, c );
cout << "Refreshed database" << std::endl;
}
if ( db_flags & DB_UPDATE ) {
updateDB( linux, c );
cout << "Updated database" << std::endl;
}
if ( db_flags & DB_CLEAN ) {
cleanDB();
cout << "Database cleaned" << std::endl;
}
// if db operations happened don't continue
if ( db_flags )
return 0;
while ( change_dir ) { while ( change_dir ) {
if ( !FSLib::isDirectory( path ) ) { if ( !FSLib::isDirectory( path ) ) {
cout << "This directory doesn't exist, please insert a correct " cout << "This directory doesn't exist, please insert a correct "

75
progress.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "progress.hpp"
#include <iostream>
#include <stdio.h>
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <winuser.h>
#define cout std::wcout
using string = std::wstring;
#else // UNIX
#include <sys/ioctl.h>
#define cout std::cout
#define TEXT( a ) a
using string = std::string;
#endif // UNIX
size_t getTermWidth() {
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi );
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
#else
struct winsize w;
ioctl( 0, TIOCGWINSZ, &w );
return w.ws_col;
#endif
}
#ifdef WIN32
void home( size_t /*UNUSED*/ ) {
static HANDLE h = NULL;
while ( !h )
h = GetStdHandle( STD_OUTPUT_HANDLE );
CONSOLE_SCREEN_BUFFER_INFO info;
GetConsoleScreenBufferInfo( h, &info );
COORD c = { 0, info.dwCursorPosition.Y - 1 };
SetConsoleCursorPosition( h, c );
}
#else
void home( size_t width ) {
cout << "\x1b[" << width << "D";
}
#endif
size_t getNum( size_t width, int perc ) {
return ( width * perc ) / 100;
}
void ProgressBar::print( int perc ) {
auto width = getTermWidth();
home( width );
auto count = getNum( width - 7, perc );
cout << "[" << string( count, TEXT( '=' ) ).c_str();
if ( perc != 100 ) {
int wc = width - 8 - count;
if ( wc < 0 )
wc = 0;
cout << ">" << string( wc, TEXT( ' ' ) ).c_str();
}
cout << "] ";
if ( perc / 10 == 0 )
cout << " ";
else if ( perc / 100 == 0 )
cout << " ";
cout << perc << "%" << std::flush;
}

8
progress.hpp Normal file
View File

@ -0,0 +1,8 @@
#ifndef PROGRESS_HPP
#define PROGRESS_HPP
namespace ProgressBar {
void print(int perc);
};
#endif

222876
sqlite3.c Normal file

File diff suppressed because it is too large Load Diff

11753
sqlite3.h Normal file

File diff suppressed because it is too large Load Diff

259
sqlitepp.hpp Normal file
View File

@ -0,0 +1,259 @@
#ifndef SQLITEPP
#define SQLITEPP
#include <iostream>
#include <unordered_map>
#include <vector>
#ifndef WIN32
#include <sqlite3.h>
#else
#include "sqlite3.h"
#include <codecvt>
#include <shlobj.h>
std::string _w_2_8( const std::wstring &wstring ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.to_bytes( wstring );
}
std::wstring _8_2_w( const std::string &utf8 ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.from_bytes( utf8 );
}
#endif
namespace SQLite {
constexpr size_t OPEN_READONLY = 0x00000001;
constexpr size_t OPEN_READWRITE = 0x00000002;
constexpr size_t OPEN_CREATE = 0x00000004;
constexpr size_t OPEN_URI = 0x00000040;
constexpr size_t OPEN_MEMORY = 0x00000080;
constexpr size_t OPEN_NOMUTEX = 0x00008000;
constexpr size_t OPEN_FULLMUTEX = 0x00010000;
constexpr size_t OPEN_SHAREDCACHE = 0x00020000;
constexpr size_t OPEN_PRIVATECACHE = 0x00040000;
class Database {
public:
Database() {}
Database( const std::string &file, size_t options ) {
open( file, options );
}
#ifdef WIN32
Database( const std::wstring &file, size_t options ) {
open( file, options );
}
#endif
~Database() {
close();
}
void open( const std::string &file, size_t options ) {
if ( sqlite3_open_v2( file.c_str(), &db, options, nullptr ) ) {
throw std::runtime_error( "Could not open database " + file );
}
}
#ifdef WIN32
void open( const std::wstring &file, size_t options ) {
if ( sqlite3_open_v2( _w_2_8( file ).c_str(), &db, options,
nullptr ) ) {
throw std::runtime_error( "Could not open database " +
_w_2_8( file ) );
}
}
#endif
// execute command without output
void exec( const std::string &command ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, command.c_str(), nullptr, nullptr, &errMsg );
if ( rc )
throw std::runtime_error( "\"" + command +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#ifdef WIN32
void exec( const std::wstring &command ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, _w_2_8( command ).c_str(), nullptr, nullptr,
&errMsg );
if ( rc )
throw std::runtime_error( "\"" + _w_2_8( command ) +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#endif
// execute command that should only output 1 result
void exec( const std::string &command, std::string &output ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, command.c_str(), insert_val_s,
static_cast< void * >( &output ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + command +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#ifdef WIN32
void exec( const std::wstring &command, std::wstring &output ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, _w_2_8( command ).c_str(), insert_val_ws,
static_cast< void * >( &output ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + _w_2_8( command ) +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#endif
// didn't work with set for some reason, so only vector is allowed
void exec( const std::string &command,
std::vector< std::unordered_map< std::string, std::string > >
&object ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, command.c_str(), insert_col_val,
static_cast< void * >( &object ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + command +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#ifdef WIN32
// didn't work with set for some reason, so only vector is allowed
void exec( const std::wstring &command,
std::vector< std::unordered_map< std::wstring, std::wstring > >
&object ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, _w_2_8( command ).c_str(), insert_col_val_w,
static_cast< void * >( &object ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + _w_2_8( command ) +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#endif
// execute command with output and store output in object (store only
// values) object can by any container as long as it's template is
// 'std::string'
template < typename V > void exec( const std::string &command, V &object ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, command.c_str(), insert_val< V >,
static_cast< void * >( &object ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + command +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#ifdef WIN32
template < typename V >
void exec( const std::wstring &command, V &object ) {
char *errMsg = nullptr;
int rc = sqlite3_exec( db, _w_2_8( command ).c_str(), insert_val_w< V >,
static_cast< void * >( &object ), &errMsg );
if ( rc )
throw std::runtime_error( "\"" + _w_2_8( command ) +
"\" resulted in an error:\n" + errMsg );
sqlite3_free( errMsg );
}
#endif
void close() {
sqlite3_close( db );
}
private:
sqlite3 *db;
static int insert_col_val( void *d, int argc, char **argv,
char **azColName ) {
auto &data = *static_cast<
std::vector< std::unordered_map< std::string, std::string > > * >(
d );
std::unordered_map< std::string, std::string > buffer;
for ( int i = 0; i < argc; i++ ) {
// insert all columns and values into 'data'
buffer[azColName[i]] = argv[i] ? argv[i] : "";
}
data.insert( data.end(), std::move( buffer ) );
return 0;
}
#ifdef WIN32
static int insert_col_val_w( void *d, int argc, char **argv,
char **azColName ) {
auto &data = *static_cast<
std::vector< std::unordered_map< std::wstring, std::wstring > > * >(
d );
std::unordered_map< std::wstring, std::wstring > buffer;
for ( int i = 0; i < argc; i++ ) {
// insert all columns and values into 'data'
buffer[_8_2_w( azColName[i] )] = argv[i] ? _8_2_w( argv[i] ) : L"";
}
data.insert( data.end(), std::move( buffer ) );
return 0;
}
#endif
static int insert_val_s( void *d, int argc, char **argv,
char ** /*UNUSED*/ ) {
auto &data = *static_cast< std::string * >( d );
if ( argc > 0 ) {
data = argv[0] ? argv[0] : "";
}
return 0;
}
#ifdef WIN32
static int insert_val_ws( void *d, int argc, char **argv,
char ** /*UNUSED*/ ) {
auto &data = *static_cast< std::wstring * >( d );
if ( argc > 0 ) {
data = argv[0] ? _8_2_w( argv[0] ) : L"";
}
return 0;
}
#endif
template < typename V >
static int insert_val( void *d, int argc, char **argv,
char ** /*UNUSED*/ ) {
auto &data = *static_cast< V * >( d );
for ( int i = 0; i < argc; i++ ) {
// insert all values into 'data'
data.insert( data.end(), argv[i] ? argv[i] : "" );
}
return 0;
}
#ifdef WIN32
template < typename V >
static int insert_val_w( void *d, int argc, char **argv,
char ** /*UNUSED*/ ) {
auto &data = *static_cast< V * >( d );
for ( int i = 0; i < argc; i++ ) {
// insert all values into 'data'
data.insert( data.end(), argv[i] ? _8_2_w( argv[i] ) : L"" );
}
return 0;
}
#endif
};
} // namespace SQLite
#endif

View File

@ -208,7 +208,7 @@ getRenamedFiles( const string &show, int season, string url,
void singleSeason( const string &path, string &show, int season, string url, void singleSeason( const string &path, string &show, int season, string url,
const string &language, const string &pattern, const string &language, const string &pattern,
const bool &linux, const bool &trust, Curl &c, const bool &linux, const bool &trust, Curl &c,
std::set< string > const *files_ptr ) { std::set< string > const *files_ptr, bool print ) {
if ( url.empty() ) if ( url.empty() )
url = getDefUrl( show, language, c ); url = getDefUrl( show, language, c );
@ -224,20 +224,22 @@ void singleSeason( const string &path, string &show, int season, string url,
getRenamedFiles( show, season, std::move( url ), language, pattern, getRenamedFiles( show, season, std::move( url ), language, pattern,
linux, c, *files_ptr ); linux, c, *files_ptr );
for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); if ( print ) {
++renamed ) { for ( auto renamed = renamed_files.begin();
cout << renamed->second.first << " --> " << renamed->second.second renamed != renamed_files.end(); ++renamed ) {
<< std::endl; cout << renamed->second.first << " --> " << renamed->second.second
} << std::endl;
}
if ( !trust ) { if ( !trust ) {
cout << "Does this seem ok? (y/n) "; cout << "Does this seem ok? (y/n) ";
string response; string response;
cin >> response; cin >> response;
cin.clear(); cin.clear();
cin.ignore( 1, '\n' ); cin.ignore( 1, '\n' );
if ( response[0] != 'y' && response[0] != 'Y' ) if ( response[0] != 'y' && response[0] != 'Y' )
return; return;
}
} }
for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end(); for ( auto renamed = renamed_files.begin(); renamed != renamed_files.end();

View File

@ -33,7 +33,8 @@ getRenamedFiles( const string &show, int season, string url,
void singleSeason( const string &path, string &show, int season, string url, void singleSeason( const string &path, string &show, int season, string url,
const string &language, const string &pattern, const string &language, const string &pattern,
const bool &linux, const bool &trust, Curl &c, const bool &linux, const bool &trust, Curl &c,
std::set< string > const *files = nullptr ); std::set< string > const *files = nullptr,
bool print = true );
void multipleSeasons( const string &path, string &show, void multipleSeasons( const string &path, string &show,
const std::set< int > seasons, const string &language, const std::set< int > seasons, const string &language,
const string &pattern, const bool &linux, const string &pattern, const bool &linux,

View File

@ -37,9 +37,20 @@ bool FSLib::exists( const string &path ) {
} }
string FSLib::canonical( const string &path ) { string FSLib::canonical( const string &path ) {
char_t *full_path = new char_t[MAX_PATH];
char_t *canonical_path = new char_t[MAX_PATH]; char_t *canonical_path = new char_t[MAX_PATH];
auto failed = !PathCanonicalizeW( canonical_path, path.c_str() ); auto failed = !GetFullPathName( path.c_str(), MAX_PATH, full_path, NULL );
if ( failed ) {
delete[] canonical_path;
delete[] full_path;
return string();
}
failed = !PathCanonicalizeW( canonical_path, full_path );
delete[] full_path;
if ( failed ) { if ( failed ) {
delete[] canonical_path; delete[] canonical_path;