Merge branch 'tests' into 'master'

Add tests and fix errors tests have discovered

See merge request zvon/tv_rename_cpp!3
This commit is contained in:
zvon 2020-05-08 21:11:51 +00:00
commit 466ca9f90d
10 changed files with 806 additions and 53 deletions

View File

@ -20,17 +20,6 @@ linux:build:
- tv_rename_gui
expire_in: 30 min
linux:test:
image: debian
stage: test
needs: ["linux:build"]
before_script:
- apt update
- apt install -y libcurl4 sqlite3
script:
- ./tv_rename --help
linux:codacy:
image: debian
stage: test
@ -43,7 +32,6 @@ linux:codacy:
script:
- make test.out
- make check
after_script:
- find . -type f -name "*.gcno" -execdir gcov -pb -r {} +
- gcovr --root . -k -j 2 --xml -o gcovr_report.xml --exclude-directories "tests" --exclude-directories "gtk" --exclude-directories "win*" --exclude-directories "sqlite-am*" --exclude-directories "rapidjson"
- bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r "gcovr_report.xml" --language CPP --force-language

View File

@ -188,7 +188,7 @@ locale/%/LC_MESSAGES/tv_rename.mo: translations/%.po locale/%/LC_MESSAGES
test.out: tests/test.cpp functions.cpp unix/network.cpp progress.cpp\
unix/filesystem.cpp tv_rename.cpp
$(CXX) $^ -lcurl -lsqlite3 --coverage -o test.out
$(CXX) $^ -lcurl -lsqlite3 --coverage -g -fsanitize=address,undefined -o test.out
.PHONY: check
check: test.out

View File

