diff --git a/include/book.h b/include/book.h new file mode 100644 index 000000000..434ba745e --- /dev/null +++ b/include/book.h @@ -0,0 +1,124 @@ +/* + * Copyright 2011 Emmanuel Engelhart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef KIWIX_BOOK_H +#define KIWIX_BOOK_H + +#include + +namespace pugi { +class xml_node; +} + +namespace kiwix +{ +enum supportedIndexType { UNKNOWN, XAPIAN }; + +class OPDSDumper; +class Reader; + +/** + * A class to store information about a book (a zim file) + */ +class Book +{ + public: + Book(); + ~Book(); + + bool update(const Book& other); + void update(const Reader& reader); + void updateFromXml(const pugi::xml_node& node, const std::string& baseDir); + void updateFromOpds(const pugi::xml_node& node); + std::string getHumanReadableIdFromPath(); + + bool readOnly() const { return m_readOnly; } + const std::string& getId() const { return m_id; } + const std::string& getPath() const { return m_path; } + bool isPathValid() const { return m_pathValid; } + const std::string& getIndexPath() const { return m_indexPath; } + const supportedIndexType& getIndexType() const { return m_indexType; } + const std::string& getTitle() const { return m_title; } + const std::string& getDescription() const { return m_description; } + const std::string& getLanguage() const { return m_language; } + const std::string& getCreator() const { return m_creator; } + const std::string& getPublisher() const { return m_publisher; } + const std::string& getDate() const { return m_date; } + const std::string& getUrl() const { return m_url; } + const std::string& getName() const { return m_name; } + const std::string& getTags() const { return m_tags; } + const std::string& getOrigId() const { return m_origId; } + const uint64_t& getArticleCount() const { return m_articleCount; } + const uint64_t& getMediaCount() const { return m_mediaCount; } + const uint64_t& getSize() const { return m_size; } + const std::string& getFavicon() const { return m_favicon; } + const std::string& getFaviconMimeType() const { return m_faviconMimeType; } + const std::string& getDownloadId() const { return m_downloadId; } + + void setReadOnly(bool readOnly) { m_readOnly = readOnly; } + void setId(const std::string& id) { m_id = id; } + void setPath(const std::string& path); + void setPathValid(bool valid) { m_pathValid = valid; } + void setIndexPath(const std::string& indexPath); + void setIndexType(supportedIndexType indexType) { m_indexType = indexType;} + void setTitle(const std::string& title) { m_title = title; } + void setDescription(const std::string& description) { m_description = description; } + void setLanguage(const std::string& language) { m_language = language; } + void setCreator(const std::string& creator) { m_creator = creator; } + void setPublisher(const std::string& publisher) { m_publisher = publisher; } + void setDate(const std::string& date) { m_date = date; } + void setUrl(const std::string& url) { m_url = url; } + void setName(const std::string& name) { m_name = name; } + void setTags(const std::string& tags) { m_tags = tags; } + void setOrigId(const std::string& origId) { m_origId = origId; } + void setArticleCount(uint64_t articleCount) { m_articleCount = articleCount; } + void setMediaCount(uint64_t mediaCount) { m_mediaCount = mediaCount; } + void setSize(uint64_t size) { m_size = size; } + void setFavicon(const std::string& favicon) { m_favicon = favicon; } + void setFaviconMimeType(const std::string& faviconMimeType) { m_faviconMimeType = faviconMimeType; } + void setDownloadId(const std::string& downloadId) { m_downloadId = downloadId; } + + protected: + std::string m_id; + std::string m_downloadId; + std::string m_path; + bool m_pathValid; + std::string m_indexPath; + supportedIndexType m_indexType; + std::string m_title; + std::string m_description; + std::string m_language; + std::string m_creator; + std::string m_publisher; + std::string m_date; + std::string m_url; + std::string m_name; + std::string m_tags; + std::string m_origId; + uint64_t m_articleCount; + uint64_t m_mediaCount; + bool m_readOnly; + uint64_t m_size; + std::string m_favicon; + std::string m_faviconMimeType; +}; + +} + +#endif diff --git a/include/common/base64.h b/include/common/base64.h index 65d5db8b2..dc13f8a4f 100644 --- a/include/common/base64.h +++ b/include/common/base64.h @@ -1,4 +1,4 @@ #include -std::string base64_encode(unsigned char const* , unsigned int len); -std::string base64_decode(std::string const& s); +std::string base64_encode(const std::string& inString); +std::string base64_decode(const std::string& s); diff --git a/include/common/networkTools.h b/include/common/networkTools.h index 6ab2e1e56..4281860f5 100644 --- a/include/common/networkTools.h +++ b/include/common/networkTools.h @@ -20,30 +20,14 @@ #ifndef KIWIX_NETWORKTOOLS_H #define KIWIX_NETWORKTOOLS_H -#ifdef _WIN32 -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include -#endif - -#include #include #include -#include namespace kiwix { std::map getNetworkInterfaces(); std::string getBestPublicIp(); +std::string download(const std::string& url); } #endif diff --git a/include/common/otherTools.h b/include/common/otherTools.h index 9b3102f9b..212567f86 100644 --- a/include/common/otherTools.h +++ b/include/common/otherTools.h @@ -26,9 +26,12 @@ #include #endif +#include + namespace kiwix { void sleep(unsigned int milliseconds); +std::string nodeToString(pugi::xml_node node); } #endif diff --git a/include/common/pathTools.h b/include/common/pathTools.h index c63fad275..1ea7ae4e0 100644 --- a/include/common/pathTools.h +++ b/include/common/pathTools.h @@ -59,5 +59,6 @@ bool copyFile(const string& sourcePath, const string& destPath); string getLastPathElement(const string& path); string getExecutablePath(); string getCurrentDirectory(); +string getDataDirectory(); bool writeTextFile(const string& path, const string& content); #endif diff --git a/include/common/stringTools.h b/include/common/stringTools.h index bd510e316..1c8a29bf5 100644 --- a/include/common/stringTools.h +++ b/include/common/stringTools.h @@ -35,8 +35,8 @@ namespace kiwix { #ifndef __ANDROID__ -std::string beautifyInteger(const unsigned int number); -std::string beautifyFileSize(const unsigned int number); +std::string beautifyInteger(uint64_t number); +std::string beautifyFileSize(uint64_t number); void printStringInHexadecimal(const char* s); void printStringInHexadecimal(icu::UnicodeString s); void stringReplacement(std::string& str, @@ -64,6 +64,12 @@ std::string lcFirst(const std::string& word); std::string toTitle(const std::string& word); std::string normalize(const std::string& word); +template +std::string to_string(T value) +{ + std::ostringstream oss; + oss << value; + return oss.str(); } - +} //namespace kiwix #endif diff --git a/include/downloader.h b/include/downloader.h index a8b373cd6..1fa439f25 100644 --- a/include/downloader.h +++ b/include/downloader.h @@ -21,15 +21,15 @@ #define KIWIX_DOWNLOADER_H #include -#ifdef ENABLE_LIBARIA2 -# include -#endif +#include +#include #include +#include namespace kiwix { - +class Aria2; struct DownloadedFile { DownloadedFile() : success(false) {} @@ -37,6 +37,46 @@ struct DownloadedFile { std::string path; }; +class AriaError : public std::runtime_error { + public: + AriaError(const std::string& message) : std::runtime_error(message) {} +}; + + +class Download { + public: + typedef enum { K_ACTIVE, K_WAITING, K_PAUSED, K_ERROR, K_COMPLETE, K_REMOVED, K_UNKNOWN } StatusResult; + + Download() : + m_status(K_UNKNOWN) {} + Download(std::shared_ptr p_aria, std::string did) + : mp_aria(p_aria), + m_status(K_UNKNOWN), + m_did(did) {}; + void updateStatus(bool follow=false); + StatusResult getStatus() { return m_status; } + std::string getDid() { return m_did; } + std::string getFollowedBy() { return m_followedBy; } + uint64_t getTotalLength() { return m_totalLength; } + uint64_t getCompletedLength() { return m_completedLength; } + uint64_t getDownloadSpeed() { return m_downloadSpeed; } + uint64_t getVerifiedLength() { return m_verifiedLength; } + std::string getPath() { return m_path; } + std::vector& getUris() { return m_uris; } + + protected: + std::shared_ptr mp_aria; + StatusResult m_status; + std::string m_did = ""; + std::string m_followedBy = ""; + uint64_t m_totalLength; + uint64_t m_completedLength; + uint64_t m_downloadSpeed; + uint64_t m_verifiedLength; + std::vector m_uris; + std::string m_path; +}; + /** * A tool to download things. * @@ -45,28 +85,19 @@ class Downloader { public: Downloader(); - ~Downloader(); + virtual ~Downloader(); - /** - * Download a content. - * - * @param url the url to download - * @return the content downloaded. - */ - DownloadedFile download(const std::string& url); + void close(); + + Download* startDownload(const std::string& uri); + Download* getDownload(const std::string& did); + + size_t getNbDownload() { return m_knownDownloads.size(); } + std::vector getDownloadIds(); private: - static pthread_mutex_t globalLock; - - std::string tmpDir; -#ifdef ENABLE_LIBARIA2 - DownloadedFile* fileHandle; - aria2::Session* session; - static int downloadEventCallback(aria2::Session* session, - aria2::DownloadEvent event, - aria2::A2Gid gid, - void* userData); -#endif + std::map> m_knownDownloads; + std::shared_ptr mp_aria; }; } diff --git a/include/library.h b/include/library.h index b572b70da..bf919ccc0 100644 --- a/include/library.h +++ b/include/library.h @@ -20,78 +20,31 @@ #ifndef KIWIX_LIBRARY_H #define KIWIX_LIBRARY_H -#include -#include -#include -#include #include #include - -#include "common/regexTools.h" -#include "common/stringTools.h" +#include #define KIWIX_LIBRARY_VERSION "20110515" -using namespace std; - namespace kiwix { -enum supportedIndexType { UNKNOWN, XAPIAN }; +class Book; +class OPDSDumper; -/** - * A class to store information about a book (a zim file) - */ -class Book -{ - public: - Book(); - ~Book(); - - static bool sortByLastOpen(const Book& a, const Book& b); - static bool sortByTitle(const Book& a, const Book& b); - static bool sortBySize(const Book& a, const Book& b); - static bool sortByDate(const Book& a, const Book& b); - static bool sortByCreator(const Book& a, const Book& b); - static bool sortByPublisher(const Book& a, const Book& b); - static bool sortByLanguage(const Book& a, const Book& b); - string getHumanReadableIdFromPath(); - - string id; - string path; - string pathAbsolute; - string last; - string indexPath; - string indexPathAbsolute; - supportedIndexType indexType; - string title; - string description; - string language; - string creator; - string publisher; - string date; - string url; - string name; - string tags; - string origId; - string articleCount; - string mediaCount; - bool readOnly; - string size; - string favicon; - string faviconMimeType; -}; - +enum supportedListSortBy { UNSORTED, TITLE, SIZE, DATE, CREATOR, PUBLISHER }; +enum supportedListMode { ALL, REMOTE, LOCAL }; /** * A Library store several books. */ class Library { + std::map books; public: Library(); ~Library(); - string version; + std::string version; /** * Add a book to the library. * @@ -104,26 +57,99 @@ class Library */ bool addBook(const Book& book); + Book& getBookById(const std::string& id); + /** * Remove a book from the library. * - * @param bookIndex the index of the book to remove. - * @return True + * @param id the id of the book to remove. + * @return True if the book were in the lirbrary and has been removed. */ - bool removeBookByIndex(const unsigned int bookIndex); - vector books; + bool removeBookById(const std::string& id); - /* - * 'current' is the variable storing the current content/book id - * in the library. This is used to be able to load per default a - * content. As Kiwix may work with many library XML files, you may - * have "current" defined many time with different values. The - * last XML file read has the priority, Although we do not have an - * library object for each file, we want to be able to fallback to - * an 'old' current book if the one which should be load - * failed. That is the reason why we need a stack here + /** + * Write the library to a file. + * + * @param path the path of the file to write to. + * @return True if the library has been correctly save. */ - stack current; + bool writeToFile(const std::string& path); + + /** + * Get the number of book in the library. + * + * @param localBooks If we must count local books (books with a path). + * @param remoteBooks If we must count remote books (books with an url) + * @return The number of books. + */ + unsigned int getBookCount(const bool localBooks, const bool remoteBooks); + + /** + * Get all langagues of the books in the library. + * + * @return A list of languages. + */ + std::vector getBooksLanguages(); + + /** + * Get all book creators of the books in the library. + * + * @return A list of book creators. + */ + std::vector getBooksCreators(); + + /** + * Get all book publishers of the books in the library. + * + * @return A list of book publishers. + */ + std::vector getBooksPublishers(); + + /** + * Get all book ids of the books in the library. + * + * @return A list of book ids. + */ + std::vector getBooksIds(); + + /** + * Filter the library and generate a new one with the keep elements. + * + * This is equivalent to `listBookIds(ALL, UNSORTED, search)`. + * + * @param search List only books with search in the title or description. + * @return The list of bookIds corresponding to the query. + */ + std::vector filter(const std::string& search); + + + /** + * List books in the library. + * + * @param mode The mode of listing : + * - ALL list all books. + * (LOCAL and REMOTE. Other filters are applied). + * - LOCAL list only local books. + * - REMOTE list only remote books. + * @param sortBy Attribute to sort by the book list. + * @param search List only books with search in the title, description. + * @param language List only books in this language. + * @param creator List only books of this creator. + * @param publisher List only books of this publisher. + * @param maxSize Do not list book bigger than maxSize. + * Set to 0 to cancel this filter. + * @return The list of bookIds corresponding to the query. + */ + std::vector listBooksIds( + supportedListMode = ALL, + supportedListSortBy sortBy = UNSORTED, + const std::string& search = "", + const std::string& language = "", + const std::string& creator = "", + const std::string& publisher = "", + size_t maxSize = 0); + + friend class OPDSDumper; }; } diff --git a/include/manager.h b/include/manager.h index d1154090f..0e980b341 100644 --- a/include/manager.h +++ b/include/manager.h @@ -20,24 +20,37 @@ #ifndef KIWIX_MANAGER_H #define KIWIX_MANAGER_H -#include -#include -#include - -#include - -#include "common/base64.h" -#include "common/pathTools.h" -#include "common/regexTools.h" +#include "book.h" #include "library.h" #include "reader.h" -using namespace std; +#include +#include + +namespace pugi { +class xml_document; +} namespace kiwix { -enum supportedListMode { LASTOPEN, REMOTE, LOCAL }; -enum supportedListSortBy { TITLE, SIZE, DATE, CREATOR, PUBLISHER }; + +class LibraryManipulator { + public: + virtual ~LibraryManipulator() {} + virtual bool addBookToLibrary(Book book) = 0; +}; + +class DefaultLibraryManipulator : public LibraryManipulator { + public: + DefaultLibraryManipulator(Library* library) : + library(library) {} + virtual ~DefaultLibraryManipulator() {} + bool addBookToLibrary(Book book) { + return library->addBook(book); + } + private: + kiwix::Library* library; +}; /** * A tool to manage a `Library`. @@ -48,7 +61,8 @@ enum supportedListSortBy { TITLE, SIZE, DATE, CREATOR, PUBLISHER }; class Manager { public: - Manager(); + Manager(LibraryManipulator* manipulator); + Manager(Library* library); ~Manager(); /** @@ -59,7 +73,7 @@ class Manager * updated content. * @return True if file has been properly parsed. */ - bool readFile(const string path, const bool readOnly = true); + bool readFile(const std::string& path, const bool readOnly = true); /** * Read a `library.xml` and add book in the file to the library. @@ -71,8 +85,8 @@ class Manager * updated content. * @return True if file has been properly parsed. */ - bool readFile(const string nativePath, - const string UTF8Path, + bool readFile(const std::string& nativePath, + const std::string& UTF8Path, const bool readOnly = true); /** @@ -84,9 +98,9 @@ class Manager * @param libraryPath The library path (used to resolve relative path) * @return True if the content has been properly parsed. */ - bool readXml(const string& xml, + bool readXml(const std::string& xml, const bool readOnly = true, - const string libraryPath = ""); + const std::string& libraryPath = ""); /** * Load a library content stored in a OPDS stream. @@ -97,69 +111,7 @@ class Manager * @param libraryPath The library path (used to resolve relative path) * @return True if the content has been properly parsed. */ - bool readOpds(const string& content, const std::string& urlHost); - - /** - * Write the library to a file. - * - * @param path the path of the file to write. - * @return True. - */ - bool writeFile(const string path); - - - /** - * Remove a book from the library. - * - * @param bookIndex the index of the book to remove - * @return True - */ - bool removeBookByIndex(const unsigned int bookIndex); - - /** - * Remove a book from the library. - * - * @param id the id of the book to remove. - * @return True if the book were in the library. - */ - bool removeBookById(const string id); - - /** - * Set the current book. - * - * @param id The id to add to the stack of current books. - * If id is empty, remove the current book from the stack. - * @return True - */ - bool setCurrentBookId(const string id); - - /** - * Get the current book id. - * - * @return The id of the current book (or empty string if no current book). - */ - string getCurrentBookId() const; - - /** - * Set the path of the external fulltext index associated to a book. - * - * @param id The id of the book to set. - * @param path The path of the external fullext index. - * @param supportedIndexType The type of the fulltext index. - * @return True if the book is in the library. - */ - bool setBookIndex(const string id, - const string path, - const supportedIndexType type = XAPIAN); - - /** - * Set the path of the zim file associated to a book. - * - * @param id The id of the book to set. - * @param path The path of the zim file. - * @return True if the book is in the library. - */ - bool setBookPath(const string id, const string path); + bool readOpds(const std::string& content, const std::string& urlHost); /** * Add a book to the library. @@ -172,9 +124,9 @@ class Manager * @return The id of the book if the book has been added to the library. * Else, an empty string. */ - string addBookFromPathAndGetId(const string pathToOpen, - const string pathToSave = "", - const string url = "", + std::string addBookFromPathAndGetId(const std::string& pathToOpen, + const std::string& pathToSave = "", + const std::string& url = "", const bool checkMetaData = false); /** @@ -188,18 +140,11 @@ class Manager * @return True if the book has been added to the library. */ - bool addBookFromPath(const string pathToOpen, - const string pathToSave = "", - const string url = "", + bool addBookFromPath(const std::string& pathToOpen, + const std::string& pathToSave = "", + const std::string& url = "", const bool checkMetaData = false); - /** - * Clone and return the internal library. - * - * @return A clone of the library. - */ - Library cloneLibrary(); - /** * Get the book corresponding to an id. * @@ -207,24 +152,7 @@ class Manager * @param[out] book The book corresponding to the id. * @return True if the book has been found. */ - bool getBookById(const string id, Book& book); - - /** - * Get the current book. - * - * @param[out] The current book. - * @return True if there is a current book. - */ - bool getCurrentBook(Book& book); - - /** - * Get the number of book in the library. - * - * @param localBooks If we must count local books (books with a path). - * @param remoteBooks If we must count remote books (books with an url) - * @return The number of books. - */ - unsigned int getBookCount(const bool localBooks, const bool remoteBooks); + bool getBookById(const std::string& id, Book& book); /** * Update the "last open date" of a book @@ -232,7 +160,7 @@ class Manager * @param id the id of the book. * @return True if the book is in the library. */ - bool updateBookLastOpenDateById(const string id); + bool updateBookLastOpenDateById(const std::string& id); /** * Remove (set to empty) paths of all books in the library. @@ -261,64 +189,28 @@ class Manager bool listBooks(const supportedListMode mode, const supportedListSortBy sortBy, const unsigned int maxSize, - const string language, - const string creator, - const string publisher, - const string search); + const std::string& language, + const std::string& creator, + const std::string& publisher, + const std::string& search); - /** - * Filter the library and generate a new one with the keep elements. - * - * @param search List only books with search in the title or description. - * @return A `Library`. - */ - Library filter(const string& search); + std::string writableLibraryPath; - - /** - * Get all langagues of the books in the library. - * - * @return A list of languages. - */ - vector getBooksLanguages(); - - /** - * Get all book creators of the books in the library. - * - * @return A list of book creators. - */ - vector getBooksCreators(); - - /** - * Get all book publishers of the books in the library. - * - * @return A list of book publishers. - */ - vector getBooksPublishers(); - - /** - * Get all book ids of the books in the library. - * - * @return A list of book ids. - */ - vector getBooksIds(); - - string writableLibraryPath; - - vector bookIdList; + std::vector bookIdList; protected: - kiwix::Library library; + kiwix::LibraryManipulator* manipulator; + bool mustDeleteManipulator; - bool readBookFromPath(const string path, Book* book = NULL); + bool readBookFromPath(const std::string& path, Book* book); bool parseXmlDom(const pugi::xml_document& doc, const bool readOnly, - const string libraryPath); + const std::string& libraryPath); bool parseOpdsDom(const pugi::xml_document& doc, const std::string& urlHost); private: - void checkAndCleanBookPaths(Book& book, const string& libraryPath); + void checkAndCleanBookPaths(Book& book, const std::string& libraryPath); }; } diff --git a/include/meson.build b/include/meson.build index 1aca8b9ab..e7ab6eca8 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,4 +1,5 @@ headers = [ + 'book.h', 'common.h', 'library.h', 'manager.h', diff --git a/include/opds_dumper.h b/include/opds_dumper.h index 5c60b8c6a..ebfa4f8df 100644 --- a/include/opds_dumper.h +++ b/include/opds_dumper.h @@ -45,7 +45,7 @@ class OPDSDumper { public: OPDSDumper() = default; - OPDSDumper(Library library); + OPDSDumper(Library* library); ~OPDSDumper(); /** @@ -54,7 +54,7 @@ class OPDSDumper * @param id The id of the library. * @return The OPDS feed. */ - std::string dumpOPDSFeed(); + std::string dumpOPDSFeed(const std::vector& bookIds); /** * Set the id of the opds stream. @@ -89,10 +89,10 @@ class OPDSDumper * * @param library The library to dump. */ - void setLibrary(Library library) { this->library = library; } + void setLibrary(Library* library) { this->library = library; } protected: - kiwix::Library library; + kiwix::Library* library; std::string id; std::string title; std::string date; diff --git a/meson.build b/meson.build index 22e2bed8b..62c0bb26c 100644 --- a/meson.build +++ b/meson.build @@ -17,7 +17,13 @@ thread_dep = dependency('threads') libicu_dep = dependency('icu-i18n', static:static_deps) libzim_dep = dependency('libzim', version : '>=4.0.0', static:static_deps) pugixml_dep = dependency('pugixml', static:static_deps) -libaria2_dep = dependency('libaria2', static:static_deps, required:false) +libcurl_dep = dependency('libcurl', static:static_deps) + +if target_machine.system() == 'windows' and static_deps + add_project_arguments('-DCURL_STATICLIB', language : 'cpp') +endif + + ctpp2_include_path = '' has_ctpp2_dep = false @@ -78,7 +84,7 @@ endif xapian_dep = dependency('xapian-core', required:false, static:static_deps) -all_deps = [thread_dep, libicu_dep, libzim_dep, xapian_dep, pugixml_dep, libaria2_dep] +all_deps = [thread_dep, libicu_dep, libzim_dep, xapian_dep, pugixml_dep, libcurl_dep] if has_ctpp2_dep all_deps += [ctpp2_dep] endif @@ -88,7 +94,6 @@ inc = include_directories('include') conf = configuration_data() conf.set('VERSION', '"@0@"'.format(meson.project_version())) conf.set('ENABLE_CTPP2', has_ctpp2_dep) -conf.set('ENABLE_LIBARIA2', libaria2_dep.found()) if build_machine.system() == 'windows' extra_link_args = ['-lshlwapi', '-lwinmm'] @@ -102,10 +107,7 @@ subdir('static') subdir('src') subdir('test') -pkg_requires = ['libzim', 'icu-i18n', 'pugixml'] -if libaria2_dep.found() - pkg_requires += ['libaria2'] -endif +pkg_requires = ['libzim', 'icu-i18n', 'pugixml', 'libcurl'] if xapian_dep.found() pkg_requires += ['xapian-core'] endif diff --git a/src/android/kiwixreader.cpp b/src/android/kiwixreader.cpp index b1f24cd5f..bc58cd87e 100644 --- a/src/android/kiwixreader.cpp +++ b/src/android/kiwixreader.cpp @@ -163,8 +163,7 @@ Java_org_kiwix_kiwixlib_JNIKiwixReader_getFavicon(JNIEnv* env, jobject obj) std::string cMime; READER->getFavicon(cContent, cMime); favicon = c2jni( - base64_encode(reinterpret_cast(cContent.c_str()), - cContent.length()), + base64_encode(cContent), env); } catch (std::exception& e) { __android_log_print(ANDROID_LOG_ERROR, "kiwix", "Unable to get ZIM favicon"); diff --git a/src/aria2.cpp b/src/aria2.cpp new file mode 100644 index 000000000..d4dbc7913 --- /dev/null +++ b/src/aria2.cpp @@ -0,0 +1,187 @@ + + +#include "aria2.h" +#include "xmlrpc.h" +#include +#include +#include +#include +#include +#include // For AriaError + +namespace kiwix { + +Aria2::Aria2(): + mp_aria(nullptr), + m_port(42042), + m_secret("kiwixariarpc"), + mp_curl(nullptr), + m_lock(PTHREAD_MUTEX_INITIALIZER) +{ + m_downloadDir = getDataDirectory(); + std::vector callCmd; + + std::string rpc_port = "--rpc-listen-port=" + to_string(m_port); + std::string download_dir = "--dir=" + getDataDirectory(); + std::string session_file = appendToDirectory(getDataDirectory(), "kiwix.session"); + std::string session = "--save-session=" + session_file; + std::string inputFile = "--input-file=" + session_file; +// std::string log_dir = "--log=\"" + logDir + "\""; +#ifdef _WIN32 + int pid = GetCurrentProcessId(); +#else + pid_t pid = getpid(); +#endif + std::string stop_with_pid = "--stop-with-process=" + to_string(pid); + std::string rpc_secret = "--rpc-secret=" + m_secret; + m_secret = "token:"+m_secret; + + callCmd.push_back("aria2c"); + callCmd.push_back("--enable-rpc"); + callCmd.push_back(rpc_secret.c_str()); + callCmd.push_back(rpc_port.c_str()); + callCmd.push_back(download_dir.c_str()); + if (fileExists(session_file)) { + callCmd.push_back(inputFile.c_str()); + } + callCmd.push_back(session.c_str()); +// callCmd.push_back(log_dir.c_str()); + callCmd.push_back("--auto-save-interval=10"); + callCmd.push_back(stop_with_pid.c_str()); + callCmd.push_back("--allow-overwrite=true"); + callCmd.push_back("--dht-entry-point=router.bittorrent.com:6881"); + callCmd.push_back("--dht-entry-point6=router.bittorrent.com:6881"); + callCmd.push_back("--quiet=true"); + callCmd.push_back("--bt-enable-lpd=true"); + callCmd.push_back("--always-resume=true"); + callCmd.push_back("--max-concurrent-downloads=42"); + callCmd.push_back("--rpc-max-request-size=6M"); + callCmd.push_back("--file-allocation=none"); + callCmd.push_back(NULL); + mp_aria = Subprocess::run(callCmd); + mp_curl = curl_easy_init(); + curl_easy_setopt(mp_curl, CURLOPT_URL, "http://localhost/rpc"); + curl_easy_setopt(mp_curl, CURLOPT_PORT, m_port); + curl_easy_setopt(mp_curl, CURLOPT_POST, 1L); + + int watchdog = 50; + while(--watchdog) { + std::this_thread::sleep_for(std::chrono::microseconds(100)); + auto res = curl_easy_perform(mp_curl); + if (res == CURLE_OK) { + break; + } + } + if (!watchdog) { + curl_easy_cleanup(mp_curl); + throw std::runtime_error("Cannot connect to aria2c rpc"); + } +} + +Aria2::~Aria2() +{ + curl_easy_cleanup(mp_curl); +} + +void Aria2::close() +{ + saveSession(); + shutdown(); +} + +size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + auto str = static_cast(userdata); + str->write(ptr, nmemb); + return nmemb; +} + +std::string Aria2::doRequest(const MethodCall& methodCall) +{ + pthread_mutex_lock(&m_lock); + auto requestContent = methodCall.toString(); + std::stringstream stringstream; + CURLcode res; + curl_easy_setopt(mp_curl, CURLOPT_POSTFIELDSIZE, requestContent.size()); + curl_easy_setopt(mp_curl, CURLOPT_POSTFIELDS, requestContent.c_str()); + curl_easy_setopt(mp_curl, CURLOPT_WRITEFUNCTION, &write_callback_to_iss); + curl_easy_setopt(mp_curl, CURLOPT_WRITEDATA, &stringstream); + res = curl_easy_perform(mp_curl); + if (res == CURLE_OK) { + long response_code; + curl_easy_getinfo(mp_curl, CURLINFO_RESPONSE_CODE, &response_code); + pthread_mutex_unlock(&m_lock); + if (response_code != 200) { + throw std::runtime_error("Invalid return code from aria"); + } + auto responseContent = stringstream.str(); + MethodResponse response(responseContent); + if (response.isFault()) { + throw AriaError(response.getFault().getFaultString()); + } + return responseContent; + } + pthread_mutex_unlock(&m_lock); + throw std::runtime_error("Cannot perform request"); +} + +std::string Aria2::addUri(const std::vector& uris) +{ + MethodCall methodCall("aria2.addUri", m_secret); + auto uriParams = methodCall.newParamValue().getArray(); + for (auto& uri : uris) { + uriParams.addValue().set(uri); + } + auto ret = doRequest(methodCall); + MethodResponse response(ret); + return response.getParamValue(0).getAsS(); +} + +std::string Aria2::tellStatus(const std::string& gid, const std::vector& statusKey) +{ + MethodCall methodCall("aria2.tellStatus", m_secret); + methodCall.newParamValue().set(gid); + if (!statusKey.empty()) { + auto statusArray = methodCall.newParamValue().getArray(); + for (auto& key : statusKey) { + statusArray.addValue().set(key); + } + } + return doRequest(methodCall); +} + +std::vector Aria2::tellActive() +{ + MethodCall methodCall("aria2.tellActive", m_secret); + auto statusArray = methodCall.newParamValue().getArray(); + statusArray.addValue().set(std::string("gid")); + statusArray.addValue().set(std::string("following")); + auto responseContent = doRequest(methodCall); + MethodResponse response(responseContent); + std::vector activeGID; + int index = 0; + while(true) { + try { + auto structNode = response.getParamValue(0).getArray().getValue(index++).getStruct(); + auto gidNode = structNode.getMember("gid"); + activeGID.push_back(gidNode.getValue().getAsS()); + } catch (InvalidRPCNode& e) { break; } + } + return activeGID; +} + +void Aria2::saveSession() +{ + MethodCall methodCall("aria2.saveSession", m_secret); + doRequest(methodCall); + std::cout << "session saved" << std::endl; +} + +void Aria2::shutdown() +{ + MethodCall methodCall("aria2.shutdown", m_secret); + doRequest(methodCall); +} + + +} // end namespace kiwix diff --git a/src/aria2.h b/src/aria2.h new file mode 100644 index 000000000..2c30174f6 --- /dev/null +++ b/src/aria2.h @@ -0,0 +1,45 @@ + + +#ifndef KIWIXLIB_ARIA2_H_ +#define KIWIXLIB_ARIA2_H_ + +#ifdef _WIN32 +// winsock2.h need to be included before windows.h (included by curl.h) +# include +#endif + +#include "subprocess.h" +#include "xmlrpc.h" + +#include +#include + +namespace kiwix { + +class Aria2 +{ + private: + std::unique_ptr mp_aria; + int m_port; + std::string m_secret; + std::string m_downloadDir; + CURL* mp_curl; + pthread_mutex_t m_lock; + + std::string doRequest(const MethodCall& methodCall); + + public: + Aria2(); + virtual ~Aria2(); + void close(); + + std::string addUri(const std::vector& uri); + std::string tellStatus(const std::string& gid, const std::vector& statusKey); + std::vector tellActive(); + void saveSession(); + void shutdown(); +}; + +}; //end namespace kiwix + +#endif // KIWIXLIB_ARIA2_H_ diff --git a/src/book.cpp b/src/book.cpp new file mode 100644 index 000000000..d8241f5f0 --- /dev/null +++ b/src/book.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2011 Emmanuel Engelhart + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "book.h" +#include "reader.h" + +#include "common/base64.h" +#include "common/regexTools.h" + +#include + +namespace kiwix +{ +/* Constructor */ +Book::Book() : m_readOnly(false) +{ +} +/* Destructor */ +Book::~Book() +{ +} + +bool Book::update(const kiwix::Book& other) +{ + if (m_readOnly) + return false; + + m_readOnly = other.m_readOnly; + + if (m_path.empty()) { + m_path = other.m_path; + } + + if (m_url.empty()) { + m_url = other.m_url; + } + + if (m_tags.empty()) { + m_tags = other.m_tags; + } + + if (m_name.empty()) { + m_name = other.m_name; + } + + if (m_indexPath.empty()) { + m_indexPath = other.m_indexPath; + m_indexType = other.m_indexType; + } + + if (m_faviconMimeType.empty()) { + m_favicon = other.m_favicon; + m_faviconMimeType = other.m_faviconMimeType; + } + return true; +} + +void Book::update(const kiwix::Reader& reader) +{ + m_path = reader.getZimFilePath(); + m_id = reader.getId(); + m_description = reader.getDescription(); + m_language = reader.getLanguage(); + m_date = reader.getDate(); + m_creator = reader.getCreator(); + m_publisher = reader.getPublisher(); + m_title = reader.getTitle(); + m_name = reader.getName(); + m_tags = reader.getTags(); + m_origId = reader.getOrigId(); + m_articleCount = reader.getArticleCount(); + m_mediaCount = reader.getMediaCount(); + m_size = reader.getFileSize(); + + reader.getFavicon(m_favicon, m_faviconMimeType); +} + +#define ATTR(name) node.attribute(name).value() +void Book::updateFromXml(const pugi::xml_node& node, const std::string& baseDir) +{ + m_id = ATTR("id"); + std::string path = ATTR("path"); + if (isRelativePath(path)) { + path = computeAbsolutePath(baseDir, path); + } + m_path = path; + path = ATTR("indexPath"); + if (!path.empty()) { + if (isRelativePath(path)) { + path = computeAbsolutePath(baseDir, path); + } + m_indexPath = path; + m_indexType = XAPIAN; + } + m_title = ATTR("title"); + m_name = ATTR("name"); + m_tags = ATTR("tags"); + m_description = ATTR("description"); + m_language = ATTR("language"); + m_date = ATTR("date"); + m_creator = ATTR("creator"); + m_publisher = ATTR("publisher"); + m_url = ATTR("url"); + m_origId = ATTR("origId"); + m_articleCount = strtoull(ATTR("articleCount"), 0, 0); + m_mediaCount = strtoull(ATTR("mediaCount"), 0, 0); + m_size = strtoull(ATTR("size"), 0, 0) << 10; + m_favicon = base64_decode(ATTR("favicon")); + m_faviconMimeType = ATTR("faviconMimeType"); + try { + m_downloadId = ATTR("downloadId"); + } catch(...) {} +} +#undef ATTR + + +#define VALUE(name) node.child(name).child_value() +void Book::updateFromOpds(const pugi::xml_node& node) +{ + m_id = VALUE("id"); + if (!m_id.compare(0, 9, "urn:uuid:")) { + m_id.erase(0, 9); + } + m_title = VALUE("title"); + m_description = VALUE("description"); + m_language = VALUE("language"); + m_date = VALUE("updated"); + m_creator = node.child("author").child("name").child_value(); + for(auto linkNode = node.child("link"); linkNode; + linkNode = linkNode.next_sibling("link")) { + std::string rel = linkNode.attribute("rel").value(); + + if (rel == "http://opds-spec.org/acquisition/open-access") { + m_url = linkNode.attribute("href").value(); + m_size = strtoull(linkNode.attribute("length").value(), 0, 0); + break; + } + } + +} +#undef VALUE + +std::string Book::getHumanReadableIdFromPath() +{ + std::string id = m_path; + if (!id.empty()) { + kiwix::removeAccents(id); + +#ifdef _WIN32 + id = replaceRegex(id, "", "^.*\\\\"); +#else + id = replaceRegex(id, "", "^.*/"); +#endif + + id = replaceRegex(id, "", "\\.zim[a-z]*$"); + id = replaceRegex(id, "_", " "); + id = replaceRegex(id, "plus", "\\+"); + } + return id; +} + +void Book::setPath(const std::string& path) +{ + m_path = isRelativePath(path) + ? computeAbsolutePath(getCurrentDirectory(), path) + : path; +} + +void Book::setIndexPath(const std::string& indexPath) +{ + m_indexPath = isRelativePath(indexPath) + ? computeAbsolutePath(getCurrentDirectory(), indexPath) + : indexPath; +} + +} diff --git a/src/common/base64.cpp b/src/common/base64.cpp index e8889a6b0..c2f7538d6 100644 --- a/src/common/base64.cpp +++ b/src/common/base64.cpp @@ -37,8 +37,10 @@ static inline bool is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } -std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { +std::string base64_encode(const std::string& inString) { std::string ret; + auto in_len = inString.size(); + const unsigned char* bytes_to_encode = reinterpret_cast(inString.data()); int i = 0; int j = 0; unsigned char char_array_3[3]; diff --git a/src/common/networkTools.cpp b/src/common/networkTools.cpp index d2516996a..0066b3d48 100644 --- a/src/common/networkTools.cpp +++ b/src/common/networkTools.cpp @@ -19,6 +19,25 @@ #include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include +#include std::map kiwix::getNetworkInterfaces() @@ -160,3 +179,32 @@ std::string kiwix::getBestPublicIp() return "127.0.0.1"; } + +size_t write_callback_to_iss(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + auto str = static_cast(userdata); + str->write(ptr, nmemb); + return nmemb; +} + +std::string kiwix::download(const std::string& url) { + auto curl = curl_easy_init(); + std::stringstream ss; + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_PORT, 80); + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_callback_to_iss); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ss); + auto res = curl_easy_perform(curl); + if (res != CURLE_OK) { + curl_easy_cleanup(curl); + throw std::runtime_error("Cannot perform request"); + } + long response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + curl_easy_cleanup(curl); + if (response_code != 200) { + throw std::runtime_error("Invalid return code from server"); + } + return ss.str(); +} diff --git a/src/common/otherTools.cpp b/src/common/otherTools.cpp index 0cb852376..b57add752 100644 --- a/src/common/otherTools.cpp +++ b/src/common/otherTools.cpp @@ -27,3 +27,19 @@ void kiwix::sleep(unsigned int milliseconds) usleep(1000 * milliseconds); #endif } + + +struct XmlStringWriter: pugi::xml_writer +{ + std::string result; + virtual void write(const void* data, size_t size){ + result.append(static_cast(data), size); + } +}; + +std::string kiwix::nodeToString(pugi::xml_node node) +{ + XmlStringWriter writer; + node.print(writer, " "); + return writer.result; +} diff --git a/src/common/pathTools.cpp b/src/common/pathTools.cpp index 876263c04..cb76475e1 100644 --- a/src/common/pathTools.cpp +++ b/src/common/pathTools.cpp @@ -310,3 +310,29 @@ string getCurrentDirectory() free(a_cwd); return s_cwd; } + +string getDataDirectory() +{ +#ifdef _WIN32 + char* cDataDir = ::getenv("APPDATA"); +#else + char* cDataDir = ::getenv("KIWIX_DATA_DIR"); +#endif + std::string dataDir = cDataDir==nullptr ? "" : cDataDir; + if (!dataDir.empty()) + return dataDir; +#ifdef _WIN32 + cDataDir = ::getenv("USERPROFILE"); + dataDir = cDataDir==nullptr ? getCurrentDirectory() : cDataDir; +#else + cDataDir = ::getenv("XDG_DATA_HOME"); + dataDir = cDataDir==nullptr ? "" : cDataDir; + if (dataDir.empty()) { + cDataDir = ::getenv("HOME"); + dataDir = cDataDir==nullptr ? getCurrentDirectory() : cDataDir; + dataDir = appendToDirectory(dataDir, ".local"); + dataDir = appendToDirectory(dataDir, "share"); + } +#endif + return appendToDirectory(dataDir, "kiwix"); +} diff --git a/src/common/stringTools.cpp b/src/common/stringTools.cpp index d702ed2b3..428733c29 100644 --- a/src/common/stringTools.cpp +++ b/src/common/stringTools.cpp @@ -60,7 +60,7 @@ std::string kiwix::removeAccents(const std::string& text) #ifndef __ANDROID__ /* Prepare integer for display */ -std::string kiwix::beautifyInteger(const unsigned int number) +std::string kiwix::beautifyInteger(uint64_t number) { std::stringstream numberStream; numberStream << number; @@ -75,14 +75,19 @@ std::string kiwix::beautifyInteger(const unsigned int number) return numberString; } -std::string kiwix::beautifyFileSize(const unsigned int number) +std::string kiwix::beautifyFileSize(uint64_t number) { - if (number > 1024 * 1024) { - return kiwix::beautifyInteger(number / (1024 * 1024)) + " GB"; - } else { - return kiwix::beautifyInteger(number / 1024 != 0 ? number / 1024 : 1) - + " MB"; - } + std::stringstream ss; + ss << std::fixed << std::setprecision(2); + if (number>>30) + ss << (number/(1024.0*1024*1024)) << " GB"; + else if (number>>20) + ss << (number/(1024.0*1024)) << " MB"; + else if (number>>10) + ss << (number/1024.0) << " KB"; + else + ss << number << " B"; + return ss.str(); } void kiwix::printStringInHexadecimal(icu::UnicodeString s) diff --git a/src/config.h.in b/src/config.h.in index c6162d720..713be9c12 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -2,5 +2,3 @@ #mesondefine VERSION #mesondefine ENABLE_CTPP2 - -#mesondefine ENABLE_LIBARIA2 diff --git a/src/downloader.cpp b/src/downloader.cpp index e5a565b71..2f9a70cd1 100644 --- a/src/downloader.cpp +++ b/src/downloader.cpp @@ -20,102 +20,135 @@ #include "downloader.h" #include "common/pathTools.h" -#ifndef _WIN32 -# include -#endif +#include +#include +#include + #include +#include "aria2.h" +#include "xmlrpc.h" +#include "common/otherTools.h" +#include + namespace kiwix { -pthread_mutex_t Downloader::globalLock = PTHREAD_MUTEX_INITIALIZER; - +void Download::updateStatus(bool follow) +{ + static std::vector statusKey = {"status", "files", "totalLength", + "completedLength", "followedBy", + "downloadSpeed", "verifiedLength"}; + std::string strStatus; + if(follow && !m_followedBy.empty()) { + strStatus = mp_aria->tellStatus(m_followedBy, statusKey); + } else { + strStatus = mp_aria->tellStatus(m_did, statusKey); + } +// std::cout << strStatus << std::endl; + MethodResponse response(strStatus); + if (response.isFault()) { + m_status = Download::K_UNKNOWN; + return; + } + auto structNode = response.getParams().getParam(0).getValue().getStruct(); + auto _status = structNode.getMember("status").getValue().getAsS(); + auto status = _status == "active" ? Download::K_ACTIVE + : _status == "waiting" ? Download::K_WAITING + : _status == "paused" ? Download::K_PAUSED + : _status == "error" ? Download::K_ERROR + : _status == "complete" ? Download::K_COMPLETE + : _status == "removed" ? Download::K_REMOVED + : Download::K_UNKNOWN; + if (status == K_COMPLETE) { + try { + auto followedByMember = structNode.getMember("followedBy"); + m_followedBy = followedByMember.getValue().getArray().getValue(0).getAsS(); + if (follow) { + status = K_ACTIVE; + updateStatus(true); + return; + } + } catch (InvalidRPCNode& e) { } + } + m_status = status; + m_totalLength = structNode.getMember("totalLength").getValue().getAsI(); + m_completedLength = structNode.getMember("completedLength").getValue().getAsI(); + m_downloadSpeed = structNode.getMember("downloadSpeed").getValue().getAsI(); + try { + auto verifiedLengthValue = structNode.getMember("verifiedLength").getValue(); + m_verifiedLength = verifiedLengthValue.getAsI(); + } catch (InvalidRPCNode& e) { m_verifiedLength = 0; } + auto filesMember = structNode.getMember("files"); + auto fileStruct = filesMember.getValue().getArray().getValue(0).getStruct(); + m_path = fileStruct.getMember("path").getValue().getAsS(); + auto urisArray = fileStruct.getMember("uris").getValue().getArray(); + int index = 0; + m_uris.clear(); + while(true) { + try { + auto uriNode = urisArray.getValue(index++).getStruct().getMember("uri"); + m_uris.push_back(uriNode.getValue().getAsS()); + } catch(InvalidRPCNode& e) { break; } + } +} /* Constructor */ -Downloader::Downloader() +Downloader::Downloader() : + mp_aria(new Aria2()) { -#ifdef ENABLE_LIBARIA2 - aria2::SessionConfig config; - config.downloadEventCallback = Downloader::downloadEventCallback; - config.userData = this; - tmpDir = makeTmpDirectory(); - aria2::KeyVals options; - options.push_back(std::pair("dir", tmpDir)); - session = aria2::sessionNew(options, config); -#endif + for (auto gid : mp_aria->tellActive()) { + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + m_knownDownloads[gid]->updateStatus(); + } } /* Destructor */ Downloader::~Downloader() { -#ifdef ENABLE_LIBARIA2 - aria2::sessionFinal(session); -#endif - rmdir(tmpDir.c_str()); } -#ifdef ENABLE_LIBARIA2 -int Downloader::downloadEventCallback(aria2::Session* session, - aria2::DownloadEvent event, - aria2::A2Gid gid, - void* userData) +void Downloader::close() { - Downloader* downloader = static_cast(userData); - - auto fileHandle = downloader->fileHandle; - auto dh = aria2::getDownloadHandle(session, gid); - - if (!dh) { - return 0; - } - - switch (event) { - case aria2::EVENT_ON_DOWNLOAD_COMPLETE: - { - if (dh->getNumFiles() > 0) { - auto f = dh->getFile(1); - fileHandle->path = f.path; - fileHandle->success = true; - } - } - break; - case aria2::EVENT_ON_DOWNLOAD_ERROR: - { - fileHandle->success = false; - } - break; - default: - break; - } - aria2::deleteDownloadHandle(dh); - return 0; + mp_aria->close(); } -#endif -DownloadedFile Downloader::download(const std::string& url) { - pthread_mutex_lock(&globalLock); - DownloadedFile fileHandle; -#ifdef ENABLE_LIBARIA2 +std::vector Downloader::getDownloadIds() { + std::vector ret; + for(auto& p:m_knownDownloads) { + ret.push_back(p.first); + } + return ret; +} + +Download* Downloader::startDownload(const std::string& uri) +{ + for (auto& p: m_knownDownloads) { + auto& d = p.second; + auto& uris = d->getUris(); + if (std::find(uris.begin(), uris.end(), uri) != uris.end()) + return d.get(); + } + std::vector uris = {uri}; + auto gid = mp_aria->addUri(uris); + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + return m_knownDownloads[gid].get(); +} + +Download* Downloader::getDownload(const std::string& did) +{ try { - std::vector uris = {url}; - aria2::KeyVals options; - aria2::A2Gid gid; - int ret; - DownloadedFile fileHandle; - - ret = aria2::addUri(session, &gid, uris, options); - if (ret < 0) { - std::cerr << "Failed to download" << std::endl; - } else { - this->fileHandle = &fileHandle; - aria2::run(session, aria2::RUN_DEFAULT); + return m_knownDownloads.at(did).get(); + } catch(exception& e) { + for (auto gid : mp_aria->tellActive()) { + if (gid == did) { + m_knownDownloads[gid] = std::unique_ptr(new Download(mp_aria, gid)); + return m_knownDownloads[gid].get(); + } } - } catch (...) {}; - this->fileHandle = nullptr; - pthread_mutex_unlock(&globalLock); -#endif - return fileHandle; + throw e; + } } } diff --git a/src/library.cpp b/src/library.cpp index 541e3c661..9a4ec60d6 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -18,72 +18,17 @@ */ #include "library.h" +#include "book.h" + +#include "common/base64.h" +#include "common/regexTools.h" +#include "common/pathTools.h" + +#include +#include namespace kiwix { -/* Constructor */ -Book::Book() : readOnly(false) -{ -} -/* Destructor */ -Book::~Book() -{ -} -/* Sort functions */ -bool Book::sortByLastOpen(const kiwix::Book& a, const kiwix::Book& b) -{ - return atoi(a.last.c_str()) > atoi(b.last.c_str()); -} - -bool Book::sortByTitle(const kiwix::Book& a, const kiwix::Book& b) -{ - return strcmp(a.title.c_str(), b.title.c_str()) < 0; -} - -bool Book::sortByDate(const kiwix::Book& a, const kiwix::Book& b) -{ - return strcmp(a.date.c_str(), b.date.c_str()) > 0; -} - -bool Book::sortBySize(const kiwix::Book& a, const kiwix::Book& b) -{ - return atoi(a.size.c_str()) < atoi(b.size.c_str()); -} - -bool Book::sortByPublisher(const kiwix::Book& a, const kiwix::Book& b) -{ - return strcmp(a.publisher.c_str(), b.publisher.c_str()) < 0; -} - -bool Book::sortByCreator(const kiwix::Book& a, const kiwix::Book& b) -{ - return strcmp(a.creator.c_str(), b.creator.c_str()) < 0; -} - -bool Book::sortByLanguage(const kiwix::Book& a, const kiwix::Book& b) -{ - return strcmp(a.language.c_str(), b.language.c_str()) < 0; -} - -std::string Book::getHumanReadableIdFromPath() -{ - std::string id = pathAbsolute; - if (!id.empty()) { - kiwix::removeAccents(id); - -#ifdef _WIN32 - id = replaceRegex(id, "", "^.*\\\\"); -#else - id = replaceRegex(id, "", "^.*/"); -#endif - - id = replaceRegex(id, "", "\\.zim[a-z]*$"); - id = replaceRegex(id, "_", " "); - id = replaceRegex(id, "plus", "\\+"); - } - return id; -} - /* Constructor */ Library::Library() : version(KIWIX_LIBRARY_VERSION) { @@ -92,63 +37,320 @@ Library::Library() : version(KIWIX_LIBRARY_VERSION) Library::~Library() { } + + bool Library::addBook(const Book& book) { /* Try to find it */ std::vector::iterator itr; - for (itr = this->books.begin(); itr != this->books.end(); ++itr) { - if (itr->id == book.id) { - if (!itr->readOnly) { - itr->readOnly = book.readOnly; + try { + auto& oldbook = books.at(book.getId()); + oldbook.update(book); + return false; + } catch (std::out_of_range&) { + books[book.getId()] = book; + return true; + } +} - if (itr->path.empty()) { - itr->path = book.path; - } - if (itr->pathAbsolute.empty()) { - itr->pathAbsolute = book.pathAbsolute; - } +bool Library::removeBookById(const std::string& id) +{ + return books.erase(id) == 1; +} - if (itr->url.empty()) { - itr->url = book.url; - } +Book& Library::getBookById(const std::string& id) +{ + return books.at(id); +} - if (itr->tags.empty()) { - itr->tags = book.tags; - } +unsigned int Library::getBookCount(const bool localBooks, + const bool remoteBooks) +{ + unsigned int result = 0; + for (auto& pair: books) { + auto& book = pair.second; + if ((!book.getPath().empty() && localBooks) + || (book.getPath().empty() && remoteBooks)) { + result++; + } + } + return result; +} - if (itr->name.empty()) { - itr->name = book.name; - } +bool Library::writeToFile(const std::string& path) { + pugi::xml_document doc; + auto baseDir = removeLastPathElement(path, true, false); - if (itr->indexPath.empty()) { - itr->indexPath = book.indexPath; - itr->indexType = book.indexType; - } + /* Add the library node */ + pugi::xml_node libraryNode = doc.append_child("library"); - if (itr->indexPathAbsolute.empty()) { - itr->indexPathAbsolute = book.indexPathAbsolute; - itr->indexType = book.indexType; - } + if (!version.empty()) + libraryNode.append_attribute("version") = version.c_str(); - if (itr->faviconMimeType.empty()) { - itr->favicon = book.favicon; - itr->faviconMimeType = book.faviconMimeType; - } + /* Add each book */ + for (auto& pair: books) { + auto& book = pair.second; + if (!book.readOnly()) { + pugi::xml_node bookNode = libraryNode.append_child("book"); + bookNode.append_attribute("id") = book.getId().c_str(); + + if (!book.getPath().empty()) { + bookNode.append_attribute("path") = computeRelativePath( + baseDir, book.getPath()).c_str(); } - return false; + if (!book.getIndexPath().empty()) { + bookNode.append_attribute("indexPath") = computeRelativePath( + baseDir, book.getIndexPath()).c_str(); + bookNode.append_attribute("indexType") = "xapian"; + } + + if (book.getOrigId().empty()) { + if (!book.getTitle().empty()) + bookNode.append_attribute("title") = book.getTitle().c_str(); + + if (!book.getName().empty()) + bookNode.append_attribute("name") = book.getName().c_str(); + + if (!book.getTags().empty()) + bookNode.append_attribute("tags") = book.getTags().c_str(); + + if (!book.getDescription().empty()) + bookNode.append_attribute("description") = book.getDescription().c_str(); + + if (!book.getLanguage().empty()) + bookNode.append_attribute("language") = book.getLanguage().c_str(); + + if (!book.getCreator().empty()) + bookNode.append_attribute("creator") = book.getCreator().c_str(); + + if (!book.getPublisher().empty()) + bookNode.append_attribute("publisher") = book.getPublisher().c_str(); + + if (!book.getFavicon().empty()) + bookNode.append_attribute("favicon") = base64_encode(book.getFavicon()).c_str(); + + if (!book.getFaviconMimeType().empty()) + bookNode.append_attribute("faviconMimeType") + = book.getFaviconMimeType().c_str(); + } else { + bookNode.append_attribute("origId") = book.getOrigId().c_str(); + } + + if (!book.getDate().empty()) { + bookNode.append_attribute("date") = book.getDate().c_str(); + } + + if (!book.getUrl().empty()) { + bookNode.append_attribute("url") = book.getUrl().c_str(); + } + + if (!book.getArticleCount()) + bookNode.append_attribute("articleCount") = to_string(book.getArticleCount()).c_str(); + + if (!book.getMediaCount()) + bookNode.append_attribute("mediaCount") = to_string(book.getMediaCount()).c_str(); + + if (book.getSize()) { + bookNode.append_attribute("size") = to_string(book.getSize()>>10).c_str(); + } + + if (!book.getDownloadId().empty()) { + bookNode.append_attribute("downloadId") = book.getDownloadId().c_str(); + } } } - /* otherwise */ - this->books.push_back(book); - return true; + /* saving file */ + return doc.save_file(path.c_str()); } -bool Library::removeBookByIndex(const unsigned int bookIndex) +std::vector Library::getBooksLanguages() { - books.erase(books.begin() + bookIndex); - return true; + std::vector booksLanguages; + std::map booksLanguagesMap; + + for (auto& pair: books) { + auto& book = pair.second; + auto& language = book.getLanguage(); + if (booksLanguagesMap.find(language) == booksLanguagesMap.end()) { + if (book.getOrigId().empty()) { + booksLanguagesMap[language] = true; + booksLanguages.push_back(language); + } + } + } + + return booksLanguages; +} + +std::vector Library::getBooksCreators() +{ + std::vector booksCreators; + std::map booksCreatorsMap; + + for (auto& pair: books) { + auto& book = pair.second; + auto& creator = book.getCreator(); + if (booksCreatorsMap.find(creator) == booksCreatorsMap.end()) { + if (book.getOrigId().empty()) { + booksCreatorsMap[creator] = true; + booksCreators.push_back(creator); + } + } + } + + return booksCreators; +} + +std::vector Library::getBooksPublishers() +{ + std::vector booksPublishers; + std::map booksPublishersMap; + + for (auto& pair:books) { + auto& book = pair.second; + auto& publisher = book.getPublisher(); + if (booksPublishersMap.find(publisher) == booksPublishersMap.end()) { + if (book.getOrigId().empty()) { + booksPublishersMap[publisher] = true; + booksPublishers.push_back(publisher); + } + } + } + + return booksPublishers; +} + +std::vector Library::getBooksIds() +{ + std::vector bookIds; + + for (auto& pair: books) { + bookIds.push_back(pair.first); + } + + return bookIds; +} + +std::vector Library::filter(const std::string& search) +{ + if (search.empty()) { + return getBooksIds(); + } + + std::vector bookIds; + for(auto& pair:books) { + auto& book = pair.second; + if (matchRegex(book.getTitle(), "\\Q" + search + "\\E") + || matchRegex(book.getDescription(), "\\Q" + search + "\\E")) { + bookIds.push_back(pair.first); + } + } + + return bookIds; +} + +template +struct Comparator { + Library* lib; + Comparator(Library* lib) : lib(lib) {} + + bool operator() (const std::string& id1, const std::string& id2) { + return get_keys(id1) < get_keys(id2); + } + + std::string get_keys(const std::string& id); + unsigned int get_keyi(const std::string& id); +}; + +template<> +std::string Comparator::get_keys(const std::string& id) +{ + return lib->getBookById(id).getTitle(); +} + +template<> +unsigned int Comparator<SIZE>::get_keyi(const std::string& id) +{ + return lib->getBookById(id).getSize(); +} + +template<> +bool Comparator<SIZE>::operator() (const std::string& id1, const std::string& id2) +{ + return get_keyi(id1) < get_keyi(id2); +} + +template<> +std::string Comparator<DATE>::get_keys(const std::string& id) +{ + return lib->getBookById(id).getDate(); +} + +template<> +std::string Comparator<CREATOR>::get_keys(const std::string& id) +{ + return lib->getBookById(id).getCreator(); +} + +template<> +std::string Comparator<PUBLISHER>::get_keys(const std::string& id) +{ + return lib->getBookById(id).getPublisher(); +} + + +std::vector<std::string> Library::listBooksIds( + supportedListMode mode, + supportedListSortBy sortBy, + const std::string& search, + const std::string& language, + const std::string& creator, + const std::string& publisher, + size_t maxSize) { + + std::vector<std::string> bookIds; + for(auto& pair:books) { + auto& book = pair.second; + if (mode == LOCAL && book.getPath().empty()) + continue; + if (mode == REMOTE && (!book.getPath().empty() || book.getUrl().empty())) + continue; + if (maxSize != 0 && book.getSize() > maxSize) + continue; + if (!language.empty() && book.getLanguage() != language) + continue; + if (!publisher.empty() && book.getPublisher() != publisher) + continue; + if (!creator.empty() && book.getCreator() != creator) + continue; + if (!search.empty() && !(matchRegex(book.getTitle(), "\\Q" + search + "\\E") + || matchRegex(book.getDescription(), "\\Q" + search + "\\E"))) + continue; + bookIds.push_back(pair.first); + } + + switch(sortBy) { + case TITLE: + std::sort(bookIds.begin(), bookIds.end(), Comparator<TITLE>(this)); + break; + case SIZE: + std::sort(bookIds.begin(), bookIds.end(), Comparator<SIZE>(this)); + break; + case DATE: + std::sort(bookIds.begin(), bookIds.end(), Comparator<DATE>(this)); + break; + case CREATOR: + std::sort(bookIds.begin(), bookIds.end(), Comparator<CREATOR>(this)); + break; + case PUBLISHER: + std::sort(bookIds.begin(), bookIds.end(), Comparator<PUBLISHER>(this)); + break; + default: + break; + } + return bookIds; } } diff --git a/src/manager.cpp b/src/manager.cpp index 446616995..ce841a146 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -18,80 +18,66 @@ */ #include "manager.h" -#include "downloader.h" +#include "common/networkTools.h" + +#include <pugixml.hpp> namespace kiwix { /* Constructor */ -Manager::Manager() : writableLibraryPath("") +Manager::Manager(LibraryManipulator* manipulator): + writableLibraryPath(""), + manipulator(manipulator), + mustDeleteManipulator(false) { } + +Manager::Manager(Library* library) : + writableLibraryPath(""), + manipulator(new DefaultLibraryManipulator(library)), + mustDeleteManipulator(true) +{ +} + /* Destructor */ Manager::~Manager() { + if (mustDeleteManipulator) { + delete manipulator; + } } bool Manager::parseXmlDom(const pugi::xml_document& doc, const bool readOnly, - const string libraryPath) + const std::string& libraryPath) { pugi::xml_node libraryNode = doc.child("library"); - if (strlen(libraryNode.attribute("current").value())) - this->setCurrentBookId(libraryNode.attribute("current").value()); - - string libraryVersion = libraryNode.attribute("version").value(); + std::string libraryVersion = libraryNode.attribute("version").value(); for (pugi::xml_node bookNode = libraryNode.child("book"); bookNode; bookNode = bookNode.next_sibling("book")) { - bool ok = true; kiwix::Book book; - book.readOnly = readOnly; - book.id = bookNode.attribute("id").value(); - book.path = bookNode.attribute("path").value(); - book.last = (std::string(bookNode.attribute("last").value()) != "undefined" - ? bookNode.attribute("last").value() - : ""); - book.indexPath = bookNode.attribute("indexPath").value(); - book.indexType = XAPIAN; - book.title = bookNode.attribute("title").value(); - book.name = bookNode.attribute("name").value(); - book.tags = bookNode.attribute("tags").value(); - book.description = bookNode.attribute("description").value(); - book.language = bookNode.attribute("language").value(); - book.date = bookNode.attribute("date").value(); - book.creator = bookNode.attribute("creator").value(); - book.publisher = bookNode.attribute("publisher").value(); - book.url = bookNode.attribute("url").value(); - book.origId = bookNode.attribute("origId").value(); - book.articleCount = bookNode.attribute("articleCount").value(); - book.mediaCount = bookNode.attribute("mediaCount").value(); - book.size = bookNode.attribute("size").value(); - book.favicon = bookNode.attribute("favicon").value(); - book.faviconMimeType = bookNode.attribute("faviconMimeType").value(); - - /* Check absolute and relative paths */ - this->checkAndCleanBookPaths(book, libraryPath); + book.setReadOnly(readOnly); + book.updateFromXml(bookNode, + removeLastPathElement(libraryPath, true, false)); /* Update the book properties with the new importer */ if (libraryVersion.empty() || atoi(libraryVersion.c_str()) <= atoi(KIWIX_LIBRARY_VERSION)) { - if (!book.path.empty()) { - ok = this->readBookFromPath(book.pathAbsolute); + if (!book.getPath().empty()) { + this->readBookFromPath(book.getPath(), &book); } } - - if (ok) { - library.addBook(book); - } + manipulator->addBookToLibrary(book); } return true; } -bool Manager::readXml(const string& xml, +bool Manager::readXml(const std::string& xml, const bool readOnly, - const string libraryPath) + const std::string& libraryPath) { pugi::xml_document doc; pugi::xml_parse_result result @@ -114,36 +100,26 @@ bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& url entryNode = entryNode.next_sibling("entry")) { kiwix::Book book; - book.readOnly = false; - book.id = entryNode.child("id").child_value(); - book.title = entryNode.child("title").child_value(); - book.description = entryNode.child("summary").child_value(); - book.language = entryNode.child("language").child_value(); - book.date = entryNode.child("updated").child_value(); - book.creator = entryNode.child("author").child("name").child_value(); + book.setReadOnly(false); + book.updateFromOpds(entryNode); for(pugi::xml_node linkNode = entryNode.child("link"); linkNode; linkNode = linkNode.next_sibling("link")) { std::string rel = linkNode.attribute("rel").value(); if (rel == "http://opds-spec.org/image/thumbnail") { auto faviconUrl = urlHost + linkNode.attribute("href").value(); - auto downloader = Downloader(); - auto fileHandle = downloader.download(faviconUrl); - if (fileHandle.success) { - auto content = getFileContent(fileHandle.path); - book.favicon = base64_encode((const unsigned char*)content.data(), content.size()); - book.faviconMimeType = linkNode.attribute("type").value(); - } else { + try { + book.setFavicon(download(faviconUrl)); + book.setFaviconMimeType(linkNode.attribute("type").value()); + } catch (...) { std::cerr << "Cannot get favicon content from " << faviconUrl << std::endl; } - - } else if (rel == "http://opds-spec.org/acquisition/open-access") { - book.url = linkNode.attribute("href").value(); + break; } } /* Update the book properties with the new importer */ - library.addBook(book); + manipulator->addBookToLibrary(book); } return true; @@ -151,7 +127,7 @@ bool Manager::parseOpdsDom(const pugi::xml_document& doc, const std::string& url -bool Manager::readOpds(const string& content, const std::string& urlHost) +bool Manager::readOpds(const std::string& content, const std::string& urlHost) { pugi::xml_document doc; pugi::xml_parse_result result @@ -165,13 +141,13 @@ bool Manager::readOpds(const string& content, const std::string& urlHost) return false; } -bool Manager::readFile(const string path, const bool readOnly) +bool Manager::readFile(const std::string& path, const bool readOnly) { return this->readFile(path, path, readOnly); } -bool Manager::readFile(const string nativePath, - const string UTF8Path, +bool Manager::readFile(const std::string& nativePath, + const std::string& UTF8Path, const bool readOnly) { bool retVal = true; @@ -194,149 +170,31 @@ bool Manager::readFile(const string nativePath, return retVal; } -bool Manager::writeFile(const string path) -{ - pugi::xml_document doc; - - /* Add the library node */ - pugi::xml_node libraryNode = doc.append_child("library"); - - if (!getCurrentBookId().empty()) { - libraryNode.append_attribute("current") = getCurrentBookId().c_str(); - } - - if (!library.version.empty()) - libraryNode.append_attribute("version") = library.version.c_str(); - - /* Add each book */ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (!itr->readOnly) { - this->checkAndCleanBookPaths(*itr, path); - - pugi::xml_node bookNode = libraryNode.append_child("book"); - bookNode.append_attribute("id") = itr->id.c_str(); - - if (!itr->path.empty()) { - bookNode.append_attribute("path") = itr->path.c_str(); - } - - if (!itr->last.empty() && itr->last != "undefined") { - bookNode.append_attribute("last") = itr->last.c_str(); - } - - if (!itr->indexPath.empty()) - bookNode.append_attribute("indexPath") = itr->indexPath.c_str(); - - if (!itr->indexPath.empty() || !itr->indexPathAbsolute.empty()) { - if (itr->indexType == XAPIAN) { - bookNode.append_attribute("indexType") = "xapian"; - } - } - - if (itr->origId.empty()) { - if (!itr->title.empty()) - bookNode.append_attribute("title") = itr->title.c_str(); - - if (!itr->name.empty()) - bookNode.append_attribute("name") = itr->name.c_str(); - - if (!itr->tags.empty()) - bookNode.append_attribute("tags") = itr->tags.c_str(); - - if (!itr->description.empty()) - bookNode.append_attribute("description") = itr->description.c_str(); - - if (!itr->language.empty()) - bookNode.append_attribute("language") = itr->language.c_str(); - - if (!itr->creator.empty()) - bookNode.append_attribute("creator") = itr->creator.c_str(); - - if (!itr->publisher.empty()) - bookNode.append_attribute("publisher") = itr->publisher.c_str(); - - if (!itr->favicon.empty()) - bookNode.append_attribute("favicon") = itr->favicon.c_str(); - - if (!itr->faviconMimeType.empty()) - bookNode.append_attribute("faviconMimeType") - = itr->faviconMimeType.c_str(); - } - - if (!itr->date.empty()) { - bookNode.append_attribute("date") = itr->date.c_str(); - } - - if (!itr->url.empty()) { - bookNode.append_attribute("url") = itr->url.c_str(); - } - - if (!itr->origId.empty()) - bookNode.append_attribute("origId") = itr->origId.c_str(); - - if (!itr->articleCount.empty()) - bookNode.append_attribute("articleCount") = itr->articleCount.c_str(); - - if (!itr->mediaCount.empty()) - bookNode.append_attribute("mediaCount") = itr->mediaCount.c_str(); - - if (!itr->size.empty()) { - bookNode.append_attribute("size") = itr->size.c_str(); - } - } - } - - /* saving file */ - doc.save_file(path.c_str()); - - return true; -} - - -bool Manager::setCurrentBookId(const string id) -{ - if (library.current.empty() || library.current.top() != id) { - if (id.empty() && !library.current.empty()) { - library.current.pop(); - } else { - library.current.push(id); - } - } - return true; -} - -string Manager::getCurrentBookId() const -{ - return library.current.empty() ? "" : library.current.top(); -} /* Add a book to the library. Return empty string if failed, book id otherwise */ -string Manager::addBookFromPathAndGetId(const string pathToOpen, - const string pathToSave, - const string url, - const bool checkMetaData) +std::string Manager::addBookFromPathAndGetId(const std::string& pathToOpen, + const std::string& pathToSave, + const std::string& url, + const bool checkMetaData) { kiwix::Book book; if (this->readBookFromPath(pathToOpen, &book)) { if (pathToSave != pathToOpen) { - book.path = pathToSave; - book.pathAbsolute - = isRelativePath(pathToSave) + book.setPath(isRelativePath(pathToSave) ? computeAbsolutePath( removeLastPathElement(writableLibraryPath, true, false), pathToSave) - : pathToSave; + : pathToSave); } if (!checkMetaData - || (checkMetaData && !book.title.empty() && !book.language.empty() - && !book.date.empty())) { - book.url = url; - library.addBook(book); - return book.id; + || (checkMetaData && !book.getTitle().empty() && !book.getLanguage().empty() + && !book.getDate().empty())) { + book.setUrl(url); + manipulator->addBookToLibrary(book); + return book.getId(); } } @@ -345,9 +203,9 @@ string Manager::addBookFromPathAndGetId(const string pathToOpen, /* Wrapper over Manager::addBookFromPath which return a bool instead of a string */ -bool Manager::addBookFromPath(const string pathToOpen, - const string pathToSave, - const string url, +bool Manager::addBookFromPath(const std::string& pathToOpen, + const std::string& pathToSave, + const std::string& url, const bool checkMetaData) { return !( @@ -355,380 +213,19 @@ bool Manager::addBookFromPath(const string pathToOpen, .empty()); } -bool Manager::readBookFromPath(const string path, kiwix::Book* book) +bool Manager::readBookFromPath(const std::string& path, kiwix::Book* book) { try { - kiwix::Reader* reader = new kiwix::Reader(path); - - if (book != NULL) { - book->path = path; - book->pathAbsolute = path; - book->id = reader->getId(); - book->description = reader->getDescription(); - book->language = reader->getLanguage(); - book->date = reader->getDate(); - book->creator = reader->getCreator(); - book->publisher = reader->getPublisher(); - book->title = reader->getTitle(); - book->name = reader->getName(); - book->tags = reader->getTags(); - book->origId = reader->getOrigId(); - std::ostringstream articleCountStream; - articleCountStream << reader->getArticleCount(); - book->articleCount = articleCountStream.str(); - - std::ostringstream mediaCountStream; - mediaCountStream << reader->getMediaCount(); - book->mediaCount = mediaCountStream.str(); - - ostringstream convert; - convert << reader->getFileSize(); - book->size = convert.str(); - - string favicon; - string faviconMimeType; - if (reader->getFavicon(favicon, faviconMimeType)) { - book->favicon = base64_encode( - reinterpret_cast<const unsigned char*>(favicon.c_str()), - favicon.length()); - book->faviconMimeType = faviconMimeType; - } - } - - delete reader; + kiwix::Reader reader(path); + book->update(reader); + book->setPathValid(true); } catch (const std::exception& e) { - std::cerr << e.what() << std::endl; + std::cerr << "Invalid " << path << " : " << e.what() << std::endl; + book->setPathValid(false); return false; } return true; } -bool Manager::removeBookByIndex(const unsigned int bookIndex) -{ - return this->library.removeBookByIndex(bookIndex); -} - -bool Manager::removeBookById(const string id) -{ - unsigned int bookIndex = 0; - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (itr->id == id) { - return this->library.removeBookByIndex(bookIndex); - } - bookIndex++; - } - return false; -} - -vector<string> Manager::getBooksLanguages() -{ - std::vector<string> booksLanguages; - std::vector<kiwix::Book>::iterator itr; - std::map<string, bool> booksLanguagesMap; - - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByLanguage); - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (booksLanguagesMap.find(itr->language) == booksLanguagesMap.end()) { - if (itr->origId.empty()) { - booksLanguagesMap[itr->language] = true; - booksLanguages.push_back(itr->language); - } - } - } - - return booksLanguages; -} - -vector<string> Manager::getBooksCreators() -{ - std::vector<string> booksCreators; - std::vector<kiwix::Book>::iterator itr; - std::map<string, bool> booksCreatorsMap; - - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByCreator); - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (booksCreatorsMap.find(itr->creator) == booksCreatorsMap.end()) { - if (itr->origId.empty()) { - booksCreatorsMap[itr->creator] = true; - booksCreators.push_back(itr->creator); - } - } - } - - return booksCreators; -} - -vector<string> Manager::getBooksIds() -{ - std::vector<string> booksIds; - std::vector<kiwix::Book>::iterator itr; - - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - booksIds.push_back(itr->id); - } - - return booksIds; -} - -vector<string> Manager::getBooksPublishers() -{ - std::vector<string> booksPublishers; - std::vector<kiwix::Book>::iterator itr; - std::map<string, bool> booksPublishersMap; - - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByPublisher); - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (booksPublishersMap.find(itr->publisher) == booksPublishersMap.end()) { - if (itr->origId.empty()) { - booksPublishersMap[itr->publisher] = true; - booksPublishers.push_back(itr->publisher); - } - } - } - - return booksPublishers; -} - -kiwix::Library Manager::cloneLibrary() -{ - return this->library; -} -bool Manager::getCurrentBook(Book& book) -{ - string currentBookId = getCurrentBookId(); - if (currentBookId.empty()) { - return false; - } else { - getBookById(currentBookId, book); - return true; - } -} - -bool Manager::getBookById(const string id, Book& book) -{ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (itr->id == id) { - book = *itr; - return true; - } - } - return false; -} - -bool Manager::updateBookLastOpenDateById(const string id) -{ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (itr->id == id) { - char unixdate[12]; - sprintf(unixdate, "%d", (int)time(NULL)); - itr->last = unixdate; - return true; - } - } - - return false; -} - -bool Manager::setBookIndex(const string id, - const string path, - const supportedIndexType type) -{ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (itr->id == id) { - itr->indexPath = path; - itr->indexPathAbsolute - = isRelativePath(path) - ? computeAbsolutePath( - removeLastPathElement(writableLibraryPath, true, false), - path) - : path; - itr->indexType = type; - return true; - } - } - - return false; -} - -bool Manager::setBookPath(const string id, const string path) -{ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (itr->id == id) { - itr->path = path; - itr->pathAbsolute - = isRelativePath(path) - ? computeAbsolutePath( - removeLastPathElement(writableLibraryPath, true, false), - path) - : path; - return true; - } - } - - return false; -} - -void Manager::removeBookPaths() -{ - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - itr->path = ""; - itr->pathAbsolute = ""; - } -} - -unsigned int Manager::getBookCount(const bool localBooks, - const bool remoteBooks) -{ - unsigned int result = 0; - std::vector<kiwix::Book>::iterator itr; - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if ((!itr->path.empty() && localBooks) - || (itr->path.empty() && remoteBooks)) { - result++; - } - } - return result; -} - -bool Manager::listBooks(const supportedListMode mode, - const supportedListSortBy sortBy, - const unsigned int maxSize, - const string language, - const string creator, - const string publisher, - const string search) -{ - this->bookIdList.clear(); - std::vector<kiwix::Book>::iterator itr; - - /* Sort */ - if (sortBy == TITLE) { - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByTitle); - } else if (sortBy == SIZE) { - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortBySize); - } else if (sortBy == DATE) { - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByDate); - } else if (sortBy == CREATOR) { - std::sort( - library.books.begin(), library.books.end(), kiwix::Book::sortByCreator); - } else if (sortBy == PUBLISHER) { - std::sort(library.books.begin(), - library.books.end(), - kiwix::Book::sortByPublisher); - } - - /* Special sort for LASTOPEN */ - if (mode == LASTOPEN) { - std::sort(library.books.begin(), - library.books.end(), - kiwix::Book::sortByLastOpen); - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - if (!itr->last.empty()) { - this->bookIdList.push_back(itr->id); - } - } - } else { - /* Generate the list of book id */ - for (itr = library.books.begin(); itr != library.books.end(); ++itr) { - bool ok = true; - - if (mode == LOCAL && itr->path.empty()) { - ok = false; - } - - if (ok == true && mode == REMOTE - && (!itr->path.empty() || itr->url.empty())) { - ok = false; - } - - if (ok == true && maxSize != 0 - && (unsigned int)atoi(itr->size.c_str()) > maxSize * 1024 * 1024) { - ok = false; - } - - if (ok == true && !language.empty() - && !matchRegex(itr->language, language)) { - ok = false; - } - - if (ok == true && !creator.empty() && itr->creator != creator) { - ok = false; - } - - if (ok == true && !publisher.empty() && itr->publisher != publisher) { - ok = false; - } - - if ((ok == true && !search.empty()) - && !(matchRegex(itr->title, "\\Q" + search + "\\E") - || matchRegex(itr->description, "\\Q" + search + "\\E") - || matchRegex(itr->language, "\\Q" + search + "\\E"))) { - ok = false; - } - - if (ok == true) { - this->bookIdList.push_back(itr->id); - } - } - } - - return true; -} - - -Library Manager::filter(const std::string& search) { - Library library; - - if (search.empty()) { - return library; - } - - for(auto book:this->library.books) { - if (matchRegex(book.title, "\\Q" + search + "\\E") - || matchRegex(book.description, "\\Q" + search + "\\E")) { - library.addBook(book); - } - } - - return library; -} - -void Manager::checkAndCleanBookPaths(Book& book, const string& libraryPath) -{ - if (!book.path.empty()) { - if (isRelativePath(book.path)) { - book.pathAbsolute = computeAbsolutePath( - removeLastPathElement(libraryPath, true, false), book.path); - } else { - book.pathAbsolute = book.path; - book.path = computeRelativePath( - removeLastPathElement(libraryPath, true, false), book.pathAbsolute); - } - } - - if (!book.indexPath.empty()) { - if (isRelativePath(book.indexPath)) { - book.indexPathAbsolute = computeAbsolutePath( - removeLastPathElement(libraryPath, true, false), book.indexPath); - } else { - book.indexPathAbsolute = book.indexPath; - book.indexPath - = computeRelativePath(removeLastPathElement(libraryPath, true, false), - book.indexPathAbsolute); - } - } -} } diff --git a/src/meson.build b/src/meson.build index f2be42e29..deefdac8f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,5 @@ kiwix_sources = [ + 'book.cpp', 'library.cpp', 'manager.cpp', 'opds_dumper.cpp', @@ -6,6 +7,8 @@ kiwix_sources = [ 'reader.cpp', 'entry.cpp', 'searcher.cpp', + 'subprocess.cpp', + 'aria2.cpp', 'common/base64.cpp', 'common/pathTools.cpp', 'common/regexTools.cpp', @@ -17,6 +20,12 @@ kiwix_sources = [ ] kiwix_sources += lib_resources +if host_machine.system() == 'windows' + kiwix_sources += 'subprocess_windows.cpp' +else + kiwix_sources += 'subprocess_unix.cpp' +endif + if xapian_dep.found() kiwix_sources += ['xapianSearcher.cpp'] endif diff --git a/src/opds_dumper.cpp b/src/opds_dumper.cpp index 63dce8542..1ce76b05b 100644 --- a/src/opds_dumper.cpp +++ b/src/opds_dumper.cpp @@ -18,11 +18,14 @@ */ #include "opds_dumper.h" +#include "book.h" + +#include <common/otherTools.h> namespace kiwix { /* Constructor */ -OPDSDumper::OPDSDumper(Library library) +OPDSDumper::OPDSDumper(Library* library) : library(library) { } @@ -31,24 +34,6 @@ OPDSDumper::~OPDSDumper() { } -struct xml_string_writer: pugi::xml_writer -{ - std::string result; - - virtual void write(const void* data, size_t size) - { - result.append(static_cast<const char*>(data), size); - } -}; - -std::string node_to_string(pugi::xml_node node) -{ - xml_string_writer writer; - node.print(writer, " "); - - return writer.result; -} - std::string gen_date_str() { auto now = time(0); @@ -69,36 +54,37 @@ std::string gen_date_str() pugi::xml_node OPDSDumper::handleBook(Book book, pugi::xml_node root_node) { auto entry_node = root_node.append_child("entry"); - ADD_TEXT_ENTRY(entry_node, "title", book.title); - ADD_TEXT_ENTRY(entry_node, "id", "urn:uuid:"+book.id); + ADD_TEXT_ENTRY(entry_node, "title", book.getTitle()); + ADD_TEXT_ENTRY(entry_node, "id", "urn:uuid:"+book.getId()); ADD_TEXT_ENTRY(entry_node, "icon", rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath()); ADD_TEXT_ENTRY(entry_node, "updated", date); - ADD_TEXT_ENTRY(entry_node, "summary", book.description); + ADD_TEXT_ENTRY(entry_node, "summary", book.getDescription()); auto content_node = entry_node.append_child("link"); content_node.append_attribute("type") = "text/html"; content_node.append_attribute("href") = (rootLocation + "/" + book.getHumanReadableIdFromPath()).c_str(); auto author_node = entry_node.append_child("author"); - ADD_TEXT_ENTRY(author_node, "name", book.creator); + ADD_TEXT_ENTRY(author_node, "name", book.getCreator()); - if (! book.url.empty()) { + if (! book.getUrl().empty()) { auto acquisition_link = entry_node.append_child("link"); acquisition_link.append_attribute("rel") = "http://opds-spec.org/acquisition/open-access"; acquisition_link.append_attribute("type") = "application/x-zim"; - acquisition_link.append_attribute("href") = book.url.c_str(); + acquisition_link.append_attribute("href") = book.getUrl().c_str(); + acquisition_link.append_attribute("length") = to_string(book.getSize()).c_str(); } - if (! book.faviconMimeType.empty() ) { + if (! book.getFaviconMimeType().empty() ) { auto image_link = entry_node.append_child("link"); image_link.append_attribute("rel") = "http://opds-spec.org/image/thumbnail"; - image_link.append_attribute("type") = book.faviconMimeType.c_str(); + image_link.append_attribute("type") = book.getFaviconMimeType().c_str(); image_link.append_attribute("href") = (rootLocation + "/meta?name=favicon&content=" + book.getHumanReadableIdFromPath()).c_str(); } return entry_node; } -string OPDSDumper::dumpOPDSFeed() +string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds) { date = gen_date_str(); pugi::xml_document doc; @@ -125,11 +111,13 @@ string OPDSDumper::dumpOPDSFeed() search_link.append_attribute("href") = searchDescriptionUrl.c_str(); } - for (auto book: library.books) { - handleBook(book, root_node); + if (library) { + for (auto& bookId: bookIds) { + handleBook(library->getBookById(bookId), root_node); + } } - return node_to_string(root_node); + return nodeToString(root_node); } } diff --git a/src/subprocess.cpp b/src/subprocess.cpp new file mode 100644 index 000000000..6c958c489 --- /dev/null +++ b/src/subprocess.cpp @@ -0,0 +1,40 @@ + + +#include "subprocess.h" + +#ifdef _WIN32 +# include "subprocess_windows.h" +#else +# include "subprocess_unix.h" +#endif + +Subprocess::Subprocess(std::unique_ptr<SubprocessImpl> impl, const commandLine_t& commandLine) : + mp_impl(std::move(impl)) +{ + mp_impl->run(commandLine); +} + +Subprocess::~Subprocess() +{ + mp_impl->kill(); +} + +std::unique_ptr<Subprocess> Subprocess::run(const commandLine_t& commandLine) +{ +#ifdef _WIN32 + auto impl = std::unique_ptr<SubprocessImpl>(new WinImpl); +#else + auto impl = std::unique_ptr<UnixImpl>(new UnixImpl); +#endif + return std::unique_ptr<Subprocess>(new Subprocess(std::move(impl), commandLine)); +} + +bool Subprocess::isRunning() +{ + return mp_impl->isRunning(); +} + +bool Subprocess::kill() +{ + return mp_impl->kill(); +} diff --git a/src/subprocess.h b/src/subprocess.h new file mode 100644 index 000000000..9ed75eb21 --- /dev/null +++ b/src/subprocess.h @@ -0,0 +1,36 @@ + +#ifndef KIWIX_SUBPROCESS_H_ +#define KIWIX_SUBPROCESS_H_ + +#include <string> +#include <memory> +#include <vector> + +typedef std::vector<const char *> commandLine_t; + +class SubprocessImpl +{ + public: + virtual void run(const commandLine_t& commandLine) = 0; + virtual bool kill() = 0; + virtual bool isRunning() = 0; + virtual ~SubprocessImpl() = default; +}; + +class Subprocess +{ + private: + // Impl depends of the system (window, unix, ...) + std::unique_ptr<SubprocessImpl> mp_impl; + Subprocess(std::unique_ptr<SubprocessImpl> impl, const commandLine_t& commandLine); + + public: + static std::unique_ptr<Subprocess> run(const commandLine_t& commandLine); + ~Subprocess(); + + bool isRunning(); + bool kill(); +}; + + +#endif // KIWIX_SUBPROCESS_H_ diff --git a/src/subprocess_unix.cpp b/src/subprocess_unix.cpp new file mode 100644 index 000000000..fad700353 --- /dev/null +++ b/src/subprocess_unix.cpp @@ -0,0 +1,93 @@ + + +#include "subprocess_unix.h" + +#include <sys/types.h> +#include <unistd.h> +#include <iostream> +#include <signal.h> +#include <sys/wait.h> +#include <stdlib.h> + +UnixImpl::UnixImpl(): + m_pid(0), + m_running(false), + m_mutex(PTHREAD_MUTEX_INITIALIZER), + m_waitingThread() +{ +} + +UnixImpl::~UnixImpl() +{ + kill(); +// Android has no pthread_cancel :( +#ifdef __ANDROID__ + pthread_kill(m_waitingThread, SIGUSR1); +#else + pthread_cancel(m_waitingThread); +#endif +} + +#ifdef __ANDROID__ +void thread_exit_handler(int sig) { + pthread_exit(0); +} +#endif + +void* UnixImpl::waitForPID(void* _self) +{ +#ifdef __ANDROID__ + struct sigaction actions; + memset(&actions, 0, sizeof(actions)); + sigemptyset(&actions.sa_mask); + actions.sa_flags = 0; + actions.sa_handler = thread_exit_handler; + sigaction(SIGUSR1, &actions, NULL); +#endif + + UnixImpl* self = static_cast<UnixImpl*>(_self); + waitpid(self->m_pid, NULL, WEXITED); + + pthread_mutex_lock(&self->m_mutex); + self->m_running = false; + pthread_mutex_unlock(&self->m_mutex); + + return self; +} + +void UnixImpl::run(const commandLine_t& commandLine) +{ + const char* binary = commandLine[0]; + std::cerr << "running " << binary << std::endl; + int pid = fork(); + switch(pid) { + case -1: + std::cerr << "cannot fork" << std::endl; + break; + case 0: + if (execvp(binary, const_cast<char* const*>(commandLine.data()))) { + perror("Cannot launch\n"); + exit(-1); + } + + break; + default: + m_pid = pid; + m_running = true; + pthread_create(&m_waitingThread, NULL, waitForPID, this); + break; + } +} + +bool UnixImpl::kill() +{ + return (::kill(m_pid, SIGKILL) == 0); +} + +bool UnixImpl::isRunning() +{ + pthread_mutex_lock(&m_mutex); + bool ret = m_running; + pthread_mutex_unlock(&m_mutex); + return ret; +} diff --git a/src/subprocess_unix.h b/src/subprocess_unix.h new file mode 100644 index 000000000..750658943 --- /dev/null +++ b/src/subprocess_unix.h @@ -0,0 +1,28 @@ +#ifndef KIWIX_SUBPROCESS_UNIX_H_ +#define KIWIX_SUBPROCESS_UNIX_H_ + +#include "subprocess.h" + +#include <pthread.h> + + +class UnixImpl : public SubprocessImpl +{ + private: + int m_pid; + bool m_running; + pthread_mutex_t m_mutex; + pthread_t m_waitingThread; + + public: + UnixImpl(); + virtual ~UnixImpl(); + + void run(const commandLine_t& commandLine); + bool kill(); + bool isRunning(); + + static void* waitForPID(void* self); +}; + +#endif //KIWIX_SUBPROCESS_UNIX_H_ diff --git a/src/subprocess_windows.cpp b/src/subprocess_windows.cpp new file mode 100644 index 000000000..226bd5d15 --- /dev/null +++ b/src/subprocess_windows.cpp @@ -0,0 +1,94 @@ + + +#include "subprocess_windows.h" + +#include <windows.h> +#include <winbase.h> +#include <iostream> +#include <sstream> + +WinImpl::WinImpl(): + m_pid(0), + m_running(false), + m_handle(INVALID_HANDLE_VALUE) +{ + InitializeCriticalSection(&m_criticalSection); +} + +WinImpl::~WinImpl() +{ + kill(); + CloseHandle(m_handle); + DeleteCriticalSection(&m_criticalSection); +} + +DWORD WINAPI WinImpl::waitForPID(void* _self) +{ + WinImpl* self = static_cast<WinImpl*>(_self); + WaitForSingleObject(self->m_handle, INFINITE); + + EnterCriticalSection(&self->m_criticalSection); + self->m_running = false; + LeaveCriticalSection(&self->m_criticalSection); + + return 0; +} + +std::unique_ptr<wchar_t[]> toWideChar(const std::string& value) +{ + auto size = MultiByteToWideChar(CP_UTF8, 0, + value.c_str(), -1, nullptr, 0); + auto wdata = std::unique_ptr<wchar_t[]>(new wchar_t[size]); + auto ret = MultiByteToWideChar(CP_UTF8, 0, + value.c_str(), -1, wdata.get(), size); + if (0 == ret) { + std::ostringstream oss; + oss << "Cannot convert to wchar : " << GetLastError(); + throw std::runtime_error(oss.str()); + } + return wdata; +} + + +void WinImpl::run(const commandLine_t& commandLine) +{ + STARTUPINFOW startInfo = {0}; + PROCESS_INFORMATION procInfo; + startInfo.cb = sizeof(startInfo); + const char* binary = commandLine[0]; + std::cerr << "running " << binary << std::endl; + std::ostringstream oss; + for(auto& item: commandLine) { + oss << item << " "; + } + if (CreateProcessW( + toWideChar(binary).get(), + toWideChar(oss.str()).get(), + NULL, + NULL, + false, + CREATE_NO_WINDOW, + NULL, + NULL, + &startInfo, + &procInfo)) { + m_pid = procInfo.dwProcessId; + m_handle = procInfo.hProcess; + CloseHandle(procInfo.hThread); + m_running = true; + CreateThread(NULL, 0, &waitForPID, this, 0, NULL ); + } +} + +bool WinImpl::kill() +{ + return TerminateProcess(m_handle, 0); +} + +bool WinImpl::isRunning() +{ + EnterCriticalSection(&m_criticalSection); + bool ret = m_running; + LeaveCriticalSection(&m_criticalSection); + return ret; +} diff --git a/src/subprocess_windows.h b/src/subprocess_windows.h new file mode 100644 index 000000000..42aaa41f5 --- /dev/null +++ b/src/subprocess_windows.h @@ -0,0 +1,27 @@ +#ifndef KIWIX_SUBPROCESS_WINDOWS_H_ +#define KIWIX_SUBPROCESS_WINDOWS_H_ + +#include "subprocess.h" + +#include <synchapi.h> + +class WinImpl : public SubprocessImpl +{ + private: + int m_pid; + bool m_running; + HANDLE m_handle; + CRITICAL_SECTION m_criticalSection; + + public: + WinImpl(); + virtual ~WinImpl(); + + void run(const commandLine_t& commandLine); + bool kill(); + bool isRunning(); + + static DWORD WINAPI waitForPID(void* self); +}; + +#endif //KIWIX_SUBPROCESS_WINDOWS_H_ diff --git a/src/xmlrpc.h b/src/xmlrpc.h new file mode 100644 index 000000000..8e6c6d306 --- /dev/null +++ b/src/xmlrpc.h @@ -0,0 +1,264 @@ + + +#ifndef KIWIX_XMLRPC_H_ +#define KIWIX_XMLRPC_H_ + +#include <common/otherTools.h> + +namespace kiwix { + +class InvalidRPCNode : public std::runtime_error { + public: + InvalidRPCNode(const std::string& msg) : std::runtime_error(msg) {}; +}; + +class Struct; +class Array; + +class Value { + pugi::xml_node m_value; + + public: + Value(pugi::xml_node value) : m_value(value) { } + + void set(int value) { + if (!m_value.child("int")) + m_value.append_child("int"); + m_value.child("int").text().set(value); + }; + + int getAsI() const { + if (!m_value.child("int")) + throw InvalidRPCNode("Type Error"); + return m_value.child("int").text().as_int(); + } + + void set(bool value) { + if (!m_value.child("boolean")) + m_value.append_child("boolean"); + m_value.child("boolean").text().set(value); + }; + + int getAsB() const { + if (!m_value.child("boolean")) + throw InvalidRPCNode("Type Error"); + return m_value.child("boolean").text().as_bool(); + } + + void set(const std::string& value) { + if (!m_value.child("string")) + m_value.append_child("string"); + m_value.child("string").text().set(value.c_str()); + }; + + std::string getAsS() const { + if (!m_value.child("string")) + throw InvalidRPCNode("Type Error"); + return m_value.child("string").text().as_string(); + } + + void set(double value) { + if (!m_value.child("double")) + m_value.append_child("double"); + m_value.child("double").text().set(value); + }; + + double getAsD() const { + if (!m_value.child("double")) + throw InvalidRPCNode("Type Error"); + return m_value.child("double").text().as_double(); + } + + inline Struct getStruct(); + inline Array getArray(); +}; + +class Array { + pugi::xml_node m_array; + public: + Array(pugi::xml_node array) : m_array(array) { + if (!m_array.child("data")) + m_array.append_child("data"); + } + + Value addValue() { + auto value = m_array.child("data").append_child("value"); + return Value(value); + } + + Value getValue(int index) const { + auto value = m_array.child("data").child("value"); + while(index && value) { + value = value.next_sibling(); + index--; + } + if (0==index) { + return Value(value); + } else { + throw InvalidRPCNode("Index error"); + } + } + +}; + +class Member { + pugi::xml_node m_member; + + public: + Member(pugi::xml_node member) : m_member(member) { } + + Value getValue() const { + return Value(m_member.child("value")); + }; +}; + +class Struct { + pugi::xml_node m_struct; + + public: + Struct(pugi::xml_node _struct) : m_struct(_struct) { } + + Member getMember(const std::string& name) const { + for(auto member=m_struct.first_child(); member; member=member.next_sibling()) { + std::string member_name = member.child("name").text().get(); + if (member_name == name) { + return Member(member); + } + } + throw InvalidRPCNode("Key Error"); + } + + Member addMember(const std::string& name) { + auto member = m_struct.append_child("member"); + member.append_child("name").text().set(name.c_str()); + member.append_child("value"); + return Member(member); + } +}; + +class Fault : public Struct { + public: + Fault(pugi::xml_node fault) : Struct(fault) {}; + + int getFaultCode() const { + return getMember("faultCode").getValue().getAsI(); + } + + std::string getFaultString() const { + return getMember("faultString").getValue().getAsS(); + } +}; + +Struct Value::getStruct() { + if (!m_value.child("struct")) + m_value.append_child("struct"); + return Struct(m_value.child("struct")); +} + +Array Value::getArray() { + if (!m_value.child("array")) + m_value.append_child("array"); + return Array(m_value.child("array")); +} + +class Param { + pugi::xml_node m_param; + public: + Param(pugi::xml_node param) : m_param(param) { + if (!m_param.child("value")) + m_param.append_child("value"); + }; + + Value getValue() const { + return Value(m_param.child("value")); + }; +}; + +class Params { + pugi::xml_node m_params; + + public: + Params(pugi::xml_node params) : m_params(params) {}; + + Param addParam() { + auto param = m_params.append_child("param"); + return Param(param); + } + Param getParam(int index) const { + auto param = m_params.child("param"); + while(index && param) { + param = param.next_sibling(); + index--; + } + if (0==index) { + return Param(param); + } else { + throw InvalidRPCNode("Index Error"); + } + } +}; + +class MethodCall { + pugi::xml_document m_doc; + + public: + MethodCall(const std::string& methodName, const std::string& secret) { + auto mCall = m_doc.append_child("methodCall"); + mCall.append_child("methodName").text().set(methodName.c_str()); + mCall.append_child("params"); + if (!secret.empty()) { + getParams().addParam().getValue().set(secret); + } + } + + Params getParams() const { + return Params(m_doc.child("methodCall").child("params")); + } + + Value newParamValue() { + return getParams().addParam().getValue(); + } + + std::string toString() const { + return nodeToString(m_doc); + } +}; + + +class MethodResponse { + pugi::xml_document m_doc; + + public: + MethodResponse(const std::string& content) { + m_doc.load_buffer(content.c_str(), content.size()); + } + + Params getParams() const { + auto params = m_doc.child("methodResponse").child("params"); + if (!params) + throw InvalidRPCNode("No params"); + return Params(params); + } + + Value getParamValue(int index) const { + return getParams().getParam(index).getValue(); + } + + bool isFault() const { + return (!!m_doc.child("methodResponse").child("fault")); + } + + Fault getFault() const { + auto fault = m_doc.child("methodResponse").child("fault"); + if (!fault) + throw InvalidRPCNode("No fault"); + return Fault(fault.child("value").child("struct")); + } +}; + + + +}; + + +#endif // KIWIX_XMLRPC_H_ diff --git a/travis/install_deps.sh b/travis/install_deps.sh index b3356a482..7687d07b7 100755 --- a/travis/install_deps.sh +++ b/travis/install_deps.sh @@ -9,8 +9,6 @@ ARCHIVE_NAME=deps_${TRAVIS_OS_NAME}_${PLATFORM}_${REPO_NAME}.tar.gz cd $HOME if [[ "$TRAVIS_OS_NAME" == "osx" ]] then - brew update - brew upgrade python3 pip3 install meson==0.43.0 wget https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-mac.zip