This commit is contained in:
zv0n 2021-07-08 07:41:34 +02:00
commit 9413214fce
25 changed files with 2690 additions and 0 deletions

43
CMakeLists.txt Normal file
View File

@ -0,0 +1,43 @@
cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(/usr/local/include)
link_directories(/usr/local/lib)
project(RenameServer)
add_executable(RenameServer
main.cpp
rename_object.cpp
functions.cpp
config/config.cpp
filesystem/unix/filesystem.cpp
jwt.cpp
)
target_link_libraries(RenameServer restbed config++ jwt)
add_library(thetvdb SHARED
thetvdb/tv_rename.cpp
thetvdb/functions.cpp
rename_object.cpp
filesystem/unix/filesystem.cpp
network/unix/network.cpp)
target_link_libraries(thetvdb curl)
add_library(simple SHARED
simple_rename/simple.cpp
rename_object.cpp
filesystem/unix/filesystem.cpp)
add_library(moviedb SHARED
themoviedb/moviedb.cpp
themoviedb/functions.cpp
rename_object.cpp
filesystem/unix/filesystem.cpp
network/unix/network.cpp)
target_link_libraries(moviedb curl)

50
config/config.cpp Normal file
View File

@ -0,0 +1,50 @@
#include "config.hpp"
#include <libconfig.h++>
#include <iostream>
bool Configuration::readConfiguration(const std::string &file) {
libconfig::Config cfg;
try {
cfg.readFile(file);
} catch (const libconfig::FileIOException &fioex) {
std::cerr << "Couldn't open configuration file" << std::endl;
return false;
} catch (const libconfig::ParseException &pex) {
std::cerr << "Couldn't parse configuration file" << std::endl;
return false;
}
cfg.lookupValue("source_path", source_path);
auto &targets = cfg.getRoot()["target_paths"];
for(auto &target : targets) {
target_paths.emplace_back(target["path"], target["name"]);
}
auto &cfg_libraries = cfg.getRoot()["libraries"];
for(auto &library : cfg_libraries) {
libraries.emplace_back(
library.lookup("path"),
library.lookup("name"),
library.lookup("config"));
}
auto &cfg_users = cfg.getRoot()["users"];
for(auto &user : cfg_users) {
users.emplace_back(
user.lookup("user"),
user.lookup("password"));
}
return true;
}
const std::string &Configuration::getSourcePath() {
return source_path;
}
const std::vector<std::tuple<std::string, std::string, std::string>> &Configuration::getLibraries() {
return libraries;
}
const std::vector<std::pair<std::string, std::string>> &Configuration::getTargetPaths() {
return target_paths;
}
const std::vector<std::pair<std::string,std::string>> &Configuration::getUsers() {
return users;
}

21
config/config.hpp Normal file
View File

@ -0,0 +1,21 @@
#ifndef RS_CONFIG_HPP
#define RS_CONFIG_HPP
#include <string>
#include <vector>
class Configuration {
public:
bool readConfiguration(const std::string &file);
const std::string &getSourcePath();
const std::vector<std::tuple<std::string, std::string, std::string>> &getLibraries();
const std::vector<std::pair<std::string,std::string>> &getTargetPaths();
const std::vector<std::pair<std::string,std::string>> &getUsers();
private:
std::vector<std::tuple<std::string,std::string,std::string>> libraries;
std::string source_path;
std::vector<std::pair<std::string, std::string>> target_paths;
std::vector<std::pair<std::string, std::string>> users;
};
#endif

126
filesystem/filesystem.hpp Normal file
View File

@ -0,0 +1,126 @@
#ifndef FSLIB_H
#define FSLIB_H
#include <string>
#ifdef _WIN32
#include <Windows.h>
using string = std::wstring;
using char_t = wchar_t;
#else
#include <dirent.h>
using string = std::string;
using char_t = char;
#endif
// windows version stolen from
// http://www.martinbroadhurst.com/list-the-files-in-a-directory-in-c.html
namespace FSLib {
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 );
string getContainingDirectory( const string &path );
string getFileName( const string &path );
string getFileExtension( const string &path );
extern char dir_divisor;
class Directory {
public:
Directory() = delete;
explicit Directory( const string &path_ );
explicit Directory( const Directory &d ) = default;
explicit Directory( Directory &&d ) = default;
class Iterator {
public:
explicit Iterator( const Directory &d_ );
~Iterator();
#ifdef _WIN32
explicit Iterator( bool ended_ );
#else
Iterator( const Directory &d_, const struct dirent *current_entry_ );
#endif
Iterator() = delete;
Iterator( const Iterator &i ) = default;
Iterator( Iterator &&i ) = default;
char_t const *operator*() const;
Iterator &operator++();
bool operator==( const Iterator &i_other ) const;
Iterator operator++( int ) {
Iterator ret( *this );
operator++();
return ret;
}
bool operator!=( const Iterator &i_other ) const {
return !( i_other == *this );
}
private:
#ifndef _WIN32
DIR *d{};
const struct dirent *current_entry{};
#else
HANDLE hFind{};
WIN32_FIND_DATA data{};
bool ended{ false };
#endif
};
using iterator = Iterator;
using const_iterator = Iterator;
iterator end();
const_iterator end() const;
iterator begin() {
return Iterator( *this );
}
const_iterator begin() const {
return Iterator( *this );
}
const_iterator cbegin() const {
return begin();
}
const_iterator cend() const {
return end();
}
const char_t *path() const {
return dir_path.c_str();
}
#ifdef _WIN32
const char_t *validPath() const {
return dir_path.substr( 0, dir_path.length() - 2 ).c_str();
}
#endif
private:
string dir_path;
};
} // namespace FSLib
#endif

View File

@ -0,0 +1,145 @@
#include "../filesystem.hpp"
#include <cstring>
#include <sys/stat.h>
#ifdef __APPLE__
#include <sys/syslimits.h>
#endif
#include <stdexcept>
#include <iostream>
char FSLib::dir_divisor = '/';
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 ".."
if ( current_entry != nullptr &&
( !strcmp( current_entry->d_name, "." ) ||
!strcmp( current_entry->d_name, ".." ) ) )
++( *this );
}
FSLib::Directory::Iterator::Iterator( const Directory &d_,
const struct dirent *current_entry_ )
: d( opendir( d_.path() ) ), current_entry( current_entry_ ) {}
FSLib::Directory::Iterator::~Iterator() {
if ( d )
closedir( d );
}
bool FSLib::exists( const string &path ) {
struct stat path_stat;
return stat( path.c_str(), &path_stat ) == 0;
}
string FSLib::canonical( const string &path ) {
char_t *canonical_path = new char_t[PATH_MAX];
auto failed = realpath( path.c_str(), canonical_path ) == nullptr;
if ( failed ) {
delete[] canonical_path;
return string();
}
string canonical_string{ canonical_path };
delete[] canonical_path;
return canonical_string;
}
bool FSLib::isDirectory( const string &path ) {
struct stat path_stat;
if ( stat( path.c_str(), &path_stat ) != 0 )
return false;
return S_ISDIR( path_stat.st_mode );
}
bool FSLib::rename( const string &file_a, const string &file_b ) {
// TODO log
std::cout << file_a << " -> " << file_b << std::endl;
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 );
}
FSLib::Directory::const_iterator FSLib::Directory::end() const {
return Iterator( *this, nullptr );
}
char_t const *FSLib::Directory::Iterator::operator*() const {
return current_entry->d_name;
}
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if ( current_entry == nullptr )
return *this;
current_entry = readdir( d );
// skip . and ..
if ( current_entry != nullptr &&
( !strcmp( current_entry->d_name, "." ) ||
!strcmp( current_entry->d_name, ".." ) ) )
return operator++();
return *this;
}
bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const {
return i_other.current_entry == current_entry;
}
string FSLib::getContainingDirectory( const string &path ) {
auto pos = path.find_last_of('/');
if(pos == string::npos) {
return ".";
}
return path.substr(0, pos);
}
string FSLib::getFileName( const string &path ) {
auto pos = path.find_last_of('/');
if(pos == string::npos) {
return path;
}
return path.substr(pos+1);
}
string FSLib::getFileExtension( const string &path ) {
auto pos = path.find_last_of('.');
if(pos == string::npos) {
return "";
}
return path.substr(pos + 1);
}

View File

@ -0,0 +1,156 @@
#include "../filesystem.hpp"
#include <Shlwapi.h>
#include <cstring>
#include <stdexcept>
#include <windows.h>
char FSLib::dir_divisor = '\\';
FSLib::Directory::Directory( const string &path_ ) : dir_path( path_ ) {
// need to append \\* for windows to search files in directory
dir_path.append( L"\\*" );
}
FSLib::Directory::Iterator::Iterator( const Directory &d_ ) {
if ( !exists( d_.validPath() ) || !isDirectory( d_.validPath() ) ) {
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"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
++( *this );
}
} else {
ended = true;
}
}
FSLib::Directory::Iterator::~Iterator() {
if ( hFind )
FindClose( hFind );
}
// this is definitely not a good way to create the "end" iterator
// but it was the only way I thought of with my limited knowledge of
// windows.h
FSLib::Directory::Iterator::Iterator( bool ended_ ) : ended( ended_ ) {}
bool FSLib::exists( const string &path ) {
struct _stat path_stat;
return _wstat( path.c_str(), &path_stat ) == 0;
}
string FSLib::canonical( const string &path ) {
char_t *full_path = new char_t[MAX_PATH];
char_t *canonical_path = new char_t[MAX_PATH];
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 ) {
delete[] canonical_path;
return string();
}
string canonical_string{ canonical_path };
delete[] canonical_path;
return canonical_string;
}
bool FSLib::isDirectory( const string &path ) {
struct _stat path_stat;
if ( _wstat( path.c_str(), &path_stat ) != 0 )
return false;
return path_stat.st_mode & _S_IFDIR;
}
bool FSLib::rename( const string &file_a, const string &file_b ) {
return MoveFileExW( file_a.c_str(), file_b.c_str(),
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 );
}
FSLib::Directory::const_iterator FSLib::Directory::end() const {
return Iterator( true );
}
char_t const *FSLib::Directory::Iterator::operator*() const {
return data.cFileName;
}
FSLib::Directory::Iterator &FSLib::Directory::Iterator::operator++() {
if ( ended == true )
return *this;
// skip . and ..
if ( FindNextFileW( hFind, &data ) == 0 ) {
ended = true;
} else if ( !wcscmp( data.cFileName, L"." ) ||
!wcscmp( data.cFileName, L".." ) ) {
return operator++();
}
return *this;
}
bool FSLib::Directory::Iterator::operator==( const Iterator &i_other ) const {
return i_other.ended == ended;
}
string FSLib::getContainingDirectory( const string &path ) {
auto pos = path.find_last_of('\\');
if(pos == string::npos) {
return ".";
}
return path.substr(0, pos);
}
string FSLib::getFileName( const string &path ) {
auto pos = path.find_last_of('\\');
if(pos == string::npos) {
return path;
}
return path.substr(pos+1);
}
string FSLib::getFileExtension( const string &path ) {
auto pos = path.find_last_of('.');
if(pos == string::npos) {
return "";
}
return path.substr(pos + 1);
}

89
functions.cpp Normal file
View File

@ -0,0 +1,89 @@
#include <dlfcn.h>
#include <iostream>
#include "functions.hpp"
#include "filesystem/filesystem.hpp"
std::vector< RenameLibrary > getLibraries(const std::vector<std::tuple<std::string,std::string,std::string>> &libraries) {
// TODO get from config file
std::vector< RenameLibrary > result{};
for ( auto &library : libraries ) {
void *libhndl = dlopen( std::get<0>(library).c_str(), RTLD_NOW );
if ( !libhndl ) {
std::cerr << "Could not load library " << std::get<0>(library) << std::endl;
closeLibraries( result );
return {};
}
RenameLibrary rl;
rl.libhndl = libhndl;
rl.init = ( bool ( * )(const std::string &) )dlsym( libhndl, "init" );
if ( !rl.init ) {
result.push_back( rl );
goto dlsymerror;
}
rl.getOptions = ( std::vector< RenameObject >( * )(
const RenameObject & ) )dlsym( libhndl, "getOptions" );
if ( !rl.getOptions ) {
result.push_back( rl );
goto dlsymerror;
}
rl.renamePath =
( bool ( * )( const std::string &, const RenameObject & ) )dlsym(
libhndl, "renamePath" );
if ( !rl.renamePath ) {
result.push_back( rl );
goto dlsymerror;
}
rl.getCustomKeys = ( std::vector< std::string >( * )() )dlsym(
libhndl, "getCustomKeys" );
if ( !rl.getCustomKeys ) {
result.push_back( rl );
goto dlsymerror;
}
rl.name = std::get<1>(library);
rl.config = std::get<2>(library);
result.push_back( rl );
}
return result;
dlsymerror:
std::cerr << "dlsym: " << dlerror() << std::endl;
closeLibraries( result );
return {};
}
void closeLibraries( std::vector< RenameLibrary > &libraries ) {
for ( auto &library : libraries ) {
dlclose( library.libhndl );
}
}
void addFilesRecursive(const std::string &prefix, std::vector< std::string > &results, const std::string &filename, const std::string &containing_directory, bool dir_only = false ) {
auto path = containing_directory + FSLib::dir_divisor + filename;
if(!dir_only || FSLib::isDirectory(path)) {
results.push_back(prefix + filename);
}
if( FSLib::isDirectory(path) ) {
auto newprefix = prefix + filename + FSLib::dir_divisor;
for(const auto &entry : FSLib::Directory(path)) {
addFilesRecursive(newprefix, results, entry, path, dir_only);
}
}
}
std::vector< std::string > getFilesInSource( const std::string &source_dir ) {
std::vector< std::string > result;
for(const auto &entry : FSLib::Directory(source_dir)) {
addFilesRecursive("", result, entry, source_dir);
}
std::sort(result.begin(), result.end());
return result;
}
std::vector< std::string > getTargetDirectories( const std::string &target_dir ) {
std::vector< std::string > result;
for(const auto &entry : FSLib::Directory(target_dir)) {
addFilesRecursive("", result, entry, target_dir, true);
}
std::sort(result.begin(), result.end());
return result;
}

12
functions.hpp Normal file
View File

@ -0,0 +1,12 @@
#ifndef RENAME_FUNCTIONS_H
#define RENAME_FUNCTIONS_H
#include <vector>
#include "rename_library.hpp"
std::vector< RenameLibrary > getLibraries(const std::vector<std::tuple<std::string,std::string,std::string>> &libraries);
void closeLibraries( std::vector< RenameLibrary > &libraries );
std::vector< std::string > getFilesInSource( const std::string &source_dir );
std::vector< std::string > getTargetDirectories( const std::string &target_dir );
#endif

64
jwt.cpp Normal file
View File

@ -0,0 +1,64 @@
#include "jwt.hpp"
#include <jwt.h>
#include <iostream>
const std::string secret_key = "supersecretkey";
std::string createLoginToken(const std::string &user) {
auto alg = JWT_ALG_HS256;
time_t iat = time(NULL);
time_t exp = iat + 60*60;
std::string result = "";
jwt_t *jwt;
auto ret = jwt_new(&jwt);
if (ret != 0 || jwt == NULL) {
fprintf(stderr, "invalid jwt\n");
goto finish;
}
jwt_add_grant_int(jwt, "iat", iat);
jwt_add_grant_int(jwt, "exp", exp);
jwt_add_grant(jwt, "user", user.c_str());
jwt_add_grant_bool(jwt, "valid", true);
jwt_set_alg(jwt, alg, reinterpret_cast<const unsigned char*>(secret_key.c_str()), secret_key.length());
result = std::string(jwt_encode_str(jwt));
finish:
jwt_free(jwt);
return result;
}
bool verifyJWT(const std::string &token) {
jwt_valid_t *jwt_valid;
jwt_t *jwt;
bool result = false;
auto ret = jwt_valid_new(&jwt_valid, JWT_ALG_HS256);
if (ret != 0 || jwt_valid == NULL) {
fprintf(stderr, "failed to allocate jwt_valid\n");
goto finish_valid;
}
jwt_valid_set_headers(jwt_valid, 1);
jwt_valid_set_now(jwt_valid, time(NULL));
jwt_valid_add_grant_bool(jwt_valid, "valid", true);
ret = jwt_decode(&jwt, token.c_str(), reinterpret_cast<const unsigned char*>(secret_key.c_str()), secret_key.length());
if (ret != 0 || jwt == NULL) {
fprintf(stderr, "invalid jwt\n");
goto finish;
}
if (jwt_validate(jwt, jwt_valid) != 0) {
fprintf(stderr, "jwt failed to validate: %08x\n", jwt_valid_get_status(jwt_valid));
jwt_dump_fp(jwt, stderr, 1);
goto finish;
}
result = true;
std::cout << "USER " << jwt_get_grant(jwt, "user") << " LOGGED IN!" << std::endl;
finished_valid:
finish:
jwt_free(jwt);
finish_valid:
jwt_valid_free(jwt_valid);
return result;
}

9
jwt.hpp Normal file
View File

@ -0,0 +1,9 @@
#ifndef JWT_HPP
#define JWT_HPP
#include <string>
std::string createLoginToken(const std::string &user);
bool verifyJWT(const std::string &token);
#endif

25
library.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef SIMPLE_RENAME_HPP
#define SIMPLE_RENAME_HPP
#include <vector>
#include "rename_object.hpp"
#ifdef _WIN32
using string = std::wstring;
#else
using string = std::string;
#endif
extern "C" {
bool init(const string &configuration);
std::vector< RenameObject > getOptions( const RenameObject &search );
bool renamePath( const string &path, const RenameObject &renamer );
std::vector< string > getCustomKeys();
}
#endif // TV_RENAME_HPP

501
main.cpp Normal file
View File

@ -0,0 +1,501 @@
#include "rename_object.hpp"
#include "functions.hpp"
#include <climits>
#include <iostream>
#include <memory>
#include <restbed>
#include <sstream>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
#include <string>
#include <chrono>
#include "jwt.hpp"
#include "filesystem/filesystem.hpp"
#include "config/config.hpp"
std::vector<RenameLibrary> libraries{};
Configuration cfg;
void sendResponse(const std::string &response, int status_code, const std::shared_ptr< restbed::Session > session) {
session->close(status_code, response, { { "Content-Length", std::to_string(response.length()) }, { "Access-Control-Allow-Origin", "*" } });
}
void performPostFunc(const std::shared_ptr<restbed::Session> session, std::function<void(const std::shared_ptr<restbed::Session>, rapidjson::GenericDocument<rapidjson::UTF8<>>&)> callback) {
const auto request = session->get_request( );
int content_length = request->get_header( "Content-Length", 0 );
session->fetch( content_length, [callback]( const std::shared_ptr< restbed::Session > session, const restbed::Bytes & body )
{
rapidjson::Document doc;
std::cout << std::string(reinterpret_cast<const char*>(body.data()), body.size()) << std::endl;
doc.Parse(reinterpret_cast<const char*>(body.data()), body.size());
if(doc.HasParseError()) {
sendResponse("ERROR: Invalid body!", 401, session);
return;
}
callback(session, doc);
});
}
bool verifyLogin( const std::shared_ptr< restbed::Session > &session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc ) {
std::cout << "WAAA" << std::endl;
if(doc.FindMember("token") == doc.MemberEnd() || !doc["token"].IsString()) {
sendResponse("ERROR: Invalid token!", 401, session);
return false;
}
std::cout << "AAAA" << std::endl;
auto token = doc["token"].GetString();
auto res = verifyJWT(token);
if(!res) {
sendResponse("ERROR: Invalid token!", 401, session);
}
return res;
}
std::vector<std::pair<std::string, size_t>> getTypes() {
std::vector<std::pair<std::string, size_t>> result{};
for(size_t i = 0; i < libraries.size(); i++) {
result.emplace_back(libraries[i].name, i);
}
return result;
}
std::string getTypesJson() {
auto types = getTypes();
std::ostringstream result;
result << "[\n";
if(types.size() > 0) {
for(auto type : types) {
result << " {\n \"id\": " << type.second << "\n";
result << " \"name\": \"" << type.first << "\"\n },\n";
}
result.seekp(-2, std::ios_base::end);
result << "\n";
}
result << "]";
return result.str();
}
void getTypesRest( const std::shared_ptr< restbed::Session > session ) {
sendResponse(getTypesJson(), restbed::OK, session);
}
std::vector< RenameObject > getOptions(const RenameObject &search, size_t type) {
if(type >= libraries.size()) {
return {};
}
auto result = libraries[type].getOptions(search);
for(auto &res : result) {
res.setLibraryId(type);
}
return result;
}
std::string getOptionsJson(const RenameObject &search, size_t type) {
std::ostringstream res;
res << "[\n";
auto options = getOptions(search, type);
if(options.size() > 0) {
for(auto &option : options) {
res << option.toJson();
res << ",\n";
}
res.seekp( -2, std::ios_base::end );
res << "\n";
}
res << "]";
return res.str();
}
void getOptionsRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(doc.HasParseError()) {
sendResponse("ERROR: Invalid body!", 401, session);
return;
}
if(!verifyLogin(session, doc)) {
return;
}
if(doc.FindMember("type") == doc.MemberEnd() || !doc["type"].IsUint64()) {
sendResponse("ERROR: Invalid type!", 401, session);
return;
}
if(doc.FindMember("search") == doc.MemberEnd() || !doc["search"].IsObject()) {
sendResponse("ERROR: Invalid search!", 401, session);
return;
}
size_t type_id = doc["type"].GetUint64();
RenameObject search;
rapidjson::StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
doc["search"].Accept(writer);
std::string s = sb.GetString();
search.fromJson(s);
if(search.getPresentedName().empty()) {
sendResponse("Empty search", 401, session);
return;
}
sendResponse(getOptionsJson(search, type_id), 200, session);
}
std::vector< std::string > getCustomKeys(size_t type) {
if(type >= libraries.size()) {
return {};
}
auto result = libraries[type].getCustomKeys();
return result;
}
std::string getCustomKeysJson(size_t type) {
std::ostringstream res;
res << "[\n";
auto custom_keys = getCustomKeys(type);
if(custom_keys.size() > 0) {
for(auto &key : custom_keys) {
res << "\"" << key << "\",\n";
}
res.seekp( -2, std::ios_base::end );
res << "\n";
}
res << "]";
return res.str();
}
void getCustomKeysRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(doc.FindMember("type") == doc.MemberEnd() || !doc["type"].IsUint64()) {
sendResponse("ERROR: Invalid type!", 401, session);
return;
}
size_t type_id = doc["type"].GetUint64();
sendResponse(getCustomKeysJson(type_id), 200, session);
}
std::pair<bool, std::string> renamePath(std::string path, const RenameObject &renamer) {
if(renamer.getLibraryId() >= libraries.size()) {
return {false, "Invalid library id"};
}
if(path[0] == '/' && path.substr(0, cfg.getSourcePath().length()) != cfg.getSourcePath()) {
return {false, "Invalid path"};
} else if(path.find("..") != std::string::npos) {
return {false, "Path cannot contain '..'"};
} else if(path[0] != '/') {
path = cfg.getSourcePath() + "/" + path;
}
if(!FSLib::exists(path)) {
return {false, "Source doesn't exist"};
}
return {libraries[renamer.getLibraryId()].renamePath(path, renamer), "Library error"};
}
std::string renamePathJson(const std::string &path, const RenameObject &renamer) {
std::ostringstream res;
res << "{\n \"success\": ";
auto rename_result = renamePath(path, renamer);
if(rename_result.first) {
res << "true";
} else {
res << "false";
}
res << "\"\n";
if(!rename_result.first) {
res << " \"error\": \"" << rename_result.second << "\"\n";
}
res << "}";
return res.str();
}
void renamePathRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc ) {
if(!verifyLogin(session, doc)) {
return;
}
if(doc.FindMember("path") == doc.MemberEnd() || !doc["path"].IsString()) {
// TODO validate path, also validate against config
sendResponse("ERROR: Invalid path!", 401, session);
return;
}
if(doc.FindMember("renamer") == doc.MemberEnd() || !doc["renamer"].IsObject()) {
sendResponse("ERROR: Invalid renamer!", 401, session);
return;
}
std::string path = doc["path"].GetString();
RenameObject renamer;
rapidjson::StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
doc["renamer"].Accept(writer);
std::string s = sb.GetString();
renamer.fromJson(s);
sendResponse(renamePathJson(path, renamer), 200, session);
}
std::string getFilesJson() {
std::ostringstream res;
res << "[\n";
auto files = getFilesInSource(cfg.getSourcePath());
if(files.size() > 0) {
for(const auto &file : files) {
res << "\"" << file << "\",\n";
}
res.seekp(-2, std::ios_base::end);
}
res << "\n]";
return res.str();
}
void getFilesRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(!verifyLogin(session, doc)) {
return;
}
sendResponse(getFilesJson(), restbed::OK, session);
}
std::vector<std::pair<uint64_t, std::string>> getTargets() {
std::vector<std::pair<uint64_t, std::string>> result;
const auto &targets = cfg.getTargetPaths();
for(uint64_t i = 0; i < targets.size(); i++) {
result.emplace_back(i, targets[i].second);
}
return result;
}
std::string getTargetsJson() {
std::ostringstream res;
res << "[\n";
auto targets = getTargets();
if(targets.size() > 0) {
for(const auto &target : targets) {
res << " {\n" << " \"id\": " << target.first << "\n";
res << " \"name\": \"" << target.second << "\"\n },\n";
}
res.seekp(-2, std::ios_base::end);
}
res << "\n]";
return res.str();
}
void getTargetsRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(!verifyLogin(session, doc)) {
return;
}
sendResponse(getTargetsJson(), restbed::OK, session);
}
std::string getTargetDirectoriesJson(uint64_t id) {
if(id >= cfg.getTargetPaths().size()) {
return "";
}
std::ostringstream res;
res << "[\n";
auto dirs = getTargetDirectories(cfg.getTargetPaths()[id].first);
if(dirs.size() > 0) {
for(const auto &dir : dirs) {
res << " \"" << dir << "\",\n";
}
res.seekp(-2, std::ios_base::end);
}
res << "\n]";
return res.str();
}
void getTargetDirectoriesRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(!verifyLogin(session, doc)) {
return;
}
uint64_t id = 0;
if(doc.FindMember("path_id") == doc.MemberEnd() || !doc["path_id"].IsUint64() || (id = doc["path_id"].GetUint64()) >= cfg.getTargetPaths().size()) {
sendResponse("ERROR: Invalid path_id!", 401, session);
return;
}
sendResponse(getTargetDirectoriesJson(id), restbed::OK, session);
}
std::pair<bool, std::string> move(std::string path, uint64_t target_id, const std::string &containing_dir) {
if(path[0] == '/' && path.substr(0, cfg.getSourcePath().length()) != cfg.getSourcePath()) {
return {false, "Invalid path"};
} else if(path.find("..") != std::string::npos) {
return {false, "Path cannot contain '..'"};
} else if(path[0] != '/') {
path = cfg.getSourcePath() + "/" + path;
}
if(target_id >= cfg.getTargetPaths().size()) {
return {false, "Invalid target_id"};
}
if(!FSLib::exists(path)) {
return {false, "Source doesn't exist"};
}
auto target_dir = cfg.getTargetPaths()[target_id].first + FSLib::dir_divisor + containing_dir;
if(!FSLib::exists(target_dir)) {
FSLib::createDirectoryFull(target_dir);
}
return {FSLib::rename(path, target_dir + FSLib::dir_divisor + FSLib::getFileName(path)), "Library error"};
}
std::string moveJson(const std::string &path, uint64_t target_id, const std::string &containing_dir) {
std::ostringstream res;
res << "{\n \"success\": ";
auto move_result = move(path, target_id, containing_dir);
if(move_result.first) {
res << "true";
} else {
res << "false";
}
res << "\"\n";
if(!move_result.first) {
res << " \"error\": \"" << move_result.second << "\"\n";
}
res << "}";
return res.str();
}
void moveRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc )
{
if(!verifyLogin(session, doc)) {
return;
}
std::string containing_dir = "";
if(doc.FindMember("path") == doc.MemberEnd() || !doc["path"].IsString()) {
// TODO validate path, also validate against config
sendResponse("ERROR: Invalid path!", 401, session);
return;
}
uint64_t id = 0;
if(doc.FindMember("target_id") == doc.MemberEnd() || !doc["target_id"].IsUint64() || (id = doc["target_id"].GetUint64()) >= cfg.getTargetPaths().size()) {
sendResponse("ERROR: Invalid target_id!", 401, session);
return;
}
if(doc.FindMember("containing_dir") != doc.MemberEnd() && doc["containing_dir"].IsString()) {
containing_dir = doc["containing_dir"].GetString();
}
std::string path = doc["path"].GetString();
// TODO correct response code
sendResponse(moveJson(path, id, containing_dir), 200, session);
}
void loginRest( const std::shared_ptr< restbed::Session > session, rapidjson::GenericDocument<rapidjson::UTF8<>> &doc ) {
if(doc.FindMember("user") == doc.MemberEnd() || !doc["user"].IsString()) {
sendResponse("ERROR: Invalid user!", 401, session);
return;
}
if(doc.FindMember("password") == doc.MemberEnd() || !doc["password"].IsString()) {
sendResponse("ERROR: Invalid password!", 401, session);
return;
}
auto user = doc["user"].GetString();
auto password = doc["password"].GetString();
bool valid = false;
for(auto &user_cfg : cfg.getUsers()) {
if(user_cfg.first == user) {
valid = user_cfg.second == password;
break;
}
}
if(valid) {
sendResponse(createLoginToken(user), 200, session);
} else {
sendResponse("Invalid user/password", 401, session);
}
}
void getOptionsCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, getOptionsRest);
}
void getCustomKeysCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, getCustomKeysRest);
}
void renameCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, renamePathRest);
}
void getFilesCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, getFilesRest);
}
void getTargetsCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, getTargetsRest);
}
void getTargetDirectoriesCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, getTargetDirectoriesRest);
}
void moveCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, moveRest);
}
void loginCall(const std::shared_ptr<restbed::Session> session) {
performPostFunc(session, loginRest);
}
int main(int argc, char **argv) {
cfg.readConfiguration("/etc/renameserver/main.cfg");
libraries = getLibraries(cfg.getLibraries());
for(auto &library : libraries) {
library.init(library.config);
}
restbed::Service service;
auto get_types = std::make_shared< restbed::Resource >();
get_types->set_path("/get_types");
get_types->set_method_handler( "GET", getTypesRest );
service.publish(get_types);
auto search = std::make_shared< restbed::Resource >();
search->set_path("/search");
search->set_method_handler( "POST", getOptionsCall );
service.publish(search);
auto custom_fields = std::make_shared< restbed::Resource >();
custom_fields->set_path("/get_custom_fields");
custom_fields->set_method_handler( "POST", getCustomKeysCall );
service.publish(custom_fields);
auto rename_path = std::make_shared< restbed::Resource >();
rename_path->set_path("/rename");
rename_path->set_method_handler( "POST", renameCall );
service.publish(rename_path);
auto get_files = std::make_shared< restbed::Resource >();
get_files->set_path("/get_files");
get_files->set_method_handler( "POST", getFilesCall );
service.publish(get_files);
auto get_targets = std::make_shared< restbed::Resource >();
get_targets->set_path("/get_targets");
get_targets->set_method_handler( "POST", getTargetsCall );
service.publish(get_targets);
auto get_target_directories = std::make_shared< restbed::Resource >();
get_target_directories->set_path("/get_target_directories");
get_target_directories->set_method_handler( "POST", getTargetDirectoriesCall );
service.publish(get_target_directories);
auto move = std::make_shared< restbed::Resource >();
move->set_path("/move");
move->set_method_handler( "POST", moveCall );
service.publish(move);
auto login = std::make_shared< restbed::Resource >();
login->set_path("/login");
login->set_method_handler( "POST", loginCall );
service.publish(login);
auto settings = std::make_shared< restbed::Settings >();
settings->set_port(1984);
settings->set_default_header( "Connection", "close" );
service.start(settings);
return 0;
}

46
network/network.hpp Normal file
View File

@ -0,0 +1,46 @@
#ifndef NETWORK_HPP
#define NETWORK_HPP
#ifdef _WIN32
#include <Windows.h>
#include <WinInet.h>
#include <string>
using string = std::wstring;
#else
#include <curl/curl.h>
#include <string>
using string = std::string;
#endif
class Request {
public:
Request();
~Request();
std::string get( const string &url );
std::string post( const string &url, const std::string &data );
void addHeader( const string &header );
void clearHeader();
bool initSuccessful();
void setServer( const string &server );
int lastResponseCode();
private:
#ifdef _WIN32
HINTERNET _hInternet;
HINTERNET _hConnect = nullptr;
std::wstring _headers = L"";
int status_code{};
#else
CURL *_curl_handle = nullptr;
struct curl_slist *_chunk = nullptr;
string _server;
#endif
};
#endif

91
network/unix/network.cpp Normal file
View File

@ -0,0 +1,91 @@
#include "../network.hpp"
#include <iostream>
#include <memory>
size_t writeCallback( void *contents, size_t size, size_t nmemb,
void *target ) {
*static_cast< std::string * >( target ) +=
std::string( static_cast< char * >( contents ), size * nmemb );
return size * nmemb;
}
Request::Request() {
curl_global_init( CURL_GLOBAL_ALL );
_curl_handle = curl_easy_init();
if ( _curl_handle == NULL ) {
std::cerr << "ERROR curl_easy_init" << std::endl;
}
curl_easy_setopt( _curl_handle, CURLOPT_WRITEFUNCTION, writeCallback );
curl_easy_setopt( _curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0" );
}
Request::~Request() {
curl_easy_cleanup( _curl_handle );
curl_slist_free_all( _chunk );
curl_global_cleanup();
}
void cleanUp( CURL *curl_handle ) {
curl_easy_setopt( curl_handle, CURLOPT_POST, 0 );
curl_easy_setopt( curl_handle, CURLOPT_POSTFIELDS, "" );
}
std::string Request::get( const std::string &url ) {
// get rid of garbage
cleanUp( _curl_handle );
std::string response;
response.reserve( 100000 );
curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA,
static_cast< void * >( &response ) );
curl_easy_setopt( _curl_handle, CURLOPT_URL, ( _server + url ).c_str() );
curl_easy_setopt( _curl_handle, CURLOPT_HTTPGET, 1 );
auto res = curl_easy_perform( _curl_handle );
if ( res != CURLE_OK ) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror( res )
<< std::endl;
return "";
}
return response;
}
std::string Request::post( const std::string &url, const std::string &data ) {
std::string response;
response.reserve( 100000 );
curl_easy_setopt( _curl_handle, CURLOPT_URL, ( _server + url ).c_str() );
curl_easy_setopt( _curl_handle, CURLOPT_POST, 1 );
curl_easy_setopt( _curl_handle, CURLOPT_POSTFIELDS, data.c_str() );
curl_easy_setopt( _curl_handle, CURLOPT_WRITEDATA,
static_cast< void * >( &response ) );
auto res = curl_easy_perform( _curl_handle );
if ( res != CURLE_OK ) {
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror( res )
<< std::endl;
return "";
}
return response;
}
void Request::addHeader( const std::string &header ) {
_chunk = curl_slist_append( _chunk, header.c_str() );
curl_easy_setopt( _curl_handle, CURLOPT_HTTPHEADER, _chunk );
}
void Request::clearHeader() {
curl_slist_free_all( _chunk );
_chunk = nullptr;
curl_easy_setopt( _curl_handle, CURLOPT_HTTPHEADER, _chunk );
}
bool Request::initSuccessful() {
return _curl_handle != nullptr;
}
void Request::setServer( const string &server ) {
_server = server;
}
int Request::lastResponseCode() {
long code{};
curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &code );
return code;
}

109
network/windows/network.cpp Normal file
View File

@ -0,0 +1,109 @@
#include "../network.hpp"
#include <iostream>
#include <memory>
const wchar_t *acceptTypes[] = { L"text/*", L"application/json", nullptr };
Request::Request() {
// Start connection
_hInternet = InternetOpen( L"WinInet/1.0", INTERNET_OPEN_TYPE_PRECONFIG,
nullptr, nullptr, 0 );
if ( !_hInternet )
std::wcerr << "ERROR InternetOpen: " << GetLastError() << std::endl;
}
Request::~Request() {
if ( _hConnect )
InternetCloseHandle( _hConnect );
if ( _hInternet )
InternetCloseHandle( _hInternet );
}
std::string sendRequest( HINTERNET hRequest, const std::wstring &headers,
const std::string &optional, int &status_code ) {
std::string response{};
response.reserve( 10000 );
// have to do a copy because c_str returns const
std::unique_ptr< char > opt( new char[optional.length()] );
memcpy( opt.get(), optional.c_str(), optional.length() );
auto requestSent =
HttpSendRequest( hRequest, headers.c_str(), headers.length(), opt.get(),
optional.length() );
if ( !requestSent ) {
status_code = -1;
std::wcerr << "ERROR HttpSendRequest: " << GetLastError() << std::endl;
return "";
}
char dataReceived[4096];
unsigned long numberOfBytesRead = 0;
// read while InternetReadFile returns true and reads more than 0 bytes
while (
InternetReadFile( hRequest, dataReceived, 4096, &numberOfBytesRead ) &&
numberOfBytesRead ) {
response.append( dataReceived, numberOfBytesRead );
}
wchar_t responseText[256];
DWORD responseTextSize = sizeof( responseText );
HttpQueryInfo( hRequest, HTTP_QUERY_STATUS_CODE, &responseText,
&responseTextSize, NULL );
status_code = std::stoi( responseText );
return response;
}
std::string Request::get( const std::wstring &url ) {
HINTERNET hRequest =
HttpOpenRequest( _hConnect, L"GET", url.c_str(), nullptr, nullptr,
acceptTypes, INTERNET_FLAG_SECURE, 0 );
if ( !hRequest )
std::wcerr << "ERROR HttpOpenRequest: " << GetLastError() << std::endl;
return sendRequest( hRequest, _headers, "", status_code );
}
std::string Request::post( const std::wstring &url, const std::string &data ) {
HINTERNET hRequest =
HttpOpenRequest( _hConnect, L"POST", url.c_str(), nullptr, nullptr,
acceptTypes, INTERNET_FLAG_SECURE, 0 );
if ( !hRequest )
std::wcerr << "ERROR HttpOpenRequest: " << GetLastError() << std::endl;
return sendRequest( hRequest, _headers, data, status_code );
}
void Request::addHeader( const std::wstring &header ) {
_headers += header + L"\r\n";
}
void Request::clearHeader() {
_headers = L"";
}
bool Request::initSuccessful() {
return _hInternet != nullptr;
}
void Request::setServer( const string &server ) {
// connect to server
_hConnect = InternetConnect( _hInternet, server.c_str(),
INTERNET_DEFAULT_HTTPS_PORT, nullptr, nullptr,
INTERNET_SERVICE_HTTP, 0, 0 );
if ( !_hConnect )
std::wcerr << "ERROR InternetConnect: " << GetLastError() << std::endl;
}
int Request::lastResponseCode() {
#ifndef _WIN32
long code{};
curl_easy_getinfo( _curl_handle, CURLINFO_RESPONSE_CODE, &code );
return code;
#else
return status_code;
#endif
}

17
rename_library.hpp Normal file
View File

@ -0,0 +1,17 @@
#ifndef RENAME_LIBRARY_H
#define RENAME_LIBRARY_H
#include <vector>
#include "rename_object.hpp"
struct RenameLibrary {
bool ( *init )( const std::string & );
std::vector< RenameObject > ( *getOptions )( const RenameObject & );
bool ( *renamePath )( const std::string &, const RenameObject & );
std::vector< std::string > ( *getCustomKeys )();
void *libhndl;
std::string name;
std::string config;
};
#endif

75
rename_object.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "rename_object.hpp"
#include <sstream>
#include <rapidjson/document.h>
#include <iostream>
void RenameObject::setLibraryId( uint64_t id ) {
_library_id = id;
}
uint64_t RenameObject::getLibraryId() const {
return _library_id;
}
void RenameObject::setPresentedName( const std::string &name ) {
_name = name;
}
const std::string &RenameObject::getPresentedName() const {
return _name;
}
void RenameObject::addCustomField( const std::string &key,
const std::string &value ) {
_custom_fields[key] = value;
}
const std::unordered_map< std::string, std::string > &RenameObject::getCustomFields() const {
return _custom_fields;
}
void RenameObject::clearCustomFields() {
_custom_fields.clear();
}
std::string RenameObject::toJson() const {
std::ostringstream result;
result << "{\n \"library_id\": " << _library_id << ",\n";
result << " \"name\": \"" << _name << "\",\n";
result << " \"custom_fields\": {\n";
if ( _custom_fields.size() > 0 ) {
for ( auto &entry : _custom_fields ) {
result << " \"" << entry.first << "\": \"" << entry.second
<< "\",\n";
}
result.seekp( -2, std::ios_base::end );
result << "\n";
}
result << " }\n}";
return result.str();
}
void invalidJson() {
throw "Invalid JSON";
}
void RenameObject::fromJson( const std::string &json ) {
rapidjson::Document doc;
doc.Parse( json.c_str() );
if( doc.HasParseError() ) {
invalidJson();
return;
}
if( doc.FindMember("library_id") != doc.MemberEnd() && doc["library_id"].IsUint64()) {
setLibraryId(doc["library_id"].GetUint64());
}
if( doc.FindMember("name") != doc.MemberEnd() && doc["name"].IsString()) {
setPresentedName(doc["name"].GetString());
}
if( doc.FindMember("custom_fields") != doc.MemberEnd() && doc["custom_fields"].IsObject()) {
auto custom_obj = doc["custom_fields"].GetObject();
for(auto it = custom_obj.begin(); it != custom_obj.end(); it++) {
addCustomField(it->name.GetString(), it->value.GetString());
}
}
}

25
rename_object.hpp Normal file
View File

@ -0,0 +1,25 @@
#ifndef RENAME_OBJECT_H
#define RENAME_OBJECT_H
#include <string>
#include <unordered_map>
class RenameObject {
public:
void setLibraryId( uint64_t id );
uint64_t getLibraryId() const;
void setPresentedName( const std::string &name );
const std::string &getPresentedName() const;
void addCustomField( const std::string &key, const std::string &value );
const std::unordered_map< std::string, std::string > &getCustomFields() const;
void clearCustomFields();
std::string toJson() const;
void fromJson( const std::string &json );
private:
uint64_t _library_id;
std::string _name;
std::unordered_map< std::string, std::string > _custom_fields;
};
#endif

51
simple_rename/simple.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "../filesystem/filesystem.hpp"
#include "../library.hpp"
#include <sstream>
#include <vector>
#include <iostream>
#ifdef _WIN32
#include <windows.h>
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
constexpr const char_t *_tv_rename_dir_divider = L"\\";
#else
constexpr const char_t *_tv_rename_dir_divider = "/";
#endif
bool init(const string &config_path) {
return true;
}
std::vector< RenameObject > getOptions( const RenameObject &/*UNUSED*/ ) {
return {};
}
bool renamePath( const string &path, const RenameObject &renamer ) {
string new_name = "";
if ( renamer.getCustomFields().find( "new_name" ) !=
renamer.getCustomFields().end() ) {
new_name = renamer.getCustomFields().at( "new_name" );
} else {
return false;
}
if ( new_name.find('/') != string::npos || new_name.find('\\') != string::npos ) {
return false;
}
return FSLib::rename( path, FSLib::canonical( FSLib::getContainingDirectory(path) ) + "/" + new_name );
}
std::vector< string > getCustomKeys() {
return { "new_name" };
}

87
themoviedb/functions.cpp Normal file
View File

@ -0,0 +1,87 @@
#include <iomanip>
#include <map>
#include <sstream>
#include <unordered_set>
#include <vector>
#include "functions.hpp"
#ifdef _WIN32
#include <codecvt>
#include <shlobj.h>
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
constexpr const char_t *dir_divider = L"\\";
#else // UNIX
#include <pwd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
constexpr const char_t *dir_divider = "/";
#endif // UNIX
#ifdef __APPLE__
void error( int status, int i_errno, const char *fmt, ... ) {
fprintf( stderr, "%s - ", strerror( i_errno ) );
va_list args;
va_start( args, fmt );
vfprintf( stderr, fmt, args );
va_end( args );
fputc( '\n', stderr );
exit( status );
}
#endif
#ifdef _WIN32
// functions to convert between string and wstring
std::string wstring_to_utf8( const std::wstring &wstring ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.to_bytes( wstring );
}
std::wstring utf8_to_wstring( const std::string &utf8 ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.from_bytes( utf8 );
}
#endif // _WIN32
// encode url so it's valid even with UTF-8 characters
string encodeUrl( const string &url ) {
// stolen from here -
// https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
#ifdef _WIN32
std::wstringstream encoded;
auto url_c = wstring_to_utf8( url );
#else
std::stringstream encoded;
const auto &url_c = url;
#endif
encoded.fill( '0' );
encoded << std::hex;
for ( const auto &x : url_c ) {
if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' ||
x == '_' || x == '.' || x == '~' || x == '+' ) {
encoded << x;
continue;
}
encoded << std::uppercase << '%' << std::setw( 2 );
encoded << int( static_cast< unsigned char >( x ) ) << std::nouppercase;
}
return encoded.str();
}

35
themoviedb/functions.hpp Normal file
View File

@ -0,0 +1,35 @@
#ifndef TV_FUNCTIONS_H
#define TV_FUNCTIONS_H
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "../network/network.hpp"
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
std::wstring utf8_to_wstring( const std::string &utf8 );
#else
using string = std::string;
using char_t = char;
#define TEXT( x ) x
#endif
#ifdef __APPLE__
void error( int status, int i_errno, const char *fmt, ... );
#endif
// CLI functions
string encodeUrl( const string &url );
#endif

200
themoviedb/moviedb.cpp Normal file
View File

@ -0,0 +1,200 @@
// API - 2ebc8e784a4072da457fae5c0d291e48
// API READ ONLY - eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyZWJjOGU3ODRhNDA3MmRhNDU3ZmFlNWMwZDI5MWU0OCIsInN1YiI6IjYwZTJlNGI5MjJlNDgwMDA2MDJmZDMzMyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.c0y7bTCI5KSsfQRw7igPx1FR40mbMF6hGTJTHn0HXH8
#include <algorithm>
#include <iostream>
#include <map>
#include "../filesystem/filesystem.hpp"
#include "functions.hpp"
#include "../library.hpp"
#ifdef _WIN32
#include "rapidjson/document.h"
#else
#include <rapidjson/document.h>
#endif
#include <sstream>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
constexpr const char_t *_tv_rename_dir_divider = L"\\";
#define toString( a ) utf8_to_wstring( a )
#else
constexpr const char_t *_tv_rename_dir_divider = "/";
#define toString( a ) a
#endif
string _moviedb_api_token{};
Request _moviedb_request;
bool init(const string &config_path) {
Request &request = _moviedb_request;
#ifdef _WIN32
request.setServer( TEXT( "api.themoviedb.org/3" ) );
#else
request.setServer( "https://api.themoviedb.org/3" );
#endif
_moviedb_api_token = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyZWJjOGU3ODRhNDA3MmRhNDU3ZmFlNWMwZDI5MWU0OCIsInN1YiI6IjYwZTJlNGI5MjJlNDgwMDA2MDJmZDMzMyIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.c0y7bTCI5KSsfQRw7igPx1FR40mbMF6hGTJTHn0HXH8";
return true;
}
std::vector< std::tuple< string, string, string, string > >
searchMovie( const string &movie, const string &language, const string &year ) {
Request &request = _moviedb_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) + _moviedb_api_token );
auto encoded_show = encodeUrl( movie );
int pages = 0;
int cur_page = 0;
std::vector< std::tuple< string, string, string, string > > ret;
do {
cur_page++;
rapidjson::Document json;
auto request_uri = TEXT( "/search/movie?query=" ) + encoded_show + TEXT("&language=" ) + language + TEXT("&page=") + toString(std::to_string(cur_page));
if(!year.empty()) {
request_uri += TEXT("&year=") + year;
}
json.Parse(
request.get( request_uri ).c_str() );
if ( json.HasParseError() )
return {};
pages = json["total_pages"].GetInt();
const rapidjson::Value &results = json["results"];
if ( !results.IsArray() ) {
return {};
}
// find all possible movies
for ( size_t i = 0; i < results.Size(); i++ ) {
auto movie = toString( results[i]["title"].GetString() );
auto id = toString( std::to_string( results[i]["id"].GetInt() ) );
string year = toString( results[i]["release_date"].GetString() );
string original = toString( results[i]["original_title"].GetString() );
if(year.empty() ) {
year = "0000";
} else {
year = year.substr(0, year.find('-'));
}
ret.emplace_back( movie, id, year, original );
}
} while(cur_page < pages && cur_page < 5);
request.clearHeader();
return ret;
}
RenameObject movieToRenameObject( const std::tuple< string, string, string, string > &movie,
const std::string &language ) {
RenameObject result;
result.setPresentedName( std::get<0>(movie) );
result.addCustomField( "id", std::get<1>(movie) );
result.addCustomField( "language", language );
result.addCustomField( "year", std::get<2>(movie) );
result.addCustomField( "original_title", std::get<3>(movie) );
result.addCustomField( "use_original", "false" );
return result;
}
std::vector< RenameObject > getOptions( const RenameObject &search ) {
string lang = "en-US";
string year = "";
if ( search.getCustomFields().find( "language" ) !=
search.getCustomFields().end() ) {
lang = search.getCustomFields().at( "language" );
}
if ( search.getCustomFields().find( "year" ) !=
search.getCustomFields().end() ) {
year = search.getCustomFields().at( "year" );
}
string name = search.getPresentedName();
auto possibilities = searchMovie( name, lang, year );
std::vector< RenameObject > result{};
for ( auto &movie : possibilities ) {
result.push_back( movieToRenameObject( movie, lang ) );
}
return result;
}
std::pair<string, string> movieFromId( const string &id, const string &language ) {
string uri = "/movie/" + id + "?language=" + language;
Request &request = _moviedb_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) + _moviedb_api_token );
rapidjson::Document json;
json.Parse( request.get( uri ).c_str() );
if( json.HasParseError() ) {
return {"", ""};
}
return {json["title"].GetString(), json["original_title"].GetString()};
}
bool renameMovie(const string &path, const string &name, const string &year) {
return FSLib::rename(path, FSLib::canonical( FSLib::getContainingDirectory(path) ) + "/" + name + " (" + year + ")." + FSLib::getFileExtension(path) );
}
bool renamePath( const string &path, const RenameObject &renamer ) {
string id = "";
string lang = "en-US";
std::pair<string,string> movie = {"",""};
string year = "";
bool use_original = false;
if ( renamer.getCustomFields().find( "language" ) !=
renamer.getCustomFields().end() ) {
lang = renamer.getCustomFields().at( "language" );
}
if ( renamer.getCustomFields().find( "year" ) !=
renamer.getCustomFields().end() ) {
year = renamer.getCustomFields().at( "year" );
}
if ( renamer.getCustomFields().find( "use_original" ) !=
renamer.getCustomFields().end() ) {
auto use = renamer.getCustomFields().at( "use_original" );
use_original = (use == "true" || use == "True" || use == "TRUE");
}
if ( renamer.getCustomFields().find( "id" ) ==
renamer.getCustomFields().end() ||
renamer.getCustomFields().at( "id" ).empty() ) {
auto results = searchMovie( renamer.getPresentedName(), lang, year );
if ( results.empty() )
return false;
id = std::get<1>(results[0]);
movie = { std::get<0>(results[0]), std::get<3>(results[0]) };
year = std::get<2>(results[0]);
} else {
id = renamer.getCustomFields().at( "id" );
movie = movieFromId( id, lang );
}
return renameMovie( path, use_original ? movie.second : movie.first, year );
}
std::vector< string > getCustomKeys() {
return { "id", "language", "year", "original_title", "use_original" };
}

255
thetvdb/functions.cpp Normal file
View File

@ -0,0 +1,255 @@
#include <iomanip>
#include <map>
#include <sstream>
#include <unordered_set>
#include <vector>
#include "../filesystem/filesystem.hpp"
#include "functions.hpp"
#ifdef _WIN32
#include <codecvt>
#include <shlobj.h>
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
constexpr const char_t *dir_divider = L"\\";
#else // UNIX
#include <pwd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
constexpr const char_t *dir_divider = "/";
#endif // UNIX
#ifdef __APPLE__
void error( int status, int i_errno, const char *fmt, ... ) {
fprintf( stderr, "%s - ", strerror( i_errno ) );
va_list args;
va_start( args, fmt );
vfprintf( stderr, fmt, args );
va_end( args );
fputc( '\n', stderr );
exit( status );
}
#endif
#ifdef _WIN32
// functions to convert between string and wstring
std::string wstring_to_utf8( const std::wstring &wstring ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.to_bytes( wstring );
}
std::wstring utf8_to_wstring( const std::string &utf8 ) {
std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > wconv;
return wconv.from_bytes( utf8 );
}
#endif // _WIN32
// encode url so it's valid even with UTF-8 characters
string encodeUrl( const string &url ) {
// stolen from here -
// https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
#ifdef _WIN32
std::wstringstream encoded;
auto url_c = wstring_to_utf8( url );
#else
std::stringstream encoded;
const auto &url_c = url;
#endif
encoded.fill( '0' );
encoded << std::hex;
for ( const auto &x : url_c ) {
if ( isalnum( static_cast< unsigned char >( x ) ) || x == '-' ||
x == '_' || x == '.' || x == '~' || x == '+' ) {
encoded << x;
continue;
}
encoded << std::uppercase << '%' << std::setw( 2 );
encoded << int( static_cast< unsigned char >( x ) ) << std::nouppercase;
}
return encoded.str();
}
// return true if file contains S[0-9]+E[0-9]+, set
// season_pos to start of season number and ep_pos to start of episode number
bool searchSeason( const char_t *const path, size_t &season_pos,
size_t &ep_pos ) {
size_t cur_pos{};
while ( path[cur_pos] != '\0' ) {
if ( ( path[cur_pos] == 's' || path[cur_pos] == 'S' ) &&
iswdigit( path[cur_pos + 1] ) ) {
cur_pos++;
if ( path[cur_pos] == '\0' )
return false;
season_pos = cur_pos; // after ++ because we want the first pos to
// point to season's number
while ( iswdigit( path[cur_pos] ) )
cur_pos++;
if ( path[cur_pos] == '\0' )
return false;
if ( ( path[cur_pos] == 'e' || path[cur_pos] == 'E' ) &&
iswdigit( path[cur_pos + 1] ) ) {
ep_pos = cur_pos + 1;
return true;
}
}
cur_pos++;
}
return false;
}
void iterateFS( std::map< int, std::map< int, string > > &seasons,
const string &path ) {
// season_pos - position of first digit of the season number
// ep_pos - position of first digit of the episode number
size_t season_pos{ string::npos };
size_t ep_pos{ string::npos };
if ( FSLib::isDirectory( path ) ) {
for ( const auto p : FSLib::Directory( path ) ) {
// if p is directory, iterate through it
if ( FSLib::isDirectory( path + dir_divider + p ) ) {
iterateFS( seasons, path + dir_divider + p );
continue;
}
// if file is a correct format, add it to file list
// for its season
if ( searchSeason( p, season_pos, ep_pos ) )
seasons[std::stoi( p + season_pos )][std::stoi( p + ep_pos )] =
path + dir_divider + p;
}
} else if ( searchSeason( path.c_str(), season_pos, ep_pos ) ) {
seasons[std::stoi( path.c_str() + season_pos )][std::stoi( path.c_str() + ep_pos )] = path;
}
}
// create file name based on given pattern
string compilePattern( const string &pattern, int season, int episode,
const string &filename, const string &episodeName,
const string &showName ) {
string output;
#ifdef _WIN32
auto season_num = std::to_wstring( season );
auto ep_num = std::to_wstring( episode );
#else
auto season_num = std::to_string( season );
auto ep_num = std::to_string( episode );
#endif
for ( size_t i = 0; i < pattern.size(); i++ ) {
// if current character is % check if a pattern follows, otherwise
// put %
if ( pattern[i] == '%' ) {
// check for numbers right after % indicating size of zero
// padding for numbers
auto pos = pattern.find_first_not_of( TEXT( "0123456789" ), i + 1 );
if ( pattern.find( TEXT( "season" ), pos - 1 ) == pos &&
pos != i + 1 ) {
// if season is AFTER numbers, put season number padded
// with zeros
// get number of leading zeros
auto leading = std::stoi( pattern.c_str() + i + 1 );
// move i to the last char of 'season'
i = pos + 5;
// get number of zeros to be put before the season number
leading -= season_num.size();
if ( leading < 0 )
leading = 0;
// add padded season to output
output += string( leading, '0' ) + season_num;
} else if ( pattern.find( TEXT( "season" ), i ) == i + 1 ) {
// if season isn't after numbers, just put season number to
// output
i += 6;
output += season_num;
} else if ( pattern.find( TEXT( "episode" ), pos - 1 ) == pos &&
pos != i + 1 ) {
// same principle as with season after number
auto leading = std::stoi( pattern.c_str() + i + 1 );
i = pos + 6;
leading -= ep_num.size();
if ( leading < 0 )
leading = 0;
output += string( leading, '0' ) + ep_num;
} else if ( pattern.find( TEXT( "episode" ), i ) == i + 1 ) {
// if episode isn't after number, just put the episode number to
// output
i += 7;
output += ep_num;
} else if ( pattern.find( TEXT( "epname" ), i ) == i + 1 ) {
// episode name from thetvdb
i += 6;
output += episodeName;
} else if ( pattern.find( TEXT( "show" ), i ) == i + 1 ) {
// show name from thetvdb
i += 4;
output += showName;
} else if ( pattern.find( TEXT( "filename" ), i ) == i + 1 ) {
// original file name
i += 8;
output += filename;
} else {
// output % if no escape sequence was found
output += '%';
}
} else if ( pattern[i] == '\\' ) {
// possibility to escape %
if ( pattern[i + 1] == '%' ) {
output += '%';
i++;
} else if ( pattern[i + 1] == '\\' ) {
output += '\\';
i++;
} else {
output += '\\';
}
} else {
// if char isn't % or / just add it to the output string
output += pattern[i];
}
}
return output;
}
/* change names of original files to generated new names
* orig - original filenames
* renamed - renamed filenames (sorted in the same order as `orig`)
*/
void renameFiles( const std::vector< std::tuple< int, string, string, string > >
&renamed_files ) {
for ( const auto &renamed : renamed_files ) {
FSLib::rename(
std::get< 1 >( renamed ) + dir_divider + std::get< 2 >( renamed ),
std::get< 1 >( renamed ) + dir_divider + std::get< 3 >( renamed ) );
}
}
// TODO read config
const string getDefaultPattern() {
return "S%2seasonE%2episode - %epname";
}

44
thetvdb/functions.hpp Normal file
View File

@ -0,0 +1,44 @@
#ifndef TV_FUNCTIONS_H
#define TV_FUNCTIONS_H
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "../network/network.hpp"
#ifdef _WIN32
using string = std::wstring;
using char_t = wchar_t;
std::wstring utf8_to_wstring( const std::string &utf8 );
#else
using string = std::string;
using char_t = char;
#define TEXT( x ) x
#endif
#ifdef __APPLE__
void error( int status, int i_errno, const char *fmt, ... );
#endif
// CLI functions
const string getDefaultPattern();
string encodeUrl( const string &url );
void iterateFS( std::map< int, std::map< int, string > > &seasons,
const string &path );
string compilePattern( const string &pattern, int season, int episode,
const string &filename, const string &episodeName,
const string &showName );
#endif

414
thetvdb/tv_rename.cpp Normal file
View File

@ -0,0 +1,414 @@
#include <algorithm>
#include <iostream>
#include <map>
#include "../filesystem/filesystem.hpp"
#include "functions.hpp"
#include "../library.hpp"
#ifdef _WIN32
#include "rapidjson/document.h"
#else
#include <rapidjson/document.h>
#endif
#include <sstream>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#include <codecvt>
#include <fcntl.h>
#include <io.h>
#include <locale>
constexpr const char_t *_tv_rename_dir_divider = L"\\";
#define cout std::wcout
#define cerr std::wcerr
#define cin std::wcin
#define toString( a ) utf8_to_wstring( a )
#else
constexpr const char_t *_tv_rename_dir_divider = "/";
#define cout std::cout
#define cerr std::cerr
#define cin std::cin
#define toString( a ) a
#endif
// TV flags
#define TV_CHDIR 0x0100
#define TV_TRUST 0x0200
#define TV_LINUX 0x0400
#define TV_DVD 0x0800
string _tv_rename_api_token{};
Request _tv_rename_request;
bool authenticate( const std::string &api_key ) {
Request &request = _tv_rename_request;
#ifdef _WIN32
request.setServer( TEXT( "api.thetvdb.com" ) );
#else
request.setServer( "https://api.thetvdb.com" );
#endif
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Content-Type: application/json" ) );
rapidjson::Document json;
json.Parse(
request.post( TEXT( "/login" ), "{ \"apikey\": \"" + api_key + "\" }" )
.c_str() );
if ( json.HasParseError() )
return false;
_tv_rename_api_token = toString( json["token"].GetString() );
request.clearHeader();
// TODO check return code
return true;
}
bool init(const string &config_path) {
return authenticate( "42B66F5E-C6BF-423F-ADF9-CC97163472F6" );
}
std::vector< std::pair< string, string > >
searchShow( const string &show, const string &language ) {
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.addHeader( TEXT( "Accept-Language: " ) + language );
auto encoded_show = encodeUrl( show );
rapidjson::Document json;
json.Parse(
request.get( TEXT( "/search/series?name=" ) + encoded_show ).c_str() );
if ( json.HasParseError() )
return {};
const rapidjson::Value &results = json["data"];
if ( !results.IsArray() ) {
return {};
}
std::vector< std::pair< string, string > > ret;
// find all possible shows
for ( size_t i = 0; i < results.Size(); i++ ) {
auto show = toString( results[i]["seriesName"].GetString() );
auto id = toString( std::to_string( results[i]["id"].GetInt() ) );
ret.emplace_back( show, id );
}
request.clearHeader();
return ret;
}
// get names for all episodes for a given season
std::vector< string > getEpisodeNames( const string &id, const string &season,
const string &language,
bool dvd = false ) {
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
request.addHeader( TEXT( "Accept-Language: " ) + language );
string baseurl = TEXT( "/series/" ) + id + TEXT( "/episodes/query?" );
if ( dvd )
baseurl += TEXT( "dvdSeason=" );
else
baseurl += TEXT( "airedSeason=" );
baseurl += season;
baseurl += TEXT( "&page=" );
string page = TEXT( "1" );
std::vector< string > episodes;
do {
episodes.resize( episodes.size() * 2 );
rapidjson::Document json;
json.Parse( request.get( baseurl + page ).c_str() );
if ( json.HasParseError() )
return {};
if ( json.FindMember( "data" ) == json.MemberEnd() )
break;
if ( json["data"].IsArray() ) {
rapidjson::Value &epdata = json["data"];
if ( episodes.size() < epdata.Size() )
episodes.resize( epdata.Size() );
for ( size_t i = 0; i < epdata.Size(); i++ ) {
if ( epdata[i]["episodeName"].IsString() ) {
size_t index = -1;
if ( dvd )
index = epdata[i]["dvdEpisodeNumber"].GetInt();
else
index = epdata[i]["airedEpisodeNumber"].GetInt();
if ( index == static_cast< size_t >( -1 ) )
continue;
if ( index > episodes.size() )
episodes.resize( index );
index--;
episodes[index] =
toString( epdata[i]["episodeName"].GetString() );
// some eps have whitespace at the end
while ( isspace( episodes[index].back() ) )
episodes[index].pop_back();
}
}
}
if ( json["links"]["next"].IsNull() )
break;
page = toString( std::to_string( json["links"]["next"].GetInt() ) );
} while ( 1 );
request.clearHeader();
return episodes;
}
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 ) {
auto season_num = toString( std::to_string( season ) );
auto episodes = getEpisodeNames( id, season_num, language, dvd );
if ( episodes.empty() )
return {};
if ( files.empty() )
return {};
// vector of pairs <ep_num,dir>,<original_name,new_name>
std::vector< std::tuple< int, string, string, string > > renamed_files;
for ( const auto &x : files ) {
auto last = x.second.find_last_of( _tv_rename_dir_divider );
string og_name;
string dir;
if ( last == string::npos ) {
og_name = x.second;
dir = TEXT( "." );
} else {
og_name = x.second.substr( last + 1 );
dir = x.second.substr( 0, last );
}
unsigned long ep_num = x.first - 1;
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_without_extension,
episodes[ep_num], show ) +
og_name_extension;
// replace '/' with '|'
for ( size_t i = 0; i < name.size(); i++ ) {
if ( name[i] == '/' ) {
name[i] = '|';
}
}
// replace characters illegal in windows if desired
if ( !unix_names ) {
name.erase( std::remove_if( name.begin(), name.end(),
[]( char_t x ) {
return x == '?' || x == '"' ||
x == '\\' || x == '*';
} ),
name.end() );
size_t max{ name.size() };
for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == '|' ) {
name[i] = '-';
} else if ( name[i] == '<' ) {
name.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
} else if ( name[i] == '>' ) {
name.erase( i, 1 );
name.insert( i, TEXT( "" ) );
max = name.size();
} else if ( name[i] == ':' ) {
name[i] = ' ';
name.insert( i + 1, TEXT( "- " ) );
max = name.size();
}
}
for ( size_t i = 0; i < max; i++ ) {
if ( name[i] == ' ' && name[i + 1] == ' ' ) {
name.erase( i, 1 );
max--;
i--;
}
}
}
renamed_files.emplace_back( x.first, dir, og_name, name );
}
}
return renamed_files;
}
string showFromId( const string &id ) {
Request &request = _tv_rename_request;
request.addHeader( TEXT( "Accept: application/json" ) );
request.addHeader( TEXT( "Authorization: Bearer " ) +
_tv_rename_api_token );
rapidjson::Document json;
json.Parse( request.get( TEXT( "/series/" ) + id ).c_str() );
if ( json.HasParseError() ) {
return "";
}
return json["data"]["seriesName"].GetString();
}
void singleSeason( const string &path, const string &show, int season,
string id, const string &language, const string &pattern,
const bool &unix_names, const bool &trust,
std::map< int, string > *files_ptr, bool dvd ) {
std::map< int, std::map< int, string > > *found_files = nullptr;
if ( files_ptr == nullptr ) {
found_files = new std::map< int, std::map< int, string > >;
iterateFS( *found_files, path );
if ( found_files->find( season ) != found_files->end() )
files_ptr = &( *found_files )[season];
}
if ( files_ptr == nullptr ) {
return;
}
auto renamed_files = getRenamedFiles( show, season, id, language, pattern,
unix_names, *files_ptr, dvd );
if ( renamed_files.empty() )
goto end;
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 +
std::get< 3 >( renamed ) );
if ( found_files == nullptr ) {
files_ptr[0][std::get< 0 >( renamed )] = std::get< 1 >( renamed ) +
_tv_rename_dir_divider +
std::get< 3 >( renamed );
}
}
end:
if ( found_files != nullptr ) {
delete found_files;
}
}
void allSeasons( const string &id, 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
iterateFS( seasons, path );
for ( auto &x : seasons ) {
singleSeason( path, show, x.first, id, language, pattern,
flags & TV_LINUX, flags & TV_TRUST, &x.second,
flags & TV_DVD );
}
}
RenameObject showToRenameObject( const std::pair< string, string > &show,
const std::string &language ) {
RenameObject result;
result.setPresentedName( show.first );
result.addCustomField( "id", show.second );
result.addCustomField( "language", language );
result.addCustomField( "order", "aired" );
result.addCustomField( "pattern", getDefaultPattern() );
return result;
}
std::vector< RenameObject > getOptions( const RenameObject &search ) {
string lang = "en";
if ( search.getCustomFields().find( "language" ) !=
search.getCustomFields().end() ) {
lang = search.getCustomFields().at( "language" );
}
string name = search.getPresentedName();
auto possibilities = searchShow( name, lang );
std::vector< RenameObject > result{};
for ( auto &show : possibilities ) {
result.push_back( showToRenameObject( show, lang ) );
}
return result;
}
bool renamePath( const string &path, const RenameObject &renamer ) {
string id = "";
string lang = "en";
string pattern = getDefaultPattern();
string show = "";
size_t flags = TV_TRUST;
if ( renamer.getCustomFields().find( "language" ) !=
renamer.getCustomFields().end() ) {
lang = renamer.getCustomFields().at( "language" );
}
if ( renamer.getCustomFields().find( "id" ) ==
renamer.getCustomFields().end() ||
renamer.getCustomFields().at( "id" ).empty() ) {
auto results = searchShow( renamer.getPresentedName(), lang );
if ( results.empty() )
return false;
id = results[0].second;
show = results[0].first;
} else if( renamer.getCustomFields().find("id") != renamer.getCustomFields().end()) {
id = renamer.getCustomFields().at( "id" );
show = showFromId( id );
}
if ( renamer.getCustomFields().find( "pattern" ) !=
renamer.getCustomFields().end() ) {
pattern = renamer.getCustomFields().at( "pattern" );
}
if ( renamer.getCustomFields().find( "order" ) !=
renamer.getCustomFields().end() &&
renamer.getCustomFields().at( "order" ) == "dvd" ) {
flags |= TV_DVD;
}
allSeasons( id, path, show, lang, pattern, flags );
return true;
}
std::vector< string > getCustomKeys() {
return { "id", "language", "pattern", "order" };
}