diff options
Diffstat (limited to 'fbreader/src/network/NetworkLinkCollection.cpp')
-rw-r--r-- | fbreader/src/network/NetworkLinkCollection.cpp | 561 |
1 files changed, 561 insertions, 0 deletions
diff --git a/fbreader/src/network/NetworkLinkCollection.cpp b/fbreader/src/network/NetworkLinkCollection.cpp new file mode 100644 index 0000000..0b530b0 --- /dev/null +++ b/fbreader/src/network/NetworkLinkCollection.cpp @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2008-2012 Geometer Plus <[email protected]> + * + * 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 <cctype> +#include <algorithm> + +#include <ZLFile.h> +#include <ZLDir.h> +#include <ZLStringUtil.h> +#include <ZLUnicodeUtil.h> +#include <ZLResource.h> +#include <ZLNetworkManager.h> +#include <ZLTimeManager.h> +#include <ZLNetworkUtil.h> +#include <ZLibrary.h> +#include <ZLDialogManager.h> +#include <ZLInputStream.h> +#include <ZLOutputStream.h> +#include "../fbreader/FBReader.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<NetworkLink> &first, + const shared_ptr<NetworkLink> &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<NetworkLink> &first, + const shared_ptr<NetworkLink> &second +) const { + return + removeLeadingNonAscii(first->getSiteName()) < + removeLeadingNonAscii(second->getSiteName()); +} + +//void NetworkLinkCollection::deleteLink(NetworkLink& link) { +// BooksDB::Instance().deleteNetworkLink(link.SiteName); +// for (std::vector<shared_ptr<NetworkLink> >::iterator it = myLinks.begin(); it != myLinks.end(); ++it) { +// if (&(**it) == &link) { +// myLinks.erase(it); +// break; +// } +// } +// FBReader::Instance().refreshWindow(); +//} + +//void NetworkLinkCollection::saveLink(NetworkLink& link, bool isAuto) { +// saveLinkWithoutRefreshing(link, isAuto); +// FBReader::Instance().refreshWindow(); +//} + +void NetworkLinkCollection::addOrUpdateLink(shared_ptr<NetworkLink> link) { + bool found = false; + bool updated = false; + + for (std::size_t i = 0; i < myLinks.size(); ++i) { + shared_ptr<NetworkLink> 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); + //FBReader::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<ZLNetworkRequest> 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<ZLFile> genericFile) : + myNetworkLinkCollection(networkLinkCollection), myGenericFile(genericFile) { } + +private: + void run() { + std::vector<shared_ptr<NetworkLink> > links; + shared_ptr<OPDSFeedReader> feedReader = new OPDSLink::GenericFeedReader(links); + shared_ptr<ZLXMLReader> parser = new OPDSLink::GenericXMLParser(feedReader); + parser->readDocument(*myGenericFile); + + for (std::vector<shared_ptr<NetworkLink> >::iterator it = links.begin(); it != links.end(); ++it) { + myNetworkLinkCollection.addOrUpdateLink(*it); + } + } + +private: + NetworkLinkCollection &myNetworkLinkCollection; + shared_ptr<ZLFile> 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<ZLFile> 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<ZLFile> 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<ZLFile> 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<ZLOutputStream> outputStream = genericFile->outputStream(true); + shared_ptr<ZLInputStream> 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<ZLNetworkRequest::Listener> 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<NetworkBookCollection> NetworkLinkCollection::simpleSearch(const std::string &pattern) { + ZLNetworkRequest::Vector dataList; + std::vector<shared_ptr<NetworkOperationData> > opDataVector; + shared_ptr<NetworkBookCollection> result; + + myErrorMessage.clear(); + + for (std::vector<shared_ptr<NetworkLink> >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { + NetworkLink &link = **it; + if (link.isEnabled()) { + shared_ptr<NetworkOperationData> opData = new NetworkOperationData(link); + opDataVector.push_back(opData); + shared_ptr<ZLNetworkRequest> 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<shared_ptr<NetworkOperationData> >::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<shared_ptr<NetworkOperationData> >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { + shared_ptr<ZLNetworkRequest> data = (*jt)->resume(); + if (!data.isNull()) { + dataList.push_back(data); + } + } + } + + return result; +} + +shared_ptr<NetworkBookCollection> NetworkLinkCollection::advancedSearch(const std::string &titleAndSeries, const std::string &author, const std::string &tag, const std::string &annotation) { + ZLNetworkRequest::Vector dataList; + std::vector<shared_ptr<NetworkOperationData> > opDataVector; + shared_ptr<NetworkBookCollection> result; + + myErrorMessage.clear(); + + for (std::vector<shared_ptr<NetworkLink> >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { + NetworkLink &link = **it; + if (link.isEnabled()) { + shared_ptr<NetworkOperationData> opData = new NetworkOperationData(link); + opDataVector.push_back(opData); + shared_ptr<ZLNetworkRequest> 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<shared_ptr<NetworkOperationData> >::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<shared_ptr<NetworkOperationData> >::const_iterator jt = opDataVector.begin(); jt != opDataVector.end(); ++jt) { + shared_ptr<ZLNetworkRequest> 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<NetworkLink> 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<shared_ptr<NetworkLink> >::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<shared_ptr<NetworkLink> >::const_iterator it = myLinks.begin(); it != myLinks.end(); ++it) { + if (host.find((*it)->getSiteName()) != std::string::npos) { + (*it)->rewriteUrl(url, externalUrl); + } + } +} |