@ -28,6 +28,7 @@ bool exists( const string &path );
bool isDirectory( const string &path );
bool rename( const string &file_a, const string &file_b );
string canonical( const string &path );
bool createDirectoryFull( const string &path );
class Directory {
public:

View File

@ -56,27 +56,20 @@ std::wstring LMsg( int id, ... ) {
static wchar_t local[MAX_PATH];
auto hInstance = GetModuleHandle( NULL );
LoadString( hInstance, id, local, MAX_PATH );
va_list args;
va_start( args, id );
int count = 0;
const wchar_t *p;
while ( ( p = va_arg( args, const wchar_t * ) ) != NULL )
count++;
va_end( args );
if ( count == 0 )
return local;
va_start( args, id );
// _vscprintf doesn't count ending '\0'
int len = _vscwprintf( local, args ) + 1;
va_end( args );
wchar_t *text = new wchar_t[len];
va_start( args, id );
vswprintf( text, len + 1, local, args );
va_end( args );
std::wstring ret = text;
delete[] text;
return ret;
}
@ -85,24 +78,18 @@ std::wstring LMsg( int id, ... ) {
std::string getlocalized( const char *id, ... ) {
const char *local = gettext( id );
va_list args;
va_start( args, id );
int count = 0;
const char *p;
while ( ( p = va_arg( args, const char * ) ) != NULL )
count++;
va_end( args );
if ( count == 0 )
return local;
va_start( args, id );
int len = vsnprintf( nullptr, 0, local, args );
int len = vsnprintf( nullptr, 0, local, args ) + 1;
va_end( args );
char *text = new char[len + 1];
char *text = new char[len];
va_start( args, id );
vsnprintf( text, len + 1, local, args );
va_end( args );
std::string ret = text;
delete[] text;
return ret;
}
@ -142,10 +129,14 @@ bool searchSeason( const char_t *const path, size_t &season_pos,
if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) &&
iswdigit( path[cur_pos + 1] ) ) {
cur_pos++;
if ( path[cur_pos] == '\0' )
return false;
season_pos = cur_pos; // after ++ because we want the first pos to
// point to season's number
while ( iswdigit( path[cur_pos] ) )
cur_pos++;
if ( path[cur_pos] == '\0' )
return false;
if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) &&
iswdigit( path[cur_pos + 1] ) ) {
ep_pos = cur_pos + 1;
@ -228,7 +219,7 @@ void printPatternHelp() {
cout << " %season - " << _( PATTERN_SEASON ) << std::endl;
cout << " " << _( PATTERN_LEADING_ZERO ) << std::endl;
cout << " %2season " << _( PATTERN_LEADING_NUM ) << std::endl;
cout << " %episode - " << _( PATTERN_EPISODE ) << std::endl;
cout << " %episode - " << _( PATTERN_EPISODE ) << std::endl;
cout << " " << _( PATTERN_LEADING_ZERO ) << std::endl;
cout << " %2episode " << _( PATTERN_LEADING_NUM ) << std::endl;
cout << _( PATTERN_DEFAULT ) << " \"%filename - %epname\"" << std::endl;
@ -426,11 +417,17 @@ string sanitize( const string &str ) {
}
void prepareDB( const string &_pattern ) {
auto dbPath = getDBName();
auto dbDir =
dbPath.substr( 0, dbPath.find_last_of( TEXT( "/" ), dbPath.length() ) );
if ( !FSLib::exists( dbDir ) )
FSLib::createDirectoryFull( dbDir );
SQLite::Database db{};
try {
db.open( getDBName(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE );
db.open( dbPath, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE );
} catch ( std::exception &e ) {
cerr << _( DB_NOT_CREATE ) << " " << getDBName() << std::endl;
cerr << _( DB_NOT_CREATE ) << " " << dbPath << std::endl;
throw;
}
db.exec(

View File

@ -30,14 +30,14 @@
#ifndef GUI // Don't need terminal specific functions in GUI
#ifdef _WIN32
size_t getTermWidth() {
int getTermWidth() {
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &csbi );
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
}
#else // UNIX
size_t getTermWidth() {
int getTermWidth() {
struct winsize w;
ioctl( 0, TIOCGWINSZ, &w );
@ -63,7 +63,7 @@ void home( size_t width ) {
#endif // not GUI
size_t getNum( size_t width, int perc ) {
int getNum( int width, int perc ) {
return ( width * perc ) / 100;
}
@ -72,6 +72,8 @@ void ProgressBar::print( int perc ) {
auto width = getTermWidth();
home( width );
auto count = getNum( width - 7, perc );
if ( count < 0 )
count = 0;
cout << "[" << string( count, TEXT( '=' ) ).c_str();
if ( perc != 100 ) {
int wc = width - 8 - count;

View File

@ -1,8 +1,51 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "../filesystem.hpp"
#include "../functions.hpp"
#include "../resources_linux.h"
#include "../tv_rename.hpp"
#include "../sqlitepp.hpp"
#include <set>
#include <stdlib.h>
// declaring functions that aren't in functions.hpp
std::string sanitize( const std::string &str );
std::string getDBName();
void cleanUpLine();
std::vector< std::tuple< int, string, string, string > >
getRenamedFiles( const string &show, int season, const string &id,
const string &language, const string &pattern,
const bool &unix_names, const std::map< int, string > &files,
bool dvd );
std::map< string, string > getLangs();
// functions required for testing
bool authenticated = false;
void testDB() {
if ( !FSLib::exists( getDBName() ) ) {
prepareDB( "S%2seasonE%2episode - %epname" );
}
}
bool runBash( const std::string &command ) {
std::string cmd = "/bin/bash -c \"" + command + "\"";
return system( cmd.c_str() ) == 0;
}
void authenticateTest() {
if ( !authenticated ) {
authenticate( "42B66F5E-C6BF-423F-ADF9-CC97163472F6" );
authenticated = true;
}
}
// functions.hpp
TEST_CASE( "encodeUrl" ) {
std::string url1 = "http://google.com";
std::string url1_expected = "http%3A%2F%2Fgoogle.com";
@ -16,17 +59,676 @@ TEST_CASE( "encodeUrl" ) {
TEST_CASE( "userHome" ) {
SECTION( "correct usage" ) {
auto home = userHome();
std::string command = "/bin/bash -c \"if [ \"${HOME}\" == \"";
std::string command = "if [ \"\\${HOME}\" == \"";
command += home;
command += "\" ] ; then exit 0 ; else exit 1 ; fi\"";
REQUIRE( system( command.c_str() ) == 0 );
command += "\" ] ; then exit 0 ; else exit 1 ; fi";
REQUIRE( runBash( command ) );
}
uid_t original_u{ 0 }, original_e{ 0 }, original_s{ 0 };
gid_t original_g{ 0 }, original_ge{ 0 }, original_gs{ 0 };
getresuid( &original_u, &original_e, &original_s );
getresgid( &original_g, &original_ge, &original_gs );
SECTION( "exception with non existing user" ) {
setresgid( 1025, 1025, 1025 );
setresuid( 1025, 1025, 1025 );
setresuid( 1025, 1025, -1 );
REQUIRE_THROWS_WITH(
userHome(), Catch::Matchers::Contains( "User with uid" ) &&
Catch::Matchers::Contains( "doesn't exist!" ) );
}
setresuid( original_u, original_e, original_s );
}
TEST_CASE( "getlocalized" ) {
auto ret = getlocalized( ADDING_TO_DB );
REQUIRE( ret == "Adding to database" );
ret = getlocalized( UPDATING_IN_DB, "test" );
REQUIRE( ret == "Updating test in database" );
}
TEST_CASE( "getDBPattern" ) {
testDB();
REQUIRE( "S%2seasonE%2episode - %epname" == getDBPattern() );
}
TEST_CASE( "searchSeason" ) {
size_t seasonpos{ 0 }, eppos{ 0 };
REQUIRE( searchSeason( "s001e003", seasonpos, eppos ) );
REQUIRE( ( seasonpos == 1 && eppos == 5 ) );
REQUIRE( searchSeason( "s001e003", seasonpos ) );
REQUIRE( seasonpos == 1 );
REQUIRE_FALSE( searchSeason( "e01S013", seasonpos, eppos ) );
REQUIRE(
searchSeason( "TesTSOIHSADF0239s023S23iewahoiehwf001e22E33sS1E1.mkv",
seasonpos, eppos ) );
REQUIRE( ( seasonpos == 45 && eppos == 47 ) );
REQUIRE_FALSE( searchSeason( "s01sE01.mkv", seasonpos ) );
}
TEST_CASE( "iterateFS" ) {
runBash(
"mkdir test_iteratefs ; cd test_iteratefs ; for f in {01..10} ; do "
"mkdir \\\"Season \\${f}\\\" ; cd \\\"Season \\${f}\\\" ; for g in "
"{01..30} ; do touch \\\"S\\${f}E\\${g}.mkv\\\" ; done ; cd .. ; "
"done" );
std::map< int, std::map< int, string > > seasons;
iterateFS( seasons, "test_iteratefs" );
REQUIRE( seasons.size() == 10 );
for ( auto &x : seasons ) {
REQUIRE( x.second.size() == 30 );
for ( auto &ep : x.second ) {
std::string epfile = "S";
if ( x.first <= 9 )
epfile += "0";
epfile += std::to_string( x.first );
epfile += "E";
if ( ep.first <= 9 )
epfile += "0";
epfile += std::to_string( ep.first );
epfile += ".mkv";
REQUIRE_THAT( ep.second, Catch::Contains( epfile ) );
}
}
REQUIRE_THROWS_WITH( iterateFS( seasons, "nonexistendir" ),
Catch::Contains( "Directory" ) &&
Catch::Contains( "doesn't exist" ) );
REQUIRE_THROWS_WITH(
iterateFS( seasons, "test_iteratefs/Season 01/S01E01.mkv" ),
Catch::Contains( "Directory" ) &&
Catch::Contains( "isn't a directory" ) );
}
TEST_CASE( "printing functions" ) {
std::string expectedHelp =
"Usage:\n tv_rename [options] [path]\n\n -h, --help "
"show this help message and exit\n\n path can be either a file or a "
"directory, if it's a directory\n all files in it and its "
"subdirectories will be renamed\n\nOPTIONS\n -s, --show <string> "
"TV show from which you want the episode names\n -n, --season "
"<numbers> Season number/s (if multiple seasons,\n "
" must be seperated by one space) or 'all'\n "
" for all seasons in selected directory\n\n -d, --dvd "
" use dvd ordering instead of aired ordering\n --name-pattern "
"<string> Pattern to which change the file name.\n --pattern-help "
" Print pattern help\n -c, --correct-path This is the "
"correct path, stop asking me!\n -t, --trust Don't ask "
"whether the names are correct\n -x, --linux Don't "
"replace characters characters that are\n "
"illegal in Windows\n -l, --lang <string> Select which language "
"the episode names shoud be in\n --print-langs Print "
"available languages\n\nDATABASE OPTIONS\n --db-add "
"Add path to the database\n --db-refresh Refresh "
"episode names for all paths in the database\n --db-update "
" Check all paths in the database,\n if "
"they contain new files, rename them\n --db-name-pattern <string> "
"Change name pattern used for files\n "
"managed by database\n --db-clean Remove deleted "
"files from the database\n --db-remove Remove path "
"from the database\n";
std::string expectedPattern =
"Possible pattern sequences are:\n %filename - original filename "
"(without filetype extension)\n %show - show name from thetvdb\n "
"%epname - episode name from thetvdb\n %season - season number\n "
"it's possible to specify leading 0 like this:\n %2season (number "
"means how many zeros)\n %episode - episode number\n it's possible "
"to specify leading 0 like this:\n %2episode (number means how "
"many zeros)\nDefault pattern is \"%filename - %epname\"\n";
std::ostringstream oss;
auto original_cout = std::cout.rdbuf();
std::cout.rdbuf( oss.rdbuf() );
printHelp();
REQUIRE( oss.str() == expectedHelp );
oss.str( "" );
oss.clear();
printPatternHelp();
REQUIRE( oss.str() == expectedPattern );
std::cout.rdbuf( original_cout );
}
TEST_CASE( "parseSeasonNumber" ) {
std::set< int > season_nums{};
parseSeasonNumbers( season_nums, "1 2 3 4 5 6" );
REQUIRE( season_nums.size() == 6 );
for ( int i = 1; i < 7; i++ ) {
REQUIRE( season_nums.find( i ) != season_nums.end() );
}
season_nums.clear();
parseSeasonNumbers( season_nums, "this is not a number" );
REQUIRE( season_nums.size() == 0 );
parseSeasonNumbers( season_nums,
"this is not a number 1 2 3 this is the end 4 5 6" );
REQUIRE( season_nums.size() == 3 );
}
std::string testCompilePattern( const std::string &pattern ) {
return compilePattern( pattern, 45, 914, "episode_of_show_s45e914",
"The one where everyone dies", "Enemies" );
}
TEST_CASE( "compilePattern" ) {
REQUIRE( testCompilePattern( "%filename" ) == "episode_of_show_s45e914" );
REQUIRE( testCompilePattern( "%show" ) == "Enemies" );
REQUIRE( testCompilePattern( "%epname" ) == "The one where everyone dies" );
REQUIRE( testCompilePattern( "%season" ) == "45" );
REQUIRE( testCompilePattern( "%episode" ) == "914" );
REQUIRE( testCompilePattern( "%1season" ) ==
testCompilePattern( "%2season" ) );
REQUIRE( testCompilePattern( "%20season" ) == "00000000000000000045" );
REQUIRE( testCompilePattern( "%1episode" ) ==
testCompilePattern( "%2episode" ) );
REQUIRE( testCompilePattern( "%20episode" ) == "00000000000000000914" );
REQUIRE( testCompilePattern(
"%filename - %show - S%3seasonE%3episode - %epname" ) ==
"episode_of_show_s45e914 - Enemies - S045E914 - The one where "
"everyone dies" );
REQUIRE( testCompilePattern( "\\%filename" ) == "%filename" );
REQUIRE( testCompilePattern( "\\\\%filename" ) ==
"\\episode_of_show_s45e914" );
REQUIRE( testCompilePattern( "\\filename" ) == "\\filename" );
REQUIRE( testCompilePattern( "%notakeyword" ) == "%notakeyword" );
}
TEST_CASE( "sanitize" ) {
REQUIRE( sanitize( "I am a nice input" ) == "I am a nice input" );
REQUIRE(
sanitize( "I am an evil input!'; DROP TABLE USER_ACCOUNT_BALANCE;" ) ==
"I am an evil input!''; DROP TABLE USER_ACCOUNT_BALANCE;" );
}
TEST_CASE( "cleanUpLine" ) {
std::ostringstream oss;
auto original_cout = std::cout.rdbuf();
std::cout.rdbuf( oss.rdbuf() );
cleanUpLine();
std::cout << std::flush;
REQUIRE_THAT( oss.str().substr( 3 ), Catch::Contains( "\x1b[2A" ) );
std::cout.rdbuf( original_cout );
}
TEST_CASE( "addToDB" ) {
testDB();
authenticateTest();
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\n" );
runBash( "mkdir add_dir ; touch add_dir/s01e01.mkv" );
std::string show = "simpsons";
addToDB( show, "add_dir", "en", true, false );
SQLite::Database db{};
db.open( getDBName(), SQLite::OPEN_READONLY );
std::vector< std::unordered_map< std::string, std::string > > result_eps{};
std::vector< std::unordered_map< std::string, std::string > >
result_shows{};
db.exec( "SELECT * FROM EPISODES;", result_eps );
db.exec( "SELECT * FROM SHOWS;", result_shows );
std::string wanted_id{};
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "The Simpsons" ) {
wanted_id = x["ID"];
break;
}
}
bool foundCorrect = false;
for ( auto &x : result_eps ) {
if ( x["PATH"].find(
"S01E01 - Simpsons Roasting on an Open Fire.mkv" ) !=
std::string::npos &&
x["SHOWID"] == wanted_id ) {
foundCorrect = true;
break;
}
}
REQUIRE( foundCorrect );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "refreshDB" ) {
testDB();
authenticateTest();
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\n" );
runBash( "mkdir refresh_dir ; touch refresh_dir/s01e01.mkv" );
std::string show = "friends";
addToDB( show, "refresh_dir", "en", true, false );
runBash( "touch refresh_dir/s01e02.mkv" );
refreshDB( true );
SQLite::Database db{};
db.open( getDBName(), SQLite::OPEN_READONLY );
std::vector< std::unordered_map< std::string, std::string > > result_eps{};
std::vector< std::unordered_map< std::string, std::string > >
result_shows{};
db.exec( "SELECT * FROM EPISODES;", result_eps );
db.exec( "SELECT * FROM SHOWS;", result_shows );
std::string wanted_id{};
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "Friends" ) {
wanted_id = x["ID"];
break;
}
}
bool foundCorrect = false;
for ( auto &x : result_eps ) {
if ( x["PATH"].find(
"S01E02 - The One With The Sonogram At The End.mkv" ) !=
std::string::npos &&
x["SHOWID"] == wanted_id ) {
foundCorrect = true;
break;
}
}
REQUIRE( foundCorrect );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "updateDB" ) {
testDB();
authenticateTest();
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\n" );
runBash( "mkdir update_dir ; touch update_dir/s01e01.mkv" );
std::string show = "seinfeld";
addToDB( show, "update_dir", "en", true, false );
runBash( "touch update_dir/s01e02.mkv" );
updateDB( true );
SQLite::Database db{};
db.open( getDBName(), SQLite::OPEN_READONLY );
std::vector< std::unordered_map< std::string, std::string > > result_eps{};
std::vector< std::unordered_map< std::string, std::string > >
result_shows{};
db.exec( "SELECT * FROM EPISODES;", result_eps );
db.exec( "SELECT * FROM SHOWS;", result_shows );
std::string wanted_id{};
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "Seinfeld" ) {
wanted_id = x["ID"];
break;
}
}
bool foundCorrect = false;
for ( auto &x : result_eps ) {
if ( x["PATH"].find( "S01E02 - The Stake Out.mkv" ) !=
std::string::npos &&
x["SHOWID"] == wanted_id ) {
foundCorrect = true;
break;
}
}
REQUIRE( foundCorrect );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "cleanDB" ) {
testDB();
authenticateTest();
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\n" );
runBash( "mkdir clean_dir ; touch clean_dir/s01e01.mkv" );
std::string show = "scrubs";
addToDB( show, "clean_dir", "en", true, false );
runBash( "rm clean_dir/S01E01*" );
cleanDB();
SQLite::Database db{};
db.open( getDBName(), SQLite::OPEN_READONLY );
std::vector< std::unordered_map< std::string, std::string > > result_eps{};
std::vector< std::unordered_map< std::string, std::string > >
result_shows{};
db.exec( "SELECT * FROM EPISODES;", result_eps );
db.exec( "SELECT * FROM SHOWS;", result_shows );
std::string wanted_id{};
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "Scrubs" ) {
wanted_id = x["ID"];
break;
}
}
bool found = false;
for ( auto &x : result_eps ) {
if ( x["PATH"].find( "S01E01 - My First Day.mkv" ) !=
std::string::npos &&
x["SHOWID"] == wanted_id ) {
found = true;
break;
}
}
REQUIRE_FALSE( found );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "removeFromDB" ) {
testDB();
authenticateTest();
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\n" );
runBash( "mkdir remove_dir ; touch remove_dir/s01e01.mkv" );
std::string show = "how i met";
addToDB( show, "remove_dir", "en", true, false );
SQLite::Database db{};
db.open( getDBName(), SQLite::OPEN_READONLY );
std::vector< std::unordered_map< std::string, std::string > >
result_shows{};
db.exec( "SELECT * FROM SHOWS;", result_shows );
std::string wanted_id{ "0" };
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "How I Met Your Mother" ) {
wanted_id = x["ID"];
break;
}
}
REQUIRE( wanted_id != "0" );
removeFromDB( FSLib::canonical( "." ) + "/remove_dir" );
wanted_id = "0";
result_shows.clear();
db.exec( "SELECT * FROM SHOWS;", result_shows );
for ( auto &x : result_shows ) {
if ( x["SHOW"] == "How I Met Your Mother" ) {
wanted_id = x["ID"];
break;
}
}
REQUIRE( wanted_id == "0" );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "changeDBPattern" ) {
testDB();
std::string old_pattern = "S%2seasonE%2episode - %epname";
std::string new_pattern = "THIS IS NOT A GOOD PATTERN";
REQUIRE( getDBPattern() == old_pattern );
changeDBPattern( new_pattern );
REQUIRE( getDBPattern() == new_pattern );
changeDBPattern( old_pattern );
REQUIRE( getDBPattern() == old_pattern );
}
// filesystem.hpp
TEST_CASE( "Directory" ) {
std::string command =
"mkdir test_dir ; cd test_dir ; for f in {01..10} ; do "
"touch \\${f} ; done";
runBash( command );
FSLib::Directory d( "test_dir" );
std::set< std::string > files = { "01", "02", "03", "04", "05",
"06", "07", "08", "09", "10" };
int counter = 0;
for ( auto x : d ) {
counter++;
REQUIRE( files.find( x ) != files.end() );
}
REQUIRE( counter == 10 );
}
TEST_CASE( "Filesystem" ) {
std::string command =
"mkdir test_dir2 ; cd test_dir2 ; for f in {01..10} ; do "
"touch \\${f} ; done";
runBash( command );
std::set< std::string > files = { "01", "02", "03", "04", "05",
"06", "07", "08", "09", "10" };
// exists
REQUIRE( FSLib::exists( "test_dir2" ) );
for ( auto &x : files ) {
REQUIRE( FSLib::exists( "test_dir2/" + x ) );
REQUIRE_FALSE( FSLib::exists( x ) );
}
// isDirectory
REQUIRE( FSLib::isDirectory( "test_dir2" ) );
REQUIRE( FSLib::isDirectory( "." ) );
REQUIRE_FALSE( FSLib::isDirectory( "test_dir2/01" ) );
REQUIRE_FALSE( FSLib::isDirectory( "nonexistentfile" ) );
// canonical
auto pwd = FSLib::canonical( "." );
command = "if [ \"\\${PWD}\" == \"";
command += pwd;
command += "\" ] ; then exit 0 ; else exit 1 ; fi";
REQUIRE( runBash( command ) );
REQUIRE( FSLib::canonical( "nonexistentfile" ) == "" );
// rename
for ( auto &x : files ) {
REQUIRE( FSLib::rename( "test_dir2/" + x, "test_dir2/renamed_" + x ) );
REQUIRE_FALSE(
FSLib::rename( "test_dir2/" + x, "test_dir2/renamed_" + x ) );
REQUIRE( FSLib::exists( "test_dir2/renamed_" + x ) );
}
}
// tv_rename.cpp
TEST_CASE( "getRenamedFiles Windows chars" ) {
std::map< int, std::string > files{};
files[1] = "I am a windows illegal file \\|<>:?\"*";
auto res = getRenamedFiles( "simpsons", 1, "71663", "en", "%filename",
false, files, false );
REQUIRE( std::get< 0 >( res[0] ) == 1 );
REQUIRE( std::get< 1 >( res[0] ) == "." );
REQUIRE( std::get< 2 >( res[0] ) ==
"I am a windows illegal file \\|<>:?\"*" );
REQUIRE( std::get< 3 >( res[0] ) ==
"I am a windows illegal file - - " );
}
TEST_CASE( "getLangs" ) {
authenticateTest();
auto langs = getLangs();
REQUIRE_FALSE( langs.empty() );
bool containsEn = false;
bool containsCs = false;
for ( auto &x : langs ) {
if ( x.first == "en" )
containsEn = true;
if ( x.first == "cs" )
containsCs = true;
}
REQUIRE( containsEn );
REQUIRE( containsCs );
}
TEST_CASE( "singleSeason" ) {
runBash(
"mkdir test_singleseason ; cd test_singleseason ; for f in {01..10} ;"
"do mkdir \\\"Season \\${f}\\\" ; cd \\\"Season \\${f}\\\" ; for g in "
"{01..30} ; do touch \\\"S\\${f}E\\${g}.mkv\\\" ; done ; cd .. ; "
"done" );
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "y\n" );
singleSeason( "test_singleseason", "simpsons", 1, "71663", "en",
"S%2seasonE%2episode - %epname", false, false, nullptr, false,
false );
REQUIRE( FSLib::exists( "test_singleseason/Season 01/S01E01 - Simpsons "
"Roasting on an Open Fire.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E02 - Bart the Genius.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E03 - Homer's Odyssey.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 01/S01E04 - There's No "
"Disgrace Like Home.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E05 - Bart the General.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E06 - Moaning Lisa.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E07 - The Call of the Simpsons.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E08 - The Telltale Head.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E09 - Life on the Fast Lane.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E10 - Homer's Night Out.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E11 - The Crepes of Wrath.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E12 - Krusty Gets Busted.mkv" ) );
REQUIRE( FSLib::exists(
"test_singleseason/Season 01/S01E13 - Some Enchanted Evening.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 02/S02E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 03/S03E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 04/S04E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 05/S05E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 06/S06E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 07/S07E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 08/S08E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 09/S09E01.mkv" ) );
REQUIRE( FSLib::exists( "test_singleseason/Season 10/S10E01.mkv" ) );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "multipleSeasons" ) {
runBash( "mkdir test_multipleseasons ; cd test_multipleseasons ;"
"for f in {01..10} ; do mkdir \\\"Season \\${f}\\\" ; cd "
"\\\"Season \\${f}\\\" ; for g in "
"{01..30} ; do touch \\\"S\\${f}E\\${g}.mkv\\\" ; done ; cd .. ; "
"done" );
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\ny\ny\ny\n" );
multipleSeasons( "test_multipleseasons", "simpsons", { 1, 2, 3 }, "en",
"S%2seasonE%2episode - %epname", 0 );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 01/S01E01 - Simpsons "
"Roasting on an Open Fire.mkv" ) );
REQUIRE( FSLib::exists(
"test_multipleseasons/Season 02/S02E01 - Bart Gets an F.mkv" ) );
REQUIRE( FSLib::exists(
"test_multipleseasons/Season 03/S03E01 - Stark Raving Dad.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 04/S04E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 05/S05E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 06/S06E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 07/S07E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 08/S08E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 09/S09E01.mkv" ) );
REQUIRE( FSLib::exists( "test_multipleseasons/Season 10/S10E01.mkv" ) );
std::cin.rdbuf( original_cin );
std::cout.clear();
}
TEST_CASE( "allSeasons" ) {
runBash( "mkdir test_allseasons ; cd test_allseasons ;"
"for f in {01..10} ; do mkdir \\\"Season \\${f}\\\" ; cd "
"\\\"Season \\${f}\\\" ; for g in "
"{01..30} ; do touch \\\"S\\${f}E\\${g}.mkv\\\" ; done ; cd .. ; "
"done" );
std::istringstream iss;
auto original_cin = std::cin.rdbuf();
std::cin.rdbuf( iss.rdbuf() );
std::cout.setstate( std::ios_base::failbit );
iss.str( "1\ny\ny\ny\ny\ny\ny\ny\ny\ny\ny\n" );
allSeasons( "test_allseasons", "simpsons", "en",
"S%2seasonE%2episode - %epname", 0 );
REQUIRE( FSLib::exists( "test_allseasons/Season 01/S01E01 - Simpsons "
"Roasting on an Open Fire.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 02/S02E01 - Bart Gets an F.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 03/S03E01 - Stark Raving Dad.mkv" ) );
REQUIRE(
FSLib::exists( "test_allseasons/Season 04/S04E01 - Kamp Krusty.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 05/S05E01 - Homer's Barbershop Quartet.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 06/S06E01 - Bart of Darkness.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 07/S07E01 - Who Shot Mr. Burns (2).mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 08/S08E01 - Treehouse of Horror VII.mkv" ) );
REQUIRE( FSLib::exists( "test_allseasons/Season 09/S09E01 - The City of "
"New York vs. Homer Simpson.mkv" ) );
REQUIRE( FSLib::exists(
"test_allseasons/Season 10/S10E01 - Lard of the Dance.mkv" ) );
std::cin.rdbuf( original_cin );
std::cout.clear();
}

View File

@ -215,11 +215,21 @@ getRenamedFiles( const string &show, int season, const string &id,
if ( ep_num < episodes.size() ) {
auto pos = og_name.find_last_of( TEXT( "." ) );
string og_name_without_extension{};
string og_name_extension{};
if ( pos == string::npos ) {
og_name_without_extension = og_name;
} else {
og_name_without_extension = og_name.substr( 0, pos );
og_name_extension = og_name.substr( pos );
}
// get desired filename
auto name = compilePattern( pattern, season, x.first,
og_name.substr( 0, pos ),
og_name_without_extension,
episodes[ep_num], show ) +
og_name.substr( pos );
og_name_extension;
// replace '/' with '|'
for ( size_t i = 0; i < name.size(); i++ ) {
if ( name[i] == '/' ) {
@ -346,7 +356,7 @@ void singleSeason( const string &path, const string &show, int season,
goto end;
if ( print || !trust ) {
for ( const auto renamed : renamed_files ) {
for ( const auto &renamed : renamed_files ) {
cout << std::get< 2 >( renamed ) << " --> "
<< std::get< 3 >( renamed ) << std::endl;
}
@ -363,7 +373,7 @@ void singleSeason( const string &path, const string &show, int season,
}
}
for ( const auto renamed : renamed_files ) {
for ( const auto &renamed : renamed_files ) {
FSLib::rename( std::get< 1 >( renamed ) + _tv_rename_dir_divider +
std::get< 2 >( renamed ),
std::get< 1 >( renamed ) + _tv_rename_dir_divider +
@ -391,7 +401,7 @@ void singleSeason( const string &path, const string &show, int season,
#ifndef GUI
void multipleSeasons( const string &path, string &show,
void multipleSeasons( const string &path, const string &show,
const std::set< int > &seasons, const string &language,
const string &pattern, const size_t &flags ) {
std::map< int, std::map< int, string > > season_map;
@ -406,7 +416,7 @@ void multipleSeasons( const string &path, string &show,
}
}
void allSeasons( const string &path, string &show, const string &language,
void allSeasons( const string &path, const string &show, const string &language,
const string &pattern, const size_t &flags ) {
std::map< int, std::map< int, string > > seasons;
// get all season number from this directory and subdirectories

View File

@ -54,11 +54,11 @@ std::vector< std::pair< string, string > > getLangs();
#else
void multipleSeasons( const string &path, string &show,
void multipleSeasons( const string &path, const string &show,
const std::set< int > &seasons, const string &language,
const string &pattern, const size_t &flags );
void allSeasons( const string &path, string &show, const string &language,
void allSeasons( const string &path, const string &show, const string &language,
const string &pattern, const size_t &flags );
string getShowId( const string &show, const string &language );

View File

@ -1,11 +1,18 @@
#include "../filesystem.hpp"
#include <cstring>
#include <sys/stat.h>
#include <stdexcept>
FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {}
FSLib::Directory::Iterator::Iterator( const Directory &d_ )
: d( opendir( d_.path() ) ) {
if ( !exists( d_.path() ) || !isDirectory( d_.path() ) ) {
throw std::runtime_error(
std::string( "Directory " ) + d_.path() +
" either doesn't exist or isn't a directory" );
}
current_entry = readdir( d );
// skip "." and ".."
@ -56,6 +63,27 @@ bool FSLib::rename( const string &file_a, const string &file_b ) {
return ::rename( file_a.c_str(), file_b.c_str() ) == 0;
}
bool FSLib::createDirectoryFull( const string &path ) {
uint64_t pos{};
// go through all directories leading to the last one
// and create them if they don't exist
do {
// get partial directory path
pos = path.find_first_of( "/", pos );
if ( pos > 0 ) {
auto dirname = path.substr( 0, pos );
// create it if it doesn't exist
if ( !FSLib::exists( dirname ) ) {
if ( mkdir( dirname.c_str(),
S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH ) != 0 )
return false;
}
}
pos++;
} while ( pos < path.length() && pos != 0 );
return true;
}
FSLib::Directory::iterator FSLib::Directory::end() {
return Iterator( *this, nullptr );
}

View File

@ -1,6 +1,7 @@
#include "../filesystem.hpp"
#include <Shlwapi.h>
#include <cstring>
#include <stdexcept>
#include <windows.h>
FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {
@ -9,6 +10,10 @@ FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {
}
FSLib::Directory::Iterator::Iterator( const Directory &d_ ) {
if ( !exists( d_.path() ) || !isDirectory( d_.path() ) ) {
throw std::runtime_error(
"Directory either doesn't exist or isn't a directory" );
}
hFind = FindFirstFileW( d_.path(), &data );
if ( hFind != INVALID_HANDLE_VALUE ) {
if ( !wcscmp( data.cFileName, L"." ) ||
@ -75,6 +80,26 @@ bool FSLib::rename( const string &file_a, const string &file_b ) {
MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING );
}
bool FSLib::createDirectoryFull( const string &path ) {
uint64_t pos = path.find_first_of( L":", 0 ) + 2;
if ( pos == string::npos )
pos = 0;
// go through all directories leading to the last one
// and create them if they don't exist
do {
// get partial directory path
pos = path.find_first_of( L"\\", pos );
auto dirname = path.substr( 0, pos );
// create it if it doesn't exist
if ( !FSLib::exists( dirname ) ) {
if ( !CreateDirectoryW( dirname.c_str(), NULL ) )
return false;
}
pos++;
} while ( pos < path.length() && pos != 0 );
return true;
}
FSLib::Directory::iterator FSLib::Directory::end() {
return Iterator( true );
}