/* * Copyright (C) 2008-2012 Geometer Plus * * 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 2 of the License, or * (at your option) 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../reader/Reader.h" #include "../networkActions/NetworkOperationRunnable.h" #include "NetworkLinkCollection.h" #include "../options/FBCategoryKey.h" #include "../database/networkdb/NetworkDB.h" #include "NetworkOperationData.h" #include "NetworkBookCollection.h" #include "BookReference.h" #include "NetworkErrors.h" #include "opds/OPDSLink.h" #include "opds/OPDSLink_GenericXMLParser.h" #include "opds/OPDSLink_GenericFeedReader.h" #include "opds/OPDSXMLParser.h" #include "opds/URLRewritingRule.h" NetworkLinkCollection *NetworkLinkCollection::ourInstance = 0; NetworkLinkCollection &NetworkLinkCollection::Instance() { if (ourInstance == 0) { ourInstance = new NetworkLinkCollection(); } return *ourInstance; } class NetworkLinkCollection::Comparator { public: bool operator() ( const shared_ptr &first, const shared_ptr &second ) const; private: std::string removeLeadingNonAscii(const std::string &title) const; }; std::string NetworkLinkCollection::Comparator::removeLeadingNonAscii(const std::string &title) const { std::string str = title; std::string::iterator it = str.begin(); for (; it != str.end(); ++it) { if ((*it & 0x80) == 0 && std::isalnum(*it)) { break; } } if (it != str.end()) { str.erase(str.begin(), it); } return str; } bool NetworkLinkCollection::Comparator::operator() ( const shared_ptr &first, const shared_ptr &second ) const { return removeLeadingNonAscii(first->getSiteName()) < removeLeadingNonAscii(second->getSiteName()); } //void NetworkLinkCollection::deleteLink(NetworkLink& link) { // BooksDB::Instance().deleteNetworkLink(link.SiteName); // for (std::vector >::iterator it = myLinks.begin(); it != myLinks.end(); ++it) { // if (&(**it) == &link) { // myLinks.erase(it); // break; // } // } // Reader::Instance().refreshWindow(); //} //void NetworkLinkCollection::saveLink(NetworkLink& link, bool isAuto) { // saveLinkWithoutRefreshing(link, isAuto); // Reader::Instance().refreshWindow(); //} void NetworkLinkCollection::addOrUpdateLink(shared_ptr link) { bool found = false; bool updated = false; for (std::size_t i = 0; i < myLinks.size(); ++i) { shared_ptr curLink = myLinks.at(i); if (curLink->getPredefinedId() == link->getPredefinedId()) { //if (*(link->getUpdated()) > *(curLink->getUpdated())) { myLinks.at(i) = link; updated = true; //TODO implement custom links saving found = true; break; } } if (!found) { myLinks.push_back(link); std::sort(myLinks.begin(), myLinks.end(), Comparator()); updated = true; } if (updated) { NetworkDB::Instance().saveNetworkLink(link); //Reader::Instance().sendRefresh(); } } static const std::string LOADING_NETWORK_LIBRARY_LIST = "loadingNetworkLibraryList"; class NetworkDownloadListRunnable : public NetworkOperationRunnable { public: NetworkDownloadListRunnable(ZLFile &tmpFile, const std::string &genericUrl) : NetworkOperationRunnable(LOADING_NETWORK_LIBRARY_LIST), myTmpFile(tmpFile), myGenericUrl(genericUrl) { } private: void run() { shared_ptr loadingRequest = ZLNetworkManager::Instance().createDownloadRequest(myGenericUrl, myTmpFile.physicalFilePath()); myErrorMessage = ZLNetworkManager::Instance().perform(loadingRequest); } private: ZLFile &myTmpFile; std::string myGenericUrl; }; class NetworkLinksUpdater : public ZLRunnable { public: NetworkLinksUpdater(NetworkLinkCollection &networkLinkCollection, shared_ptr genericFile) : myNetworkLinkCollection(networkLinkCollection), myGenericFile(genericFile) { } private: void run() { std::vector > links; shared_ptr feedReader = new OPDSLink::GenericFeedReader(links); shared_ptr parser = new OPDSLink::GenericXMLParser(feedReader); parser->readDocument(*myGenericFile); for (std::vector >::iterator it = links.begin(); it != links.end(); ++it) { myNetworkLinkCollection.addOrUpdateLink(*it); } } private: NetworkLinkCollection &myNetworkLinkCollection; shared_ptr myGenericFile; }; NetworkLinkCollection::NetworkLinkCollection() : DirectoryOption(ZLCategoryKey::NETWORK, "Options", "DownloadDirectory", ""), LastUpdateTimeOption(ZLCategoryKey::NETWORK, "Update", "LastUpdateTime", -1), myIsInitialized(false) { } class NetworkLinkCollectionSynchronizer : public DBRunnable { public: NetworkLinkCollectionSynchronizer(NetworkLinkCollection &collection) : myCollection(collection) {} bool run() { myCollection.synchronize(); return true; } private: NetworkLinkCollection &myCollection; }; void NetworkLinkCollection::initialize() { if (myIsInitialized) { return; } NetworkLinkCollectionSynchronizer runnable(*this); NetworkDB::Instance().executeAsTransaction(runnable); } void NetworkLinkCollection::synchronize() { NetworkDB::Instance().loadNetworkLinks(myLinks); std::sort(myLinks.begin(), myLinks.end(), Comparator()); updateLinks("http://data.fbreader.org/catalogs/generic-1.9.xml"); } void NetworkLinkCollection::updateLinks(std::string genericUrl) { shared_ptr genericFile = getGenericFile(genericUrl); if (genericFile.isNull()) { NetworkErrors::showErrorMessage(NetworkErrors::errorMessage(NetworkErrors::ERROR_CANT_DOWNLOAD_LIBRARIES_LIST)); return; } NetworkLinksUpdater updater(*this, genericFile); ZLDialogManager::Instance().wait(ZLResourceKey(LOADING_NETWORK_LIBRARY_LIST), updater); myIsInitialized = true; } shared_ptr NetworkLinkCollection::getGenericFile(std::string genericUrl) { const std::string FILE_NAME = "fbreader_catalogs-" + genericUrl.substr(genericUrl.find_last_of('/') + 1); ZLFile genericFileDir(ZLNetworkManager::CacheDirectory()); genericFileDir.directory(true); shared_ptr genericFile = new ZLFile(ZLNetworkManager::CacheDirectory() + ZLibrary::FileNameDelimiter + FILE_NAME); long diff = LastUpdateTimeOption.value() == -1 ? -1 : ZLTime().millisecondsFrom(ZLTime(LastUpdateTimeOption.value(), 0)); if (genericFile->exists() && diff != -1 && diff < 7 * 24 * 60 * 60 * 1000) { //1 week return genericFile; } ZLFile tmpFile(ZLNetworkManager::CacheDirectory() + ZLibrary::FileNameDelimiter + "tmp" + FILE_NAME); NetworkDownloadListRunnable runnable(tmpFile, genericUrl); runnable.executeWithUI(); if (runnable.hasErrors()) { if (!genericFile->exists()) { //loading list from saved file even if obsolete return 0; } else { return genericFile; } } shared_ptr outputStream = genericFile->outputStream(true); shared_ptr inputStream = tmpFile.inputStream(); if (!outputStream->open() || !inputStream->open()) { tmpFile.remove(); return 0; } char buffer[2048]; std::size_t readed = 0; do { readed = inputStream->read(buffer, 2048); outputStream->write(buffer, readed); } while (readed > 0); LastUpdateTimeOption.setValue(ZLTime().inSeconds()); return genericFile; } NetworkLinkCollection::~NetworkLinkCollection() { } static std::string normalize(const std::string &url) { static const std::string PREFIX0 = "http://feedbooks.com/"; static const std::string PREFIX1 = "http://www.feedbooks.com/"; static const std::string STANZA_PREFIX = "http://feedbooks.com/book/stanza/"; std::string nURL = url; if (ZLStringUtil::stringStartsWith(nURL, PREFIX1)) { nURL = PREFIX0 + nURL.substr(PREFIX1.length()); } if (ZLStringUtil::stringStartsWith(nURL, STANZA_PREFIX)) { nURL = PREFIX0 + "book/" + nURL.substr(STANZA_PREFIX.length()) + ".epub"; } return nURL; } std::string NetworkLinkCollection::bookFileName(const BookReference &reference) { myErrorMessage.clear(); return bookFileName(::normalize(reference.cleanURL()), reference.BookFormat, reference.ReferenceType); } static bool parseUrl(const std::string &url, std::string &hostAndPath, std::string &query) { std::size_t hostBegin = url.find("://"); if (hostBegin == std::string::npos) { return false; } hostBegin += 3; if (!url.compare(hostBegin, 4, "www.")) { hostBegin += 4; } std::size_t pathEnd = url.find('?', hostBegin); hostAndPath = url.substr(hostBegin, pathEnd - hostBegin); if (pathEnd != std::string::npos) { query = url.substr(pathEnd + 1); } return true; } std::string NetworkLinkCollection::bookFileName(const std::string &url, BookReference::Format format, BookReference::Type type) { static const std::string escapeChars = "<>:\"|?*\\"; std::string path; std::string query; if (!::parseUrl(url, path, query)) { return std::string(); } std::string fileName = DirectoryOption.value(); if (!ZLStringUtil::stringEndsWith(fileName, ZLibrary::FileNameDelimiter)) { fileName += ZLibrary::FileNameDelimiter; } if (type == BookReference::DOWNLOAD_DEMO) { fileName += "Demos" + ZLibrary::FileNameDelimiter; } for (std::size_t i = 0; i < path.size(); ++i) { char ch = path[i]; if (escapeChars.find(ch) != std::string::npos) { path[i] = '_'; } if (ch == '/') { path[i] = ZLibrary::FileNameDelimiter[0]; } } const std::size_t nameIndex = path.find_last_of(ZLibrary::FileNameDelimiter); if (nameIndex + 1 == path.length()) { path.resize(path.length() - 1); //removing ending / if exists } std::string ext; switch (format) { case BookReference::EPUB: ext = ".epub"; break; case BookReference::MOBIPOCKET: ext = ".mobi"; break; case BookReference::FB2_ZIP: ext = ".fb2.zip"; break; case BookReference::NONE: break; } if (ext.empty()) { std::size_t tmp = path.find('.', nameIndex); // using not find_last_of to preserve extensions like `.fb2.zip` if (tmp == std::string::npos) { return std::string(); } ext = path.substr(tmp); path.resize(tmp); } else if (ZLStringUtil::stringEndsWith(path, ext)) { path.resize(path.size() - ext.size()); } if (!query.empty()) { std::size_t index = 0; while (index < query.size()) { std::size_t j = query.find('&', index); if (j == std::string::npos) { j = query.size(); } std::string param = query.substr(index, j); if (!ZLStringUtil::stringStartsWith(param, "username=") && !ZLStringUtil::stringStartsWith(param, "password=") && !ZLStringUtil::stringEndsWith(param, "=")) { std::size_t k = path.size(); path.append("_").append(param); while (k < path.size()) { char ch = path[k]; if (escapeChars.find(ch) != std::string::npos || ch == '/') { path[k] = '_'; } ++k; } } index = j + 1; } } fileName.append(path); fileName.append(ext); return fileName; } bool NetworkLinkCollection::downloadBook(const BookReference &reference, std::string &fileName, shared_ptr listener) { std::string nURL = ::normalize(reference.URL); rewriteUrl(nURL); const std::string nNetworkBookId = ::normalize(reference.cleanURL()); const ZLResource &errorResource = ZLResource::resource("dialog")["networkError"]; myErrorMessage.clear(); if (nURL.empty() || nNetworkBookId.empty()) { myErrorMessage = errorResource["unknownErrorMessage"].value(); return false; } fileName = bookFileName(nNetworkBookId, reference.BookFormat, reference.ReferenceType); //creating directory if not existed const std::size_t directoryIndex = fileName.find_last_of(ZLibrary::FileNameDelimiter); ZLFile(fileName.substr(0, directoryIndex)).directory(true); if (ZLFile(fileName).exists()) { return true; } if (fileName.empty()) { if (myErrorMessage.empty()) { myErrorMessage = errorResource["unknownErrorMessage"].value(); } return false; } if (ZLFile(fileName).exists()) { ZLFile(fileName).remove(); } ZLNetworkManager::Instance().downloadFile(nURL, fileName, listener); return true; } shared_ptr NetworkLinkCollection::simpleSearch(const std::string &pattern) { ZLNetworkRequest::Vector dataList; std::vector > opDataVector; shared_ptr result; myErrorMessage.clear(); for (std::vector >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { NetworkLink &link = **it; if (link.isEnabled()) { shared_ptr opData = new NetworkOperationData(link); opDataVector.push_back(opData); shared_ptr data = link.simpleSearchData(*opData, pattern); if (!data.isNull()) { dataList.push_back(data); } } } while (myErrorMessage.empty() && !dataList.empty()) { myErrorMessage = ZLNetworkManager::Instance().perform(dataList); for (std::vector >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { NetworkOperationData &opData = **jt; if (!opData.Items.empty() && result.isNull()) { result = new NetworkBookCollection(); } for (NetworkItem::List::const_iterator kt = opData.Items.begin(); kt != opData.Items.end(); ++kt) { result->addBook(*kt); } } dataList.clear(); for (std::vector >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { shared_ptr data = (*jt)->resume(); if (!data.isNull()) { dataList.push_back(data); } } } return result; } shared_ptr NetworkLinkCollection::advancedSearch(const std::string &titleAndSeries, const std::string &author, const std::string &tag, const std::string &annotation) { ZLNetworkRequest::Vector dataList; std::vector > opDataVector; shared_ptr result; myErrorMessage.clear(); for (std::vector >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { NetworkLink &link = **it; if (link.isEnabled()) { shared_ptr opData = new NetworkOperationData(link); opDataVector.push_back(opData); shared_ptr data = link.advancedSearchData(*opData, titleAndSeries, author, tag, annotation); if (!data.isNull()) { dataList.push_back(data); } } } while (myErrorMessage.empty() && !dataList.empty()) { myErrorMessage = ZLNetworkManager::Instance().perform(dataList); for (std::vector >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { NetworkOperationData &opData = **jt; if (!opData.Items.empty() && result.isNull()) { result = new NetworkBookCollection(); } for (NetworkItem::List::const_iterator kt = opData.Items.begin(); kt != opData.Items.end(); ++kt) { result->addBook(*kt); } } dataList.clear(); for (std::vector >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { shared_ptr data = (*jt)->resume(); if (!data.isNull()) { dataList.push_back(data); } } } return result; } NetworkLinkCollection::LinkVector NetworkLinkCollection::activeLinks() const { LinkVector filteredList; for (std::size_t i = 0; i < myLinks.size(); ++i) { shared_ptr link = myLinks.at(i); if (link->isEnabled()) { filteredList.push_back(link); } } return filteredList; } std::size_t NetworkLinkCollection::size() const { return myLinks.size(); } NetworkLink &NetworkLinkCollection::link(std::size_t index) const { return *myLinks[index]; } std::size_t NetworkLinkCollection::numberOfEnabledLinks() const { std::size_t count = 0; for (std::vector >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { if ((*it)->isEnabled()) { ++count; } } return count; } void NetworkLinkCollection::rewriteUrl(std::string &url, bool externalUrl) const { const std::string host = ZLUnicodeUtil::toLower(ZLNetworkUtil::hostFromUrl(url)); for (std::vector >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { if (host.find((*it)->getSiteName()) != std::string::npos) { (*it)->rewriteUrl(url, externalUrl); } } }