diff --git a/src/io/bibutils.cpp b/src/io/bibutils.cpp index 02a26e1c..5fca7136 100644 --- a/src/io/bibutils.cpp +++ b/src/io/bibutils.cpp @@ -1,210 +1,210 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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, see . * ***************************************************************************/ #include "bibutils.h" #include #include #include #include #include "logging_io.h" class BibUtils::Private { public: BibUtils::Format format; Private(BibUtils *parent) : format(BibUtils::Format::MODS) { Q_UNUSED(parent) } }; BibUtils::BibUtils() : d(new BibUtils::Private(this)) { /// nothing } BibUtils::~BibUtils() { delete d; } void BibUtils::setFormat(const BibUtils::Format format) { d->format = format; } BibUtils::Format BibUtils::format() const { return d->format; } bool BibUtils::available() { enum State {untested, avail, unavail}; static State state = untested; /// Perform test only once, later rely on statically stored result if (state == untested) { /// Test a number of known BibUtils programs static const QStringList programs {QStringLiteral("bib2xml"), QStringLiteral("isi2xml"), QStringLiteral("ris2xml"), QStringLiteral("end2xml")}; state = avail; for (const QString &program : programs) { const QString fullPath = QStandardPaths::findExecutable(program); if (fullPath.isEmpty()) { state = unavail; ///< missing a single program is reason to assume that BibUtils is not correctly installed break; } } if (state == avail) qCDebug(LOG_KBIBTEX_IO) << "BibUtils found, using it to import/export certain types of bibliographies"; else if (state == unavail) qCWarning(LOG_KBIBTEX_IO) << "No or only an incomplete installation of BibUtils found"; } return state == avail; } bool BibUtils::convert(QIODevice &source, const BibUtils::Format sourceFormat, QIODevice &destination, const BibUtils::Format destinationFormat) const { /// To proceed, either the source format or the destination format /// has to be MODS, otherwise ... if (sourceFormat != BibUtils::Format::MODS && destinationFormat != BibUtils::Format::MODS) { /// Add indirection: convert source format to MODS, /// then convert MODS data to destination format /// Intermediate buffer to hold MODS data QBuffer buffer; bool result = convert(source, sourceFormat, buffer, BibUtils::Format::MODS); if (result) result = convert(buffer, BibUtils::Format::MODS, destination, destinationFormat); return result; } QString bibUtilsProgram; QString utf8Argument = QStringLiteral("-un"); /// Determine part of BibUtils program name that represents source format switch (sourceFormat) { case BibUtils::Format::MODS: bibUtilsProgram = QStringLiteral("xml"); utf8Argument = QStringLiteral("-nb"); break; case BibUtils::Format::BibTeX: bibUtilsProgram = QStringLiteral("bib"); break; case BibUtils::Format::BibLaTeX: bibUtilsProgram = QStringLiteral("biblatex"); break; case BibUtils::Format::ISI: bibUtilsProgram = QStringLiteral("isi"); break; case BibUtils::Format::RIS: bibUtilsProgram = QStringLiteral("ris"); break; case BibUtils::Format::EndNote: bibUtilsProgram = QStringLiteral("end"); break; case BibUtils::Format::EndNoteXML: bibUtilsProgram = QStringLiteral("endx"); break; /// case ADS not supported by BibUtils case BibUtils::Format::WordBib: bibUtilsProgram = QStringLiteral("wordbib"); break; case BibUtils::Format::Copac: bibUtilsProgram = QStringLiteral("copac"); break; case BibUtils::Format::Med: bibUtilsProgram = QStringLiteral("med"); break; default: qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils input format:" << sourceFormat; return false; } bibUtilsProgram.append(QStringLiteral("2")); /// Determine part of BibUtils program name that represents destination format switch (destinationFormat) { case BibUtils::Format::MODS: bibUtilsProgram.append(QStringLiteral("xml")); break; case BibUtils::Format::BibTeX: bibUtilsProgram.append(QStringLiteral("bib")); break; /// case BibLaTeX not supported by BibUtils case BibUtils::Format::ISI: bibUtilsProgram.append(QStringLiteral("isi")); break; case BibUtils::Format::RIS: bibUtilsProgram.append(QStringLiteral("ris")); break; case BibUtils::Format::EndNote: bibUtilsProgram.append(QStringLiteral("end")); break; /// case EndNoteXML not supported by BibUtils case BibUtils::Format::ADS: bibUtilsProgram.append(QStringLiteral("ads")); break; case BibUtils::Format::WordBib: bibUtilsProgram.append(QStringLiteral("wordbib")); break; /// case Copac not supported by BibUtils /// case Med not supported by BibUtils default: qCWarning(LOG_KBIBTEX_IO) << "Unsupported BibUtils output format:" << destinationFormat; return false; } /// Test if required BibUtils program is available bibUtilsProgram = QStandardPaths::findExecutable(bibUtilsProgram); if (bibUtilsProgram.isEmpty()) return false; /// Test if source device is readable if (!source.isReadable() && !source.open(QIODevice::ReadOnly)) return false; /// Test if destination device is writable if (!destination.isWritable() && !destination.open(QIODevice::WriteOnly)) { source.close(); return false; } QProcess bibUtilsProcess; const QStringList arguments {QStringLiteral("-i"), QStringLiteral("utf8"), utf8Argument}; /// Start BibUtils program/process bibUtilsProcess.start(bibUtilsProgram, arguments); bool result = bibUtilsProcess.waitForStarted(); if (result) { /// Write source data to process's stdin bibUtilsProcess.write(source.readAll()); /// Close process's stdin start transformation bibUtilsProcess.closeWriteChannel(); result = bibUtilsProcess.waitForFinished(); /// If process run without problems ... if (result && bibUtilsProcess.exitStatus() == QProcess::NormalExit) { /// Read process's output, i.e. the transformed data const QByteArray stdOut = bibUtilsProcess.readAllStandardOutput(); if (!stdOut.isEmpty()) { /// Write transformed data to destination device - const int amountWritten = destination.write(stdOut); + const int amountWritten = static_cast(destination.write(stdOut)); /// Check that the same amount of bytes is written /// as received from the BibUtils program result = amountWritten == stdOut.size(); } else result = false; } else result = false; } /// In case it did not terminate earlier bibUtilsProcess.terminate(); /// Close both source and destination device source.close(); destination.close(); return result; } QDebug operator<<(QDebug dbg, const BibUtils::Format &format) { static const auto pairs = QHash { {static_cast(BibUtils::Format::MODS), "MODS"}, {static_cast(BibUtils::Format::BibTeX), "BibTeX"}, {static_cast(BibUtils::Format::BibLaTeX), "BibLaTeX"}, {static_cast(BibUtils::Format::ISI), "ISI"}, {static_cast(BibUtils::Format::RIS), "RIS"}, {static_cast(BibUtils::Format::EndNote), "EndNote"}, {static_cast(BibUtils::Format::EndNoteXML), "EndNoteXML"}, {static_cast(BibUtils::Format::ADS), "ADS"}, {static_cast(BibUtils::Format::WordBib), "WordBib"}, {static_cast(BibUtils::Format::Copac), "Copac"}, {static_cast(BibUtils::Format::Med), "Med"} }; dbg.nospace(); const int formatInt = static_cast(format); dbg << (pairs.contains(formatInt) ? pairs[formatInt] : "???"); return dbg; } diff --git a/src/networking/findpdf.cpp b/src/networking/findpdf.cpp index 15f70e3c..7e99c793 100644 --- a/src/networking/findpdf.cpp +++ b/src/networking/findpdf.cpp @@ -1,455 +1,455 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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, see . * ***************************************************************************/ #include "findpdf.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "internalnetworkaccessmanager.h" #include "logging_networking.h" int maxDepth = 5; static const char *depthProperty = "depth"; static const char *termProperty = "term"; static const char *originProperty = "origin"; class FindPDF::Private { private: FindPDF *p; public: int aliveCounter; QList result; Entry currentEntry; QSet knownUrls; QSet runningDownloads; Private(FindPDF *parent) : p(parent), aliveCounter(0) { /// nothing } bool queueUrl(const QUrl &url, const QString &term, const QString &origin, int depth) { if (!knownUrls.contains(url) && depth > 0) { knownUrls.insert(url); QNetworkRequest request = QNetworkRequest(url); QNetworkReply *reply = InternalNetworkAccessManager::instance().get(request); InternalNetworkAccessManager::instance().setNetworkReplyTimeout(reply, 15); ///< set a timeout on network connections reply->setProperty(depthProperty, QVariant::fromValue(depth)); reply->setProperty(termProperty, term); reply->setProperty(originProperty, origin); runningDownloads.insert(reply); connect(reply, &QNetworkReply::finished, p, &FindPDF::downloadFinished); ++aliveCounter; return true; } else return false; } void processGeneralHTML(QNetworkReply *reply, const QString &text) { /// fetch some properties from Reply object const QString term = reply->property(termProperty).toString(); const QString origin = reply->property(originProperty).toString(); bool ok = false; int depth = reply->property(depthProperty).toInt(&ok); if (!ok) depth = 0; /// regular expressions to guess links to follow const QVector specificAnchorRegExp = { QRegularExpression(QString(QStringLiteral("]*href=\"([^\"]*%1[^\"]*[.]pdf)\"")).arg(QRegularExpression::escape(term)), QRegularExpression::CaseInsensitiveOption), QRegularExpression(QString(QStringLiteral("]*href=\"([^\"]+)\"[^>]*>[^<]*%1[^<]*[.]pdf")).arg(QRegularExpression::escape(term)), QRegularExpression::CaseInsensitiveOption), QRegularExpression(QString(QStringLiteral("]*href=\"([^\"]*%1[^\"]*)\"")).arg(QRegularExpression::escape(term)), QRegularExpression::CaseInsensitiveOption), QRegularExpression(QString(QStringLiteral("]*href=\"([^\"]+)\"[^>]*>[^<]*%1[^<]*\\b")).arg(QRegularExpression::escape(term)), QRegularExpression::CaseInsensitiveOption) }; static const QRegularExpression genericAnchorRegExp = QRegularExpression(QStringLiteral("]*href=\"([^\"]+)\""), QRegularExpression::CaseInsensitiveOption); bool gotLink = false; for (const QRegularExpression &anchorRegExp : specificAnchorRegExp) { const QRegularExpressionMatch match = anchorRegExp.match(text); if (match.hasMatch()) { const QUrl url = QUrl::fromEncoded(match.captured(1).toLatin1()); queueUrl(reply->url().resolved(url), term, origin, depth - 1); gotLink = true; break; } } if (!gotLink) { /// this is only the last resort: /// to follow the first link found in the HTML document const QRegularExpressionMatch match = genericAnchorRegExp.match(text); if (match.hasMatch()) { const QUrl url = QUrl::fromEncoded(match.captured(1).toLatin1()); queueUrl(reply->url().resolved(url), term, origin, depth - 1); } } } void processGoogleResult(QNetworkReply *reply, const QString &text) { static const QString h3Tag(QStringLiteral("property(termProperty).toString(); bool ok = false; int depth = reply->property(depthProperty).toInt(&ok); if (!ok) depth = 0; /// extract the first numHitsToFollow-many hits found by Google Scholar const int numHitsToFollow = 10; int p = -1; for (int i = 0; i < numHitsToFollow; ++i) { if ((p = text.indexOf(h3Tag, p + 1)) >= 0 && (p = text.indexOf(aTag, p + 1)) >= 0 && (p = text.indexOf(hrefAttrib, p + 1)) >= 0) { int p1 = p + 6; int p2 = text.indexOf(QLatin1Char('"'), p1 + 1); QUrl url(text.mid(p1, p2 - p1)); const QString googleService = reply->url().host().contains(QStringLiteral("scholar.google")) ? QStringLiteral("scholar.google") : QStringLiteral("www.google"); queueUrl(reply->url().resolved(url), term, googleService, depth - 1); } } } void processSpringerLink(QNetworkReply *reply, const QString &text) { static const QRegularExpression fulltextPDFlink(QStringLiteral("href=\"([^\"]+/fulltext.pdf)\"")); const QRegularExpressionMatch match = fulltextPDFlink.match(text); if (match.hasMatch()) { bool ok = false; int depth = reply->property(depthProperty).toInt(&ok); if (!ok) depth = 0; const QUrl url(match.captured(1)); queueUrl(reply->url().resolved(url), QString(), QStringLiteral("springerlink"), depth - 1); } } void processCiteSeerX(QNetworkReply *reply, const QString &text) { static const QRegularExpression downloadPDFlink(QStringLiteral("href=\"(/viewdoc/download[^\"]+type=pdf)\"")); const QRegularExpressionMatch match = downloadPDFlink.match(text); if (match.hasMatch()) { bool ok = false; int depth = reply->property(depthProperty).toInt(&ok); if (!ok) depth = 0; const QUrl url = QUrl::fromEncoded(match.captured(1).toLatin1()); queueUrl(reply->url().resolved(url), QString(), QStringLiteral("citeseerx"), depth - 1); } } void processACMDigitalLibrary(QNetworkReply *reply, const QString &text) { static const QRegularExpression downloadPDFlink(QStringLiteral("href=\"(ft_gateway.cfm\\?id=\\d+&ftid=\\d+&dwn=1&CFID=\\d+&CFTOKEN=\\d+)\"")); const QRegularExpressionMatch match = downloadPDFlink.match(text); if (match.hasMatch()) { bool ok = false; int depth = reply->property(depthProperty).toInt(&ok); if (!ok) depth = 0; const QUrl url = QUrl::fromEncoded(match.captured(1).toLatin1()); queueUrl(reply->url().resolved(url), QString(), QStringLiteral("acmdl"), depth - 1); } } bool processPDF(QNetworkReply *reply, const QByteArray &data) { bool progress = false; const QString origin = reply->property(originProperty).toString(); const QUrl url = reply->url(); /// Search for duplicate URLs bool containsUrl = false; for (const ResultItem &ri : const_cast &>(result)) { containsUrl |= ri.url == url; /// Skip already visited URLs if (containsUrl) break; } if (!containsUrl) { Poppler::Document *doc = Poppler::Document::loadFromData(data); ResultItem resultItem; resultItem.tempFilename = new QTemporaryFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QDir::separator() + QStringLiteral("kbibtex_findpdf_XXXXXX.pdf")); resultItem.tempFilename->setAutoRemove(true); if (resultItem.tempFilename->open()) { - const int lenDataWritten = resultItem.tempFilename->write(data); + const int lenDataWritten = static_cast(resultItem.tempFilename->write(data)); resultItem.tempFilename->close(); if (lenDataWritten != data.length()) { /// Failed to write to temporary file qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to write to temporary file for filename" << resultItem.tempFilename->fileName(); delete resultItem.tempFilename; resultItem.tempFilename = nullptr; } } else { /// Failed to create temporary file qCWarning(LOG_KBIBTEX_NETWORKING) << "Failed to create temporary file for templaet" << resultItem.tempFilename->fileTemplate(); delete resultItem.tempFilename; resultItem.tempFilename = nullptr; } resultItem.url = url; resultItem.textPreview = doc->info(QStringLiteral("Title")).simplified(); static const int maxTextLen = 1024; for (int i = 0; i < doc->numPages() && resultItem.textPreview.length() < maxTextLen; ++i) { Poppler::Page *page = doc->page(i); if (!resultItem.textPreview.isEmpty()) resultItem.textPreview += QLatin1Char(' '); resultItem.textPreview += page->text(QRect()).simplified().leftRef(maxTextLen); delete page; } resultItem.textPreview.remove(QStringLiteral("Microsoft Word - ")); ///< Some word processors need to put their name everywhere ... resultItem.downloadMode = DownloadMode::No; resultItem.relevance = origin == Entry::ftDOI ? 1.0 : (origin == QStringLiteral("eprint") ? 0.75 : 0.5); result << resultItem; progress = true; delete doc; } return progress; } QUrl ieeeDocumentUrlToDownloadUrl(const QUrl &url) { /// Basic checking if provided URL is from IEEE Xplore if (!url.host().contains(QStringLiteral("ieeexplore.ieee.org"))) return url; /// Assuming URL looks like this: /// http://ieeexplore.ieee.org/document/8092651/ static const QRegularExpression documentIdRegExp(QStringLiteral("/(\\d{6,})/$")); const QRegularExpressionMatch documentIdRegExpMatch = documentIdRegExp.match(url.path()); if (!documentIdRegExpMatch.hasMatch()) return url; /// Use document id extracted above to build URL to PDF file return QUrl(QStringLiteral("http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=") + documentIdRegExpMatch.captured(1)); } }; FindPDF::FindPDF(QObject *parent) : QObject(parent), d(new Private(this)) { /// nothing } FindPDF::~FindPDF() { abort(); delete d; } bool FindPDF::search(const Entry &entry) { if (d->aliveCounter > 0) return false; d->knownUrls.clear(); d->result.clear(); d->currentEntry = entry; emit progress(0, d->aliveCounter, 0); /// Generate a string which contains the title's beginning QString searchWords; if (entry.contains(Entry::ftTitle)) { const QStringList titleChunks = PlainTextValue::text(entry.value(Entry::ftTitle)).split(QStringLiteral(" "), QString::SkipEmptyParts); if (!titleChunks.isEmpty()) { searchWords = titleChunks[0]; for (int i = 1; i < titleChunks.count() && searchWords.length() < 64; ++i) searchWords += QLatin1Char(' ') + titleChunks[i]; } } const QStringList authors = entry.authorsLastName(); for (int i = 0; i < authors.count() && searchWords.length() < 96; ++i) searchWords += QLatin1Char(' ') + authors[i]; searchWords.remove(QLatin1Char('{')).remove(QLatin1Char('}')); QStringList urlFields {Entry::ftDOI, Entry::ftUrl, QStringLiteral("ee")}; for (int i = 2; i < 256; ++i) urlFields << QString(QStringLiteral("%1%2")).arg(Entry::ftDOI).arg(i) << QString(QStringLiteral("%1%2")).arg(Entry::ftUrl).arg(i); for (const QString &field : const_cast(urlFields)) { if (entry.contains(field)) { const QString fieldText = PlainTextValue::text(entry.value(field)); QRegularExpressionMatchIterator doiRegExpMatchIt = KBibTeX::doiRegExp.globalMatch(fieldText); while (doiRegExpMatchIt.hasNext()) { const QRegularExpressionMatch doiRegExpMatch = doiRegExpMatchIt.next(); d->queueUrl(QUrl(KBibTeX::doiUrlPrefix + doiRegExpMatch.captured(0)), fieldText, Entry::ftDOI, maxDepth); } QRegularExpressionMatchIterator urlRegExpMatchIt = KBibTeX::urlRegExp.globalMatch(fieldText); while (urlRegExpMatchIt.hasNext()) { QRegularExpressionMatch urlRegExpMatch = urlRegExpMatchIt.next(); d->queueUrl(QUrl(urlRegExpMatch.captured(0)), searchWords, Entry::ftUrl, maxDepth); } } } if (entry.contains(QStringLiteral("eprint"))) { /// check eprint fields as used for arXiv const QString eprintId = PlainTextValue::text(entry.value(QStringLiteral("eprint"))); if (!eprintId.isEmpty()) { const QUrl arxivUrl = QUrl::fromUserInput(QStringLiteral("http://arxiv.org/find/all/1/all:+") + eprintId + QStringLiteral("/0/1/0/all/0/1")); d->queueUrl(arxivUrl, eprintId, QStringLiteral("eprint"), maxDepth); } } if (!searchWords.isEmpty()) { /// Search in Google const QUrl googleUrl = QUrl::fromUserInput(QStringLiteral("https://www.google.com/search?hl=en&sa=G&q=filetype:pdf ") + searchWords); d->queueUrl(googleUrl, searchWords, QStringLiteral("www.google"), maxDepth); /// Search in Google Scholar const QUrl googleScholarUrl = QUrl::fromUserInput(QStringLiteral("https://scholar.google.com/scholar?hl=en&btnG=Search&as_sdt=1&q=filetype:pdf ") + searchWords); d->queueUrl(googleScholarUrl, searchWords, QStringLiteral("scholar.google"), maxDepth); /// Search in Bing const QUrl bingUrl = QUrl::fromUserInput(QStringLiteral("https://www.bing.com/search?setlang=en-US&q=filetype:pdf ") + searchWords); d->queueUrl(bingUrl, searchWords, QStringLiteral("bing"), maxDepth); /// Search in CiteSeerX const QUrl citeseerXurl = QUrl::fromUserInput(QStringLiteral("http://citeseerx.ist.psu.edu/search?submit=Search&sort=rlv&t=doc&q=") + searchWords); d->queueUrl(citeseerXurl, searchWords, QStringLiteral("citeseerx"), maxDepth); /// Search in StartPage const QUrl startPageUrl = QUrl::fromUserInput(QStringLiteral("https://www.startpage.com/do/asearch?cat=web&cmd=process_search&language=english&engine0=v1all&abp=-1&t=white&nj=1&prf=23ad6aab054a88d3da5c443280cee596&suggestOn=0&query=filetype:pdf ") + searchWords); d->queueUrl(startPageUrl, searchWords, QStringLiteral("startpage"), maxDepth); } if (d->aliveCounter == 0) { qCWarning(LOG_KBIBTEX_NETWORKING) << "Directly at start, no URLs are queue for a search -> this should never happen"; emit finished(); } return true; } QList FindPDF::results() { if (d->aliveCounter == 0) return d->result; else { /// Return empty list while search is running return QList(); } } void FindPDF::abort() { QSet::Iterator it = d->runningDownloads.begin(); while (it != d->runningDownloads.end()) { QNetworkReply *reply = *it; it = d->runningDownloads.erase(it); reply->abort(); } } void FindPDF::downloadFinished() { static const char *htmlHead1 = "aliveCounter; emit progress(d->knownUrls.count(), d->aliveCounter, d->result.count()); QNetworkReply *reply = static_cast(sender()); d->runningDownloads.remove(reply); const QString term = reply->property(termProperty).toString(); const QString origin = reply->property(originProperty).toString(); bool depthOk = false; int depth = reply->property(depthProperty).toInt(&depthOk); if (!depthOk) depth = 0; if (reply->error() == QNetworkReply::NoError) { const QByteArray data = reply->readAll(); QUrl redirUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); redirUrl = redirUrl.isValid() ? reply->url().resolved(redirUrl) : QUrl(); qCDebug(LOG_KBIBTEX_NETWORKING) << "finished Downloading " << reply->url().toDisplayString() << " depth=" << depth << " d->aliveCounter=" << d->aliveCounter << " data.size=" << data.size() << " redirUrl=" << redirUrl.toDisplayString() << " origin=" << origin; if (redirUrl.isValid()) { redirUrl = d->ieeeDocumentUrlToDownloadUrl(redirUrl); d->queueUrl(redirUrl, term, origin, depth - 1); } else if (data.contains(htmlHead1) || data.contains(htmlHead2) || data.contains(htmlHead3)) { /// returned data is a HTML file, i.e. contains " 0) { /// Get webpage as plain text /// Assume UTF-8 data const QString text = QString::fromUtf8(data.constData()); /// regular expression to check if this is a Google Scholar result page static const QRegularExpression googleScholarTitleRegExp(QStringLiteral("[^>]* - Google Scholar")); /// regular expression to check if this is a SpringerLink page static const QRegularExpression springerLinkTitleRegExp(QStringLiteral("[^>]* - Springer - [^>]*")); /// regular expression to check if this is a CiteSeerX page static const QRegularExpression citeseerxTitleRegExp(QStringLiteral("CiteSeerX — [^>]*")); /// regular expression to check if this is a ACM Digital Library page static const QString acmDigitalLibraryString(QStringLiteral("The ACM Digital Library is published by the Association for Computing Machinery")); if (googleScholarTitleRegExp.match(text).hasMatch()) d->processGoogleResult(reply, text); else if (springerLinkTitleRegExp.match(text).hasMatch()) d->processSpringerLink(reply, text); else if (citeseerxTitleRegExp.match(text).hasMatch()) d->processCiteSeerX(reply, text); else if (text.contains(acmDigitalLibraryString)) d->processACMDigitalLibrary(reply, text); else { /// regular expression to extract title static const QRegularExpression titleRegExp(QStringLiteral("(.*?)")); const QRegularExpressionMatch match = titleRegExp.match(text); if (match.hasMatch()) qCDebug(LOG_KBIBTEX_NETWORKING) << "Using general HTML processor for page" << match.captured(1) << " URL=" << reply->url().toDisplayString(); else qCDebug(LOG_KBIBTEX_NETWORKING) << "Using general HTML processor for URL=" << reply->url().toDisplayString(); d->processGeneralHTML(reply, text); } } } else if (data.contains(pdfHead)) { /// looks like a PDF file -> grab it const bool gotPDFfile = d->processPDF(reply, data); if (gotPDFfile) emit progress(d->knownUrls.count(), d->aliveCounter, d->result.count()); } else { /// Assume UTF-8 data const QString text = QString::fromUtf8(data.constData()); qCWarning(LOG_KBIBTEX_NETWORKING) << "don't know how to handle " << text.left(256); } } else qCWarning(LOG_KBIBTEX_NETWORKING) << "error from reply: " << reply->errorString() << "(" << reply->url().toDisplayString() << ")" << " term=" << term << " origin=" << origin << " depth=" << depth; if (d->aliveCounter == 0) { /// no more running downloads left emit finished(); } } diff --git a/src/program/openfileinfo.cpp b/src/program/openfileinfo.cpp index af590b8f..50c58cff 100644 --- a/src/program/openfileinfo.cpp +++ b/src/program/openfileinfo.cpp @@ -1,713 +1,713 @@ /*************************************************************************** * Copyright (C) 2004-2019 by Thomas Fischer * * * * 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, see . * ***************************************************************************/ #include "openfileinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logging_program.h" class OpenFileInfo::OpenFileInfoPrivate { private: static int globalCounter; int m_counter; public: static const QString keyLastAccess; static const QString keyURL; static const QString dateTimeFormat; OpenFileInfo *p; KParts::ReadOnlyPart *part; KService::Ptr internalServicePtr; QWidget *internalWidgetParent; QDateTime lastAccessDateTime; StatusFlags flags; OpenFileInfoManager *openFileInfoManager; QString mimeType; QUrl url; OpenFileInfoPrivate(OpenFileInfoManager *openFileInfoManager, const QUrl &url, const QString &mimeType, OpenFileInfo *p) : m_counter(-1), p(p), part(nullptr), internalServicePtr(KService::Ptr()), internalWidgetParent(nullptr), flags(nullptr) { this->openFileInfoManager = openFileInfoManager; this->url = url; if (this->url.isValid() && this->url.scheme().isEmpty()) qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << this->url.toDisplayString(); this->mimeType = mimeType; } ~OpenFileInfoPrivate() { if (part != nullptr) { KParts::ReadWritePart *rwp = qobject_cast(part); if (rwp != nullptr) rwp->closeUrl(true); delete part; } } KParts::ReadOnlyPart *createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr()) { if (!p->flags().testFlag(OpenFileInfo::StatusFlag::Open)) { qCWarning(LOG_KBIBTEX_PROGRAM) << "Cannot create part for a file which is not open"; return nullptr; } Q_ASSERT_X(internalWidgetParent == nullptr || internalWidgetParent == newWidgetParent, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "internal widget should be either NULL or the same one as supplied as \"newWidgetParent\""); /** use cached part for this parent if possible */ if (internalWidgetParent == newWidgetParent && (newServicePtr == KService::Ptr() || internalServicePtr == newServicePtr)) { Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "Part is NULL"); return part; } else if (part != nullptr) { KParts::ReadWritePart *rwp = qobject_cast(part); if (rwp != nullptr) rwp->closeUrl(true); part->deleteLater(); part = nullptr; } /// reset to invalid values in case something goes wrong internalServicePtr = KService::Ptr(); internalWidgetParent = nullptr; if (!newServicePtr) { /// no valid KService has been passed /// try to find a read-write part to open file newServicePtr = p->defaultService(); } if (!newServicePtr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); qCCritical(LOG_KBIBTEX_PROGRAM) << "Cannot find service to handle mimetype " << mimeType << endl; return nullptr; } QString errorString; - part = newServicePtr->createInstance(newWidgetParent, (QObject *)newWidgetParent, QVariantList(), &errorString); + part = newServicePtr->createInstance(newWidgetParent, qobject_cast(newWidgetParent), QVariantList(), &errorString); if (part == nullptr) { qCDebug(LOG_KBIBTEX_PROGRAM) << "PATH=" << getenv("PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "LD_LIBRARY_PATH=" << getenv("LD_LIBRARY_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "XDG_DATA_DIRS=" << getenv("XDG_DATA_DIRS"); qCDebug(LOG_KBIBTEX_PROGRAM) << "QT_PLUGIN_PATH=" << getenv("QT_PLUGIN_PATH"); qCDebug(LOG_KBIBTEX_PROGRAM) << "KDEDIRS=" << getenv("KDEDIRS"); qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not instantiate read-write part for service" << newServicePtr->name() << "(mimeType=" << mimeType << ", library=" << newServicePtr->library() << ", error msg=" << errorString << ")"; /// creating a read-write part failed, so maybe it is read-only (like Okular's PDF viewer)? - part = newServicePtr->createInstance(newWidgetParent, (QObject *)newWidgetParent, QVariantList(), &errorString); + part = newServicePtr->createInstance(newWidgetParent, qobject_cast(newWidgetParent), QVariantList(), &errorString); } if (part == nullptr) { /// still cannot create part, must be error qCCritical(LOG_KBIBTEX_PROGRAM) << "Could not instantiate part for service" << newServicePtr->name() << "(mimeType=" << mimeType << ", library=" << newServicePtr->library() << ", error msg=" << errorString << ")"; return nullptr; } if (url.isValid()) { /// open URL in part part->openUrl(url); /// update document list widget accordingly p->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); p->addFlags(OpenFileInfo::StatusFlag::HasName); } else { /// initialize part with empty document part->openUrl(QUrl()); } p->addFlags(OpenFileInfo::StatusFlag::Open); internalServicePtr = newServicePtr; internalWidgetParent = newWidgetParent; Q_ASSERT_X(part != nullptr, "KParts::ReadOnlyPart *OpenFileInfo::OpenFileInfoPrivate::createPart(QWidget *newWidgetParent, KService::Ptr newServicePtr = KService::Ptr())", "Creation of part failed, is NULL"); /// test should not be necessary, but just to be save ... return part; } int counter() { if (!url.isValid() && m_counter < 0) m_counter = ++globalCounter; else if (url.isValid()) qCWarning(LOG_KBIBTEX_PROGRAM) << "This function should not be called if URL is valid"; return m_counter; } }; int OpenFileInfo::OpenFileInfoPrivate::globalCounter = 0; const QString OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat = QStringLiteral("yyyy-MM-dd-hh-mm-ss-zzz"); const QString OpenFileInfo::OpenFileInfoPrivate::keyLastAccess = QStringLiteral("LastAccess"); const QString OpenFileInfo::OpenFileInfoPrivate::keyURL = QStringLiteral("URL"); OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QUrl &url) : d(new OpenFileInfoPrivate(openFileInfoManager, url, FileInfo::mimeTypeForUrl(url).name(), this)) { /// nothing } OpenFileInfo::OpenFileInfo(OpenFileInfoManager *openFileInfoManager, const QString &mimeType) : d(new OpenFileInfoPrivate(openFileInfoManager, QUrl(), mimeType, this)) { /// nothing } OpenFileInfo::~OpenFileInfo() { delete d; } void OpenFileInfo::setUrl(const QUrl &url) { Q_ASSERT_X(url.isValid(), "void OpenFileInfo::setUrl(const QUrl&)", "URL is not valid"); d->url = url; if (d->url.scheme().isEmpty()) qCWarning(LOG_KBIBTEX_PROGRAM) << "No scheme specified for URL" << d->url.toDisplayString(); d->mimeType = FileInfo::mimeTypeForUrl(url).name(); addFlags(OpenFileInfo::StatusFlag::HasName); } QUrl OpenFileInfo::url() const { return d->url; } bool OpenFileInfo::isModified() const { KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); if (rwPart == nullptr) return false; else return rwPart->isModified(); } bool OpenFileInfo::save() { KParts::ReadWritePart *rwPart = qobject_cast< KParts::ReadWritePart *>(d->part); if (rwPart == nullptr) return true; else return rwPart->save(); } bool OpenFileInfo::close() { if (d->part == nullptr) { /// if there is no part, closing always "succeeds" return true; } KParts::ReadWritePart *rwp = qobject_cast(d->part); if (rwp == nullptr || rwp->closeUrl(true)) { d->part->deleteLater(); d->part = nullptr; d->internalWidgetParent = nullptr; return true; } return false; } QString OpenFileInfo::mimeType() const { return d->mimeType; } QString OpenFileInfo::shortCaption() const { if (d->url.isValid()) return d->url.fileName(); else return i18n("Unnamed-%1", d->counter()); } QString OpenFileInfo::fullCaption() const { if (d->url.isValid()) return d->url.url(QUrl::PreferLocalFile); else return shortCaption(); } /// Clazy warns: "Missing reference on non-trivial type" for argument 'servicePtr', /// but type 'KService::Ptr' is actually a pointer (QExplicitlySharedDataPointer). KParts::ReadOnlyPart *OpenFileInfo::part(QWidget *parent, KService::Ptr servicePtr) { return d->createPart(parent, servicePtr); } OpenFileInfo::StatusFlags OpenFileInfo::flags() const { return d->flags; } void OpenFileInfo::setFlags(StatusFlags statusFlags) { /// disallow files without name or valid url to become favorites if (!d->url.isValid() || !d->flags.testFlag(StatusFlag::HasName)) statusFlags &= ~static_cast(StatusFlag::Favorite); /// files that got opened are by definition recently used files if (!d->url.isValid() && d->flags.testFlag(StatusFlag::Open)) statusFlags &= StatusFlag::RecentlyUsed; bool hasChanged = d->flags != statusFlags; d->flags = statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } void OpenFileInfo::addFlags(StatusFlags statusFlags) { /// disallow files without name or valid url to become favorites if (!d->url.isValid() || !d->flags.testFlag(StatusFlag::HasName)) statusFlags &= ~static_cast(StatusFlag::Favorite); bool hasChanged = (~d->flags & statusFlags) > 0; d->flags |= statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } void OpenFileInfo::removeFlags(StatusFlags statusFlags) { bool hasChanged = (d->flags & statusFlags) > 0; d->flags &= ~statusFlags; if (hasChanged) emit flagsChanged(statusFlags); } QDateTime OpenFileInfo::lastAccess() const { return d->lastAccessDateTime; } void OpenFileInfo::setLastAccess(const QDateTime &dateTime) { d->lastAccessDateTime = dateTime; emit flagsChanged(OpenFileInfo::StatusFlag::RecentlyUsed); } KService::List OpenFileInfo::listOfServices() { const QString mt = mimeType(); /// First, try to locate KPart that can both read and write the queried MIME type KService::List result = KMimeTypeTrader::self()->query(mt, QStringLiteral("KParts/ReadWritePart")); if (result.isEmpty()) { /// Second, if no 'writing' KPart was found, try to locate KPart that can at least read the queried MIME type result = KMimeTypeTrader::self()->query(mt, QStringLiteral("KParts/ReadOnlyPart")); if (result.isEmpty()) { /// If not even a 'reading' KPart was found, something is off, so warn the user and stop here qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not find any KPart that reads or writes mimetype" << mt; return result; } } /// Always include KBibTeX's KPart in list of services: /// First, check if KBibTeX's KPart is already in list as returned by /// KMimeTypeTrader::self()->query(..) bool listIncludesKBibTeXPart = false; for (KService::List::ConstIterator it = result.constBegin(); it != result.constEnd(); ++it) { qCDebug(LOG_KBIBTEX_PROGRAM) << "Found library for" << mt << ":" << (*it)->library(); listIncludesKBibTeXPart |= (*it)->library() == QStringLiteral("kbibtexpart"); } /// Then, if KBibTeX's KPart is not in the list, try to located it by desktop name if (!listIncludesKBibTeXPart) { KService::Ptr kbibtexpartByDesktopName = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); if (kbibtexpartByDesktopName != nullptr) { result << kbibtexpartByDesktopName; qCDebug(LOG_KBIBTEX_PROGRAM) << "Adding library for" << mt << ":" << kbibtexpartByDesktopName->library(); } else { qCDebug(LOG_KBIBTEX_PROGRAM) << "Could not locate KBibTeX's KPart neither by MIME type search, nor by desktop name"; } } return result; } KService::Ptr OpenFileInfo::defaultService() { const QString mt = mimeType(); KService::Ptr result; if (mt == QStringLiteral("application/pdf") || mt == QStringLiteral("text/x-bibtex")) { /// If either a BibTeX file or a PDF file is to be opened, enforce using /// KBibTeX's part over anything else. /// KBibTeX has a FileImporterPDF which allows it to load .pdf file /// that got generated with KBibTeX and contain the original /// .bib file as an 'attachment'. /// This importer does not work with any other .pdf files!!! result = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); } if (result == nullptr) { /// First, try to locate KPart that can both read and write the queried MIME type result = KMimeTypeTrader::self()->preferredService(mt, QStringLiteral("KParts/ReadWritePart")); if (result == nullptr) { /// Second, if no 'writing' KPart was found, try to locate KPart that can at least read the queried MIME type result = KMimeTypeTrader::self()->preferredService(mt, QStringLiteral("KParts/ReadOnlyPart")); if (result == nullptr && mt == QStringLiteral("text/x-bibtex")) /// Third, if MIME type is for BibTeX files, try loading KBibTeX part via desktop name result = KService::serviceByDesktopName(QStringLiteral("kbibtexpart")); } } if (result != nullptr) qCDebug(LOG_KBIBTEX_PROGRAM) << "Using service" << result->name() << "(" << result->comment() << ") for mime type" << mt << "through library" << result->library(); else qCWarning(LOG_KBIBTEX_PROGRAM) << "Could not find service for mime type" << mt; return result; } KService::Ptr OpenFileInfo::currentService() { return d->internalServicePtr; } class OpenFileInfoManager::OpenFileInfoManagerPrivate { private: static const QString configGroupNameRecentlyUsed; static const QString configGroupNameFavorites; static const QString configGroupNameOpen; static const int maxNumRecentlyUsedFiles, maxNumFavoriteFiles, maxNumOpenFiles; public: OpenFileInfoManager *p; OpenFileInfoManager::OpenFileInfoList openFileInfoList; OpenFileInfo *currentFileInfo; OpenFileInfoManagerPrivate(OpenFileInfoManager *parent) : p(parent), currentFileInfo(nullptr) { /// nothing } ~OpenFileInfoManagerPrivate() { for (OpenFileInfoManager::OpenFileInfoList::Iterator it = openFileInfoList.begin(); it != openFileInfoList.end();) { OpenFileInfo *ofi = *it; delete ofi; it = openFileInfoList.erase(it); } } static bool byNameLessThan(const OpenFileInfo *left, const OpenFileInfo *right) { return left->shortCaption() < right->shortCaption(); } static bool byLRULessThan(const OpenFileInfo *left, const OpenFileInfo *right) { return left->lastAccess() > right->lastAccess(); /// reverse sorting! } void readConfig() { readConfig(OpenFileInfo::StatusFlag::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); readConfig(OpenFileInfo::StatusFlag::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); readConfig(OpenFileInfo::StatusFlag::Open, configGroupNameOpen, maxNumOpenFiles); } void writeConfig() { writeConfig(OpenFileInfo::StatusFlag::RecentlyUsed, configGroupNameRecentlyUsed, maxNumRecentlyUsedFiles); writeConfig(OpenFileInfo::StatusFlag::Favorite, configGroupNameFavorites, maxNumFavoriteFiles); writeConfig(OpenFileInfo::StatusFlag::Open, configGroupNameOpen, maxNumOpenFiles); } void readConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); bool isFirst = true; KConfigGroup cg(config, configGroupName); for (int i = 0; i < maxNumFiles; ++i) { QUrl fileUrl = QUrl(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), "")); if (!fileUrl.isValid()) break; if (fileUrl.scheme().isEmpty()) fileUrl.setScheme(QStringLiteral("file")); /// For local files, test if they exist; ignore local files that do not exist if (fileUrl.isLocalFile()) { if (!QFileInfo::exists(fileUrl.toLocalFile())) continue; } OpenFileInfo *ofi = p->contains(fileUrl); if (ofi == nullptr) { ofi = p->open(fileUrl); } ofi->addFlags(statusFlag); ofi->addFlags(OpenFileInfo::StatusFlag::HasName); ofi->setLastAccess(QDateTime::fromString(cg.readEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ""), OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); if (isFirst) { isFirst = false; if (statusFlag == OpenFileInfo::StatusFlag::Open) p->setCurrentFile(ofi); } } } void writeConfig(OpenFileInfo::StatusFlag statusFlag, const QString &configGroupName, int maxNumFiles) { KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("kbibtexrc")); KConfigGroup cg(config, configGroupName); OpenFileInfoManager::OpenFileInfoList list = p->filteredItems(statusFlag); int i = 0; for (OpenFileInfoManager::OpenFileInfoList::ConstIterator it = list.constBegin(); i < maxNumFiles && it != list.constEnd(); ++it, ++i) { OpenFileInfo *ofi = *it; cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i), ofi->url().url(QUrl::PreferLocalFile)); cg.writeEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i), ofi->lastAccess().toString(OpenFileInfo::OpenFileInfoPrivate::dateTimeFormat)); } for (; i < maxNumFiles; ++i) { cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyURL).arg(i)); cg.deleteEntry(QString(QStringLiteral("%1-%2")).arg(OpenFileInfo::OpenFileInfoPrivate::keyLastAccess).arg(i)); } config->sync(); } }; const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameRecentlyUsed = QStringLiteral("DocumentList-RecentlyUsed"); const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameFavorites = QStringLiteral("DocumentList-Favorites"); const QString OpenFileInfoManager::OpenFileInfoManagerPrivate::configGroupNameOpen = QStringLiteral("DocumentList-Open"); const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumFavoriteFiles = 256; const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumRecentlyUsedFiles = 8; const int OpenFileInfoManager::OpenFileInfoManagerPrivate::maxNumOpenFiles = 16; OpenFileInfoManager::OpenFileInfoManager(QObject *parent) : QObject(parent), d(new OpenFileInfoManagerPrivate(this)) { QTimer::singleShot(300, this, [this]() { d->readConfig(); }); } OpenFileInfoManager &OpenFileInfoManager::instance() { /// Allocate this singleton on heap not stack like most other singletons /// Supposedly, QCoreApplication will clean this singleton at application's end static OpenFileInfoManager *singleton = new OpenFileInfoManager(QCoreApplication::instance()); return *singleton; } OpenFileInfoManager::~OpenFileInfoManager() { delete d; } OpenFileInfo *OpenFileInfoManager::createNew(const QString &mimeType) { OpenFileInfo *result = new OpenFileInfo(this, mimeType); connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); d->openFileInfoList << result; result->setLastAccess(); return result; } OpenFileInfo *OpenFileInfoManager::open(const QUrl &url) { Q_ASSERT_X(url.isValid(), "OpenFileInfo *OpenFileInfoManager::open(const QUrl&)", "URL is not valid"); OpenFileInfo *result = contains(url); if (result == nullptr) { /// file not yet open result = new OpenFileInfo(this, url); connect(result, &OpenFileInfo::flagsChanged, this, &OpenFileInfoManager::flagsChanged); d->openFileInfoList << result; } /// else: file was already open, re-use and return existing OpenFileInfo pointer result->setLastAccess(); return result; } OpenFileInfo *OpenFileInfoManager::contains(const QUrl &url) const { if (!url.isValid()) return nullptr; /// can only be unnamed file for (auto *ofi : const_cast(d->openFileInfoList)) { if (ofi->url() == url) return ofi; } return nullptr; } bool OpenFileInfoManager::changeUrl(OpenFileInfo *openFileInfo, const QUrl &url) { OpenFileInfo *previouslyContained = contains(url); /// check if old url differs from new url and old url is valid if (previouslyContained != nullptr && previouslyContained->flags().testFlag(OpenFileInfo::StatusFlag::Open) && previouslyContained != openFileInfo) { qCWarning(LOG_KBIBTEX_PROGRAM) << "Open file with same URL already exists, forcefully closing it" << endl; close(previouslyContained); } QUrl oldUrl = openFileInfo->url(); openFileInfo->setUrl(url); if (url != oldUrl && oldUrl.isValid()) { /// current document was most probabily renamed (e.g. due to "Save As") /// add old URL to recently used files, but exclude the open files list OpenFileInfo *ofi = open(oldUrl); // krazy:exclude=syscalls OpenFileInfo::StatusFlags statusFlags = (openFileInfo->flags() & ~static_cast(OpenFileInfo::StatusFlag::Open)) | OpenFileInfo::StatusFlag::RecentlyUsed; ofi->setFlags(statusFlags); } if (previouslyContained != nullptr) { /// keep Favorite flag if set in file that have previously same URL if (previouslyContained->flags().testFlag(OpenFileInfo::StatusFlag::Favorite)) openFileInfo->setFlags(openFileInfo->flags() | OpenFileInfo::StatusFlag::Favorite); /// remove the old entry with the same url has it will be replaced by the new one d->openFileInfoList.remove(d->openFileInfoList.indexOf(previouslyContained)); previouslyContained->deleteLater(); OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::StatusFlag::Open; statusFlags |= OpenFileInfo::StatusFlag::RecentlyUsed; statusFlags |= OpenFileInfo::StatusFlag::Favorite; emit flagsChanged(statusFlags); } if (openFileInfo == d->currentFileInfo) emit currentChanged(openFileInfo, KService::Ptr()); emit flagsChanged(openFileInfo->flags()); return true; } bool OpenFileInfoManager::close(OpenFileInfo *openFileInfo) { if (openFileInfo == nullptr) { qCWarning(LOG_KBIBTEX_PROGRAM) << "void OpenFileInfoManager::close(OpenFileInfo *openFileInfo): Cannot close openFileInfo which is NULL"; return false; } bool isClosing = false; openFileInfo->setLastAccess(); /// remove flag "open" from file to be closed and determine which file to show instead OpenFileInfo *nextCurrent = (d->currentFileInfo == openFileInfo) ? nullptr : d->currentFileInfo; for (OpenFileInfo *ofi : const_cast(d->openFileInfoList)) { if (!isClosing && ofi == openFileInfo && openFileInfo->close()) { isClosing = true; /// Mark file as closed (i.e. not open) openFileInfo->removeFlags(OpenFileInfo::StatusFlag::Open); /// If file has a filename, remember as recently used if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::HasName)) openFileInfo->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); } else if (nextCurrent == nullptr && ofi->flags().testFlag(OpenFileInfo::StatusFlag::Open)) nextCurrent = ofi; } /// If the current document is to be closed, /// switch over to the next available one if (isClosing) setCurrentFile(nextCurrent); return isClosing; } bool OpenFileInfoManager::queryCloseAll() { /// Assume that all closing operations succeed bool isClosing = true; /// For keeping track of files that get closed here OpenFileInfoList restoreLaterList; /// For each file known ... for (OpenFileInfo *openFileInfo : const_cast(d->openFileInfoList)) { /// Check only open file (ignore recently used, favorites, ...) if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::Open)) { if (openFileInfo->close()) { /// If file could be closed without user canceling the operation ... /// Mark file as closed (i.e. not open) openFileInfo->removeFlags(OpenFileInfo::StatusFlag::Open); /// If file has a filename, remember as recently used if (openFileInfo->flags().testFlag(OpenFileInfo::StatusFlag::HasName)) openFileInfo->addFlags(OpenFileInfo::StatusFlag::RecentlyUsed); /// Remember file as to be marked as open later restoreLaterList.append(openFileInfo); } else { /// User chose to cancel closing operation, /// stop everything here isClosing = false; break; } } } if (isClosing) { /// Closing operation was not cancelled, therefore mark /// all files that were open before as open now. /// This makes the files to be reopened when KBibTeX is /// restarted again (assuming that this function was /// called when KBibTeX is exiting). for (OpenFileInfo *openFileInfo : const_cast(restoreLaterList)) { openFileInfo->addFlags(OpenFileInfo::StatusFlag::Open); } d->writeConfig(); } return isClosing; } OpenFileInfo *OpenFileInfoManager::currentFile() const { return d->currentFileInfo; } /// Clazy warns: "Missing reference on non-trivial type" for argument 'servicePtr', /// but type 'KService::Ptr' is actually a pointer (QExplicitlySharedDataPointer). void OpenFileInfoManager::setCurrentFile(OpenFileInfo *openFileInfo, KService::Ptr servicePtr) { bool hasChanged = d->currentFileInfo != openFileInfo; OpenFileInfo *previous = d->currentFileInfo; d->currentFileInfo = openFileInfo; if (d->currentFileInfo != nullptr) { d->currentFileInfo->addFlags(OpenFileInfo::StatusFlag::Open); d->currentFileInfo->setLastAccess(); } if (hasChanged) { if (previous != nullptr) previous->setLastAccess(); emit currentChanged(openFileInfo, servicePtr); } else if (openFileInfo != nullptr && servicePtr != openFileInfo->currentService()) emit currentChanged(openFileInfo, servicePtr); } OpenFileInfoManager::OpenFileInfoList OpenFileInfoManager::filteredItems(OpenFileInfo::StatusFlag required, OpenFileInfo::StatusFlags forbidden) { OpenFileInfoList result; for (OpenFileInfoList::Iterator it = d->openFileInfoList.begin(); it != d->openFileInfoList.end(); ++it) { OpenFileInfo *ofi = *it; if (ofi->flags().testFlag(required) && (ofi->flags() & forbidden) == 0) result << ofi; } if (required == OpenFileInfo::StatusFlag::RecentlyUsed) std::sort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byLRULessThan); else if (required == OpenFileInfo::StatusFlag::Favorite || required == OpenFileInfo::StatusFlag::Open) std::sort(result.begin(), result.end(), OpenFileInfoManagerPrivate::byNameLessThan); return result; } void OpenFileInfoManager::deferredListsChanged() { OpenFileInfo::StatusFlags statusFlags = OpenFileInfo::StatusFlag::Open; statusFlags |= OpenFileInfo::StatusFlag::RecentlyUsed; statusFlags |= OpenFileInfo::StatusFlag::Favorite; emit flagsChanged(statusFlags); }