diff --git a/src/kioslaves/tags/kio_tags.h b/src/kioslaves/tags/kio_tags.h --- a/src/kioslaves/tags/kio_tags.h +++ b/src/kioslaves/tags/kio_tags.h @@ -23,8 +23,11 @@ #ifndef BALOO_KIO_TAGS_H_ #define BALOO_KIO_TAGS_H_ +#include #include +#include "query.h" + namespace Baloo { @@ -35,6 +38,13 @@ TagsProtocol(const QByteArray& pool_socket, const QByteArray& app_socket); ~TagsProtocol() Q_DECL_OVERRIDE; + enum UrlType { + InvalidUrl, + FileUrl, + TagUrl + }; + Q_ENUM(UrlType) + /** * List all files and folders tagged with the corresponding tag, along with * additional tags that can be used to filter the results @@ -46,11 +56,6 @@ */ void get(const QUrl& url) Q_DECL_OVERRIDE; - /** - * Not supported. - */ - void put(const QUrl& url, int permissions, KIO::JobFlags flags) Q_DECL_OVERRIDE; - /** * Files and folders can be copied to the virtual folders resulting * is assignment of the corresponding tag. @@ -75,6 +80,11 @@ */ void mimetype(const QUrl& url) Q_DECL_OVERRIDE; + /** + * Virtual folders will be created. + */ + void mkdir(const QUrl& url, int permissions) Q_DECL_OVERRIDE; + /** * Files will be forwarded. * Tags will be created as virtual folders. @@ -84,18 +94,23 @@ bool rewriteUrl(const QUrl& url, QUrl& newURL) Q_DECL_OVERRIDE; private: - enum ParseResult { - RootUrl, - TagUrl, - FileUrl, - InvalidUrl + enum ParseFlags { + ChopLastSection, + LazyValidation }; - ParseResult parseUrl(const QUrl& url, QString& tag, QString& fileUrl, bool ignoreErrors = false); - bool splitUrl(const QUrl& url, QList& tags, QString& filename); + struct ParseResult { + UrlType urlType = InvalidUrl; + QString decodedUrl; + QString tag; + QUrl fileUrl; + KFileMetaData::UserMetaData metaData = KFileMetaData::UserMetaData(QString()); + Query query; + KIO::UDSEntryList pathUDSResults; + }; - QString decodeFileUrl(const QString& urlString); - QString encodeFileUrl(const QString& url); + ParseResult parseUrl(const QUrl& url, const QList &flags = QList()); + QStringList m_unassignedTags; }; } diff --git a/src/kioslaves/tags/kio_tags.cpp b/src/kioslaves/tags/kio_tags.cpp --- a/src/kioslaves/tags/kio_tags.cpp +++ b/src/kioslaves/tags/kio_tags.cpp @@ -1,6 +1,7 @@ /* * This file is part of the KDE Baloo Project * Copyright (C) 2012-2014 Vishesh Handa + * Copyright (C) 2017 James D. Smith * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -24,10 +25,12 @@ #include #include +#include #include #include -#include +#include +#include #include #include @@ -39,6 +42,8 @@ #include "query.h" #include "term.h" +Q_LOGGING_CATEGORY(KIO_TAGS, "kf5.kio.kio_tags") + using namespace Baloo; TagsProtocol::TagsProtocol(const QByteArray& pool_socket, const QByteArray& app_socket) @@ -50,388 +55,417 @@ { } - -namespace -{ -KIO::UDSEntry createUDSEntryForTag(const QString& tag) -{ - KIO::UDSEntry uds; - uds.insert(KIO::UDSEntry::UDS_NAME, tag); - uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, tag); - uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); - uds.insert(KIO::UDSEntry::UDS_DISPLAY_TYPE, i18n("Tag")); - uds.insert(KIO::UDSEntry::UDS_ACCESS, 0700); - uds.insert(KIO::UDSEntry::UDS_USER, KUser().loginName()); - uds.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("tag")); - - return uds; -} -} - void TagsProtocol::listDir(const QUrl& url) { - qDebug() << url; - - QString tag; - QString fileUrl; - QStringList paths; - - ParseResult result = parseUrl(url, tag, fileUrl); - - TagListJob* job = new TagListJob(); - job->exec(); - - switch (result) { - case InvalidUrl: - return; - - case RootUrl: { - qDebug() << "Root Url"; - - for (QString resultTag : job->tags()) { - if (resultTag.contains(QLatin1Char('/'))) { - resultTag = resultTag.section(QLatin1Char('/'), 0, 0, QString::SectionSkipEmpty); - } - if (paths.contains(resultTag, Qt::CaseInsensitive)) { - continue; - } else { - paths.insert(0, resultTag); - } - listEntry(createUDSEntryForTag(resultTag)); - } - - finished(); - return; + ParseResult result = parseUrl(url); + + switch(result.urlType) { + case InvalidUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "list() invalid url"; + error(KIO::ERR_CANNOT_ENTER_DIRECTORY, result.decodedUrl); + return; + case FileUrl: + ForwardingSlaveBase::listDir(result.fileUrl); + return; + case TagUrl: + listEntries(result.pathUDSResults); } - case TagUrl: { - for (QString resultTag : job->tags()) { - if (resultTag.startsWith(tag, Qt::CaseInsensitive) && resultTag.contains(QLatin1Char('/'))) { - resultTag.remove(0, (tag.size() + 1)); - resultTag = resultTag.section(QLatin1Char('/'), 0, 0, QString::SectionSkipEmpty); - if (paths.contains(resultTag, Qt::CaseInsensitive)) { - continue; - } else { - paths.insert(0, resultTag); - } - listEntry(createUDSEntryForTag(resultTag)); - } - } - - Query q; - q.setSortingOption(Query::SortNone); - q.setSearchString(QStringLiteral("tag=\"%1\"").arg(tag)); + finished(); +} - ResultIterator it = q.exec(); - while (it.next()) { - const QUrl url = QUrl::fromLocalFile(it.filePath()); - const QString fileUrl = url.toLocalFile(); - - // Somehow stat the file - KIO::UDSEntry uds; - if (KIO::StatJob* job = KIO::stat(url, KIO::HideProgressInfo)) { - // we do not want to wait for the event loop to delete the job - QScopedPointer sp(job); - job->setAutoDelete(false); - if (job->exec()) { - uds = job->statResult(); - } else { - continue; +void TagsProtocol::stat(const QUrl& url) +{ + ParseResult result = parseUrl(url); + + switch(result.urlType) { + case InvalidUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "stat() invalid url"; + error(KIO::ERR_DOES_NOT_EXIST, result.decodedUrl); + return; + case FileUrl: + ForwardingSlaveBase::stat(result.fileUrl); + return; + case TagUrl: + for (const KIO::UDSEntry& entry : result.pathUDSResults) { + if (entry.stringValue(KIO::UDSEntry::UDS_EXTRA) == result.tag) { + statEntry(entry); } } - - uds.insert(KIO::UDSEntry::UDS_NAME, encodeFileUrl(fileUrl)); - uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, url.fileName()); - //uds.insert(KIO::UDSEntry::UDS_URL, url); - uds.insert(KIO::UDSEntry::UDS_TARGET_URL, url.url()); - uds.insert(KIO::UDSEntry::UDS_LOCAL_PATH, fileUrl); - - listEntry(uds); - } - - finished(); - return; } - case FileUrl: - qDebug() << "File URL : " << fileUrl; - ForwardingSlaveBase::listDir(QUrl::fromLocalFile(fileUrl)); - return; - } + finished(); } -void TagsProtocol::stat(const QUrl& url) +void TagsProtocol::copy(const QUrl& src, const QUrl& dest, int permissions, KIO::JobFlags flags) { - qDebug() << url; + Q_UNUSED(permissions); + Q_UNUSED(flags); - QString tag; - QString fileUrl; + ParseResult srcResult = parseUrl(src); + ParseResult dstResult = parseUrl(dest, QList() << ChopLastSection << LazyValidation); - ParseResult result = parseUrl(url, tag, fileUrl); - switch (result) { - case InvalidUrl: + if (srcResult.urlType == InvalidUrl) { + qCWarning(KIO_TAGS) << srcResult.decodedUrl << "copy() invalid src url"; + error(KIO::ERR_DOES_NOT_EXIST, srcResult.decodedUrl); return; - - case RootUrl: { - KIO::UDSEntry uds; - uds.insert(KIO::UDSEntry::UDS_ACCESS, 0700); - uds.insert(KIO::UDSEntry::UDS_USER, KUser().loginName()); - uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); - uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); - - uds.insert(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES, QStringLiteral("tag")); - uds.insert(KIO::UDSEntry::UDS_DISPLAY_TYPE, i18n("Tag")); - - uds.insert(KIO::UDSEntry::UDS_NAME, QStringLiteral(".")); - uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("All Tags")); - - statEntry(uds); - finished(); + } else if (dstResult.urlType == InvalidUrl) { + qCWarning(KIO_TAGS) << dstResult.decodedUrl << "copy() invalid dest url"; + error(KIO::ERR_DOES_NOT_EXIST, dstResult.decodedUrl); return; } - case TagUrl: { - statEntry(createUDSEntryForTag(tag)); - finished(); - return; + auto rewriteTags = [] (KFileMetaData::UserMetaData& md, const QString& newTag) { + qCDebug(KIO_TAGS) << md.filePath() << "adding tag" << newTag; + QStringList tags = md.tags(); + tags.append(newTag); + md.setTags(tags); + }; + + if (srcResult.metaData.tags().contains(dstResult.tag)) { + qCWarning(KIO_TAGS) << srcResult.fileUrl.toLocalFile() << "file already has tag" << dstResult.tag; + infoMessage(i18n("File %1 already has tag %2", srcResult.fileUrl.toLocalFile(), dstResult.tag)); + } else if (dstResult.urlType == TagUrl) { + rewriteTags(srcResult.metaData, dstResult.tag); } - case FileUrl: - ForwardingSlaveBase::get(QUrl::fromLocalFile(fileUrl)); - return; + finished(); +} + +void TagsProtocol::get(const QUrl& url) +{ + ParseResult result = parseUrl(url); + + switch(result.urlType) { + case InvalidUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "get() invalid url"; + error(KIO::ERR_DOES_NOT_EXIST, result.decodedUrl); + return; + case FileUrl: + ForwardingSlaveBase::get(result.fileUrl); + return; + case TagUrl: + error(KIO::ERR_UNSUPPORTED_ACTION, result.decodedUrl); + return; } } -void TagsProtocol::copy(const QUrl& src, const QUrl& dest, int, KIO::JobFlags) +void TagsProtocol::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) { - qDebug() << src << dest; + Q_UNUSED(flags); - if (src.scheme() != QLatin1String("file")) { - error(KIO::ERR_UNSUPPORTED_ACTION, src.toString()); - return; - } + ParseResult srcResult = parseUrl(src); + ParseResult dstResult; - QString tag; - QString fileUrl; + if (srcResult.urlType == FileUrl) { + dstResult = parseUrl(dest, QList() << ChopLastSection); + } else if (srcResult.urlType == TagUrl) { + dstResult = parseUrl(dest, QList() << LazyValidation); + } - ParseResult result = parseUrl(dest, tag, fileUrl); - switch (result) { - case InvalidUrl: + if (srcResult.urlType == InvalidUrl) { + qCWarning(KIO_TAGS) << srcResult.decodedUrl << "rename() invalid src url"; + error(KIO::ERR_DOES_NOT_EXIST, srcResult.decodedUrl); return; - - case RootUrl: - case TagUrl: - error(KIO::ERR_UNSUPPORTED_ACTION, src.toString()); + } else if (dstResult.urlType == InvalidUrl) { + qCWarning(KIO_TAGS) << dstResult.decodedUrl << "rename() invalid dest url"; + error(KIO::ERR_DOES_NOT_EXIST, dstResult.decodedUrl); return; + } - case FileUrl: - /* - * FIXME: Do we really need to support copy operations? - Baloo::FileFetchJob* job = new Baloo::FileFetchJob(fileUrl); - job->exec(); - Baloo::File file = job->file(); + auto rewriteTags = [] (KFileMetaData::UserMetaData& md, const QString& oldTag, const QString& newTag) { + qCDebug(KIO_TAGS) << md.filePath() << "swapping tag" << oldTag << "with" << newTag; + QStringList tags = md.tags(); + tags.removeAll(oldTag); + tags.append(newTag); + md.setTags(tags); + }; + + if (srcResult.metaData.tags().contains(dstResult.tag)) { + qCWarning(KIO_TAGS) << srcResult.fileUrl.toLocalFile() << "file already has tag" << dstResult.tag; + infoMessage(i18n("File %1 already has tag %2", srcResult.fileUrl.toLocalFile(), dstResult.tag)); + } else if (srcResult.urlType == FileUrl && src.isLocalFile()) { + rewriteTags(srcResult.metaData, srcResult.tag, dstResult.tag); + } else if (dstResult.urlType == TagUrl) { + ResultIterator it = srcResult.query.exec(); + while (it.next()) { + KFileMetaData::UserMetaData md(it.filePath()); + if (it.filePath() == srcResult.fileUrl.toLocalFile()) { + rewriteTags(md, srcResult.tag, dstResult.tag); + } else if (srcResult.fileUrl.isEmpty()) { + for (const QString& tag : md.tags()) { + if (tag == srcResult.tag || (tag.startsWith(srcResult.tag + QLatin1Char('/')))) { + QString newTag = tag; + newTag.replace(srcResult.tag, dstResult.tag, Qt::CaseInsensitive); + rewriteTags(md, tag, newTag); + } + } + } + } + } - file.addTag(tag); - Baloo::FileModifyJob* mjob = new Baloo::FileModifyJob(file); - mjob->exec(); - */ + finished(); +} - finished(); - return; +void TagsProtocol::del(const QUrl& url, bool isfile) +{ + Q_UNUSED(isfile); + + ParseResult result = parseUrl(url); + + auto rewriteTags = [] (KFileMetaData::UserMetaData& md, const QString& tag) { + qCDebug(KIO_TAGS) << md.filePath() << "removing tag" << tag; + QStringList tags = md.tags(); + tags.removeAll(tag); + md.setTags(tags); + }; + + switch(result.urlType) { + case InvalidUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "del() invalid url"; + error(KIO::ERR_DOES_NOT_EXIST, result.decodedUrl); + return; + case FileUrl: + case TagUrl: + ResultIterator it = result.query.exec(); + while (it.next()) { + KFileMetaData::UserMetaData md(it.filePath()); + if (it.filePath() == result.fileUrl.toLocalFile()) { + rewriteTags(md, result.tag); + } else if (result.fileUrl.isEmpty()) { + for (const QString &tag : md.tags()) { + if ((tag == result.tag) || (tag.startsWith(QString(result.tag + QLatin1Char('/')), Qt::CaseInsensitive))) { + rewriteTags(md, tag); + } + } + } + } } -} + finished(); +} -void TagsProtocol::get(const QUrl& url) +void TagsProtocol::mimetype(const QUrl& url) { - qDebug() << url; + ParseResult result = parseUrl(url); + + switch(result.urlType) { + case InvalidUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "mimetype() invalid url"; + error(KIO::ERR_DOES_NOT_EXIST, result.decodedUrl); + return; + case FileUrl: + ForwardingSlaveBase::mimetype(result.fileUrl); + return; + case TagUrl: + mimeType(QStringLiteral("inode/directory")); + } - QString tag; - QString fileUrl; + finished(); +} - ParseResult result = parseUrl(url, tag, fileUrl); - switch (result) { - case InvalidUrl: - return; +void TagsProtocol::mkdir(const QUrl& url, int permissions) +{ + Q_UNUSED(permissions); - case RootUrl: - case TagUrl: - error(KIO::ERR_UNSUPPORTED_ACTION, url.toString()); - return; + ParseResult result = parseUrl(url, QList() << LazyValidation); - case FileUrl: - ForwardingSlaveBase::get(QUrl::fromLocalFile(fileUrl)); - return; + switch(result.urlType) { + case InvalidUrl: + case FileUrl: + qCWarning(KIO_TAGS) << result.decodedUrl << "mkdir() invalid url"; + error(KIO::ERR_DOES_NOT_EXIST, result.decodedUrl); + return; + case TagUrl: + m_unassignedTags << result.tag; } -} + finished(); +} -void TagsProtocol::put(const QUrl& url, int permissions, KIO::JobFlags flags) +bool TagsProtocol::rewriteUrl(const QUrl& url, QUrl& newURL) { - Q_UNUSED(permissions); - Q_UNUSED(flags); + Q_UNUSED(url); + Q_UNUSED(newURL); - error(KIO::ERR_UNSUPPORTED_ACTION, url.toString()); - return; + return false; } - -void TagsProtocol::rename(const QUrl& src, const QUrl& dest, KIO::JobFlags flags) +TagsProtocol::ParseResult TagsProtocol::parseUrl(const QUrl& url, const QList &flags) { - qDebug() << src << dest; - if (src.isLocalFile()) { - error(KIO::ERR_CANNOT_DELETE_ORIGINAL, src.toString()); - return; - } + TagsProtocol::ParseResult result; + result.decodedUrl = QUrl::fromPercentEncoding(url.toString().toUtf8()); - QString srcTag; - QString fileUrl; + auto createUDSEntryForTag = [] (const QString& tagSection, const QString& tag) { + KIO::UDSEntry uds; + uds.insert(KIO::UDSEntry::UDS_NAME, tagSection); + uds.insert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + uds.insert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); + uds.insert(KIO::UDSEntry::UDS_ACCESS, 0700); + uds.insert(KIO::UDSEntry::UDS_USER, KUser().loginName()); + uds.insert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("tag")); + uds.insert(KIO::UDSEntry::UDS_EXTRA, tag); + + QString displayType; + if (tagSection == tag) { + displayType = i18n("Tag"); + } else if (!tag.isEmpty()) { + displayType = i18n("Tag Fragment"); + } else { + displayType = i18n("All Tags"); + } - ParseResult srcResult = parseUrl(src, srcTag, fileUrl); - switch (srcResult) { - case InvalidUrl: - return; + uds.insert(KIO::UDSEntry::UDS_DISPLAY_TYPE, displayType); - case RootUrl: - case TagUrl: - error(KIO::ERR_UNSUPPORTED_ACTION, src.toString()); - return; + if (tagSection == QLatin1Char('.')) { + QString displayName = i18n("Tags"); + if (!tag.isEmpty()) { + displayName = tag.section(QLatin1Char('/'), -1); + } - case FileUrl: { - // Yes, this is weird, but it is required - // It is required cause the dest url is of the form tags:/tag/file_url_with_new_filename - // So we extract the new fileUrl from the 'src', and apply the new file to the dest - QString destUrl = fileUrl; - int lastIndex = destUrl.lastIndexOf(QDir::separator()); - destUrl.resize(lastIndex + 1); - destUrl.append(dest.fileName()); + uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayName); + } - ForwardingSlaveBase::rename(QUrl(fileUrl), QUrl(destUrl), flags); - return; - } - } -} + if (tagSection == QStringLiteral("..")) { + QString displayName = i18n("Tags"); + if (!tag.isEmpty()) { + displayName = tag.section(QLatin1Char('/'), -3); + } -void TagsProtocol::del(const QUrl& url, bool isfile) -{ - Q_UNUSED(url) - Q_UNUSED(isfile); - /* + uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayName); + } - Tag tag; - QString fileUrl; + return uds; + }; - ParseResult result = parseUrl(url, tag, fileUrl); - switch (result) { - case InvalidUrl: - return; + TagListJob* tagJob = new TagListJob(); + if (!tagJob->exec()) { + qCWarning(KIO_TAGS) << "tag fetch failed:" << tagJob->errorString(); + return result; + } - case RootUrl: - error(KIO::ERR_UNSUPPORTED_ACTION, url.toString()); - return; + if (url.isLocalFile()) { + result.urlType = FileUrl; + result.fileUrl = url; + result.metaData = KFileMetaData::UserMetaData(url.toLocalFile()); + qCDebug(KIO_TAGS) << result.decodedUrl << "url file path:" << result.fileUrl.toLocalFile(); + } else if (url.scheme() == QLatin1String("tags")) { + bool validTag = flags.contains(LazyValidation); + QString filePath; + + // Extract any local file path from the URL. + if (result.decodedUrl.contains(QStringLiteral("?/"))) { + filePath = QDir::separator() + result.decodedUrl.section(QStringLiteral("?/"), -1, QString::SectionSkipEmpty); + result.fileUrl = QUrl::fromLocalFile(filePath); + result.metaData = KFileMetaData::UserMetaData(filePath); + qCDebug(KIO_TAGS) << result.decodedUrl << "url file path:" << filePath; + } - case TagUrl: { - TagRemoveJob* job = new TagRemoveJob(tag); - job->exec(); + // Determine the tag from the URL. + result.tag = result.decodedUrl.section(QStringLiteral("?/"), 0, 0); + result.tag.remove(url.scheme() + QLatin1Char(':')); + result.tag = QDir::cleanPath(result.tag); + while (result.tag.startsWith(QLatin1Char('/'))) { + result.tag.remove(0, 1); + } - finished(); - return; - } + if (!filePath.isEmpty() || flags.contains(ChopLastSection)) { + result.tag = result.tag.section(QDir::separator(), 0, -2); + } + + validTag = validTag || result.tag.isEmpty(); - case FileUrl: { - qDebug() << "Removing file url : " << fileUrl; - // FIXME: FUCK!!!!!! - TagRelation rel(tag, ); - TagRelationRemoveJob* job = new TagRelationRemoveJob(T); - job->exec(); + if (!result.tag.isEmpty()) { + // Create a query to find files that may be in the operation's scope. + QString query = result.tag; + query.prepend("tag:"); + query.replace('/', " AND tag:"); + result.query.setSearchString(query); - if (job->error()) { - qWarning() << job->errorString(); - error(KIO::ERR_CANNOT_DELETE, job->errorString()); + qCDebug(KIO_TAGS) << result.decodedUrl << "url query:" << query; + + if (result.tag.contains(QLatin1Char('/'))) { + qCDebug(KIO_TAGS) << result.decodedUrl << "url is a tag fragment:" << result.tag; + } else { + qCDebug(KIO_TAGS) << result.decodedUrl << "url is a tag:" << result.tag; + } } else { - finished(); + qCDebug(KIO_TAGS) << result.decodedUrl << "url is the root tag url"; } - return; - } - }*/ -} - -void TagsProtocol::mimetype(const QUrl& url) -{ - qDebug() << url; + // Create the tag directory entries. + int index = result.tag.count(QLatin1Char('/')) + (result.tag.isEmpty() ? 0 : 1); + QStringList tagPaths; - QString tag; - QString fileUrl; + const QStringList tags = QStringList() << tagJob->tags() << m_unassignedTags; + for (const QString& tag : tags) { + if (result.tag.isEmpty() || (tag.startsWith(result.tag, Qt::CaseInsensitive))) { + QString tagSection = tag.section(QLatin1Char('/'), index, index, QString::SectionSkipEmpty); + if (!tagPaths.contains(tagSection, Qt::CaseInsensitive) && !tagSection.isEmpty()) { + result.pathUDSResults << createUDSEntryForTag(tagSection, tag); + tagPaths << tagSection; - ParseResult result = parseUrl(url, tag, fileUrl); - switch (result) { - case InvalidUrl: - return; + qCDebug(KIO_TAGS) << result.decodedUrl << "added path:" << tagSection; + } + } - case RootUrl: - case TagUrl: - mimeType(QStringLiteral("inode/directory")); - finished(); - return; + validTag = validTag || tag.startsWith(result.tag, Qt::CaseInsensitive); + } - case FileUrl: - ForwardingSlaveBase::mimetype(QUrl::fromLocalFile(fileUrl)); - return; + if (validTag && result.fileUrl.isEmpty()) { + result.urlType = TagUrl; + } else if (validTag && !result.fileUrl.isEmpty()) { + result.urlType = FileUrl; + } } -} -// The ForwardingSlaveBase functions are always called with a file:// url -// In this case we just set the newUrl = url -bool TagsProtocol::rewriteUrl(const QUrl& url, QUrl& newURL) -{ - if (url.scheme() != QLatin1String("file")) - return false; + qCDebug(KIO_TAGS) << result.decodedUrl << "url type:" << result.urlType; - newURL = url; - return true; -} + if (result.urlType == FileUrl) { + return result; + } else { + result.pathUDSResults << createUDSEntryForTag(QStringLiteral("."), result.tag); + } -QString TagsProtocol::decodeFileUrl(const QString& urlString) -{ - return QString::fromUtf8(QByteArray::fromPercentEncoding(urlString.toUtf8(), '_')); -} + // The root tag url has no file entries + if (result.tag.isEmpty()) { + return result; + } else { + result.pathUDSResults << createUDSEntryForTag(QStringLiteral(".."), result.tag); + } -QString TagsProtocol::encodeFileUrl(const QString& url) -{ - return QString::fromUtf8(url.toUtf8().toPercentEncoding(QByteArray(), QByteArray(), '_')); -} + // Query for any files associated with the tag. + Query q; + q.setSortingOption(Query::SortNone); + q.setSearchString(QStringLiteral("tag=\"%1\"").arg(result.tag)); + ResultIterator it = q.exec(); + while (it.next()) { + const QUrl& match = QUrl::fromLocalFile(it.filePath()); + // Somehow stat the file + KIO::UDSEntry uds; + KIO::StatJob* job = KIO::stat(match, KIO::HideProgressInfo); + // we do not want to wait for the event loop to delete the job + QScopedPointer sp(job); + job->setAutoDelete(false); + if (job->exec()) { + uds = job->statResult(); + + qCDebug(KIO_TAGS) << result.decodedUrl << "adding file:" << match.fileName(); + } else { + continue; + } -TagsProtocol::ParseResult TagsProtocol::parseUrl(const QUrl& url, QString& tag, QString& fileUrl, bool) -{ - // Isolate the path and sanitize it by removing any beginning and trailing slashes - QString path = url.path(); - while (path.startsWith(QLatin1Char('/'))) - path.remove(0, 1); - while (path.endsWith(QLatin1Char('/'))) - path.chop(1); - - // If the resulting string is empty, the result is the root url - if (path.isEmpty()) - return RootUrl; - - // If the path doesn't end with a trailing slash, the result is a tag - if (!url.path().endsWith(QLatin1Char('/'))) { - tag = path; - fileUrl.clear(); - - return TagUrl; - } else { - tag = path; - QString fileName = url.fileName(); - fileUrl = decodeFileUrl(fileName); + uds.insert(KIO::UDSEntry::UDS_NAME, match.fileName() + QStringLiteral("?") + it.filePath()); + uds.insert(KIO::UDSEntry::UDS_DISPLAY_NAME, match.fileName()); + uds.insert(KIO::UDSEntry::UDS_TARGET_URL, match.toString()); + uds.insert(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES, QStringLiteral("tag")); - return FileUrl; + result.pathUDSResults << uds; } -} + return result; +} extern "C" { diff --git a/src/kioslaves/tags/tags.protocol b/src/kioslaves/tags/tags.protocol --- a/src/kioslaves/tags/tags.protocol +++ b/src/kioslaves/tags/tags.protocol @@ -4,14 +4,14 @@ input=none output=filesystem reading=true -writing=false -deleting=false +writing=true +deleting=true linking=false -makedir=false -moving=false +makedir=true +moving=true copyFromFile=true -renameFromFile=false -deleteRecursive=false +renameFromFile=true +deleteRecursive=true listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link source=false Icon=tag