Initial
This commit is contained in:
commit
9413214fce
43
CMakeLists.txt
Normal file
43
CMakeLists.txt
Normal 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
50
config/config.cpp
Normal 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
21
config/config.hpp
Normal 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
126
filesystem/filesystem.hpp
Normal 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
|
145
filesystem/unix/filesystem.cpp
Normal file
145
filesystem/unix/filesystem.cpp
Normal 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);
|
||||
}
|
156
filesystem/windows/filesystem.cpp
Normal file
156
filesystem/windows/filesystem.cpp
Normal 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
89
functions.cpp
Normal 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
12
functions.hpp
Normal 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
64
jwt.cpp
Normal 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
9
jwt.hpp
Normal 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
25
library.hpp
Normal 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
501
main.cpp
Normal 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
46
network/network.hpp
Normal 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
91
network/unix/network.cpp
Normal 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
109
network/windows/network.cpp
Normal 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
17
rename_library.hpp
Normal 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
75
rename_object.cpp
Normal 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
25
rename_object.hpp
Normal 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
51
simple_rename/simple.cpp
Normal 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
87
themoviedb/functions.cpp
Normal 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
35
themoviedb/functions.hpp
Normal 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
200
themoviedb/moviedb.cpp
Normal 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
255
thetvdb/functions.cpp
Normal 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
44
thetvdb/functions.hpp
Normal 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
414
thetvdb/tv_rename.cpp
Normal 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" };
|
||||
}
|
Loading…
Reference in New Issue
Block a user