File indexing completed on 2024-04-14 05:34:11

0001 /*
0002     SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
0003     SPDX-FileCopyrightText: 2012 Sergei Stolyarov <sergei@regolit.com>
0004     SPDX-FileCopyrightText: 2010 Thomas Richard <thomas.richard@proan.be>
0005     SPDX-FileCopyrightText: 2009-2010 Peter Penz <peter.penz19@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "fileviewdropboxplugin.h"
0011 
0012 #include <KFileItem>
0013 #include <KFileItemListProperties>
0014 #include <KLocalizedString>
0015 #include <KActionCollection>
0016 #include <KPluginFactory>
0017 
0018 #include <QDir>
0019 #include <QPointer>
0020 #include <QLocalSocket>
0021 #include <QFileSystemWatcher>
0022 #include <QStringBuilder>
0023 
0024 K_PLUGIN_CLASS_WITH_JSON(FileViewDropboxPlugin, "fileviewdropboxplugin.json")
0025 
0026 class FileViewDropboxPlugin::Private
0027 {
0028 public:
0029     Private(FileViewDropboxPlugin* parent) :
0030         contextFilePaths(),
0031         controlSocketPath(),
0032         controlSocket(new QLocalSocket(parent)),
0033         databaseFileWatcher(new QFileSystemWatcher(parent)),
0034         contextActions(new KActionCollection(parent))
0035     {
0036     }
0037 
0038     QStringList contextFilePaths;
0039     QString controlSocketPath;
0040     QPointer<QLocalSocket> controlSocket;
0041     QPointer<QLocalSocket> itemStateSocket;
0042     QPointer<QFileSystemWatcher> databaseFileWatcher;
0043     QPointer<KActionCollection> contextActions;
0044 };
0045 
0046 QMap<QString, KVersionControlPlugin::ItemVersion> FileViewDropboxPlugin::m_itemVersions;
0047 
0048 FileViewDropboxPlugin::FileViewDropboxPlugin(QObject* parent, const QVariantList& args):
0049     KVersionControlPlugin(parent),
0050     d(new Private(this))
0051 {
0052     Q_UNUSED(args);
0053 
0054     if (m_itemVersions.isEmpty()) {
0055         m_itemVersions.insert(QStringLiteral("up to date"), KVersionControlPlugin::NormalVersion);
0056         m_itemVersions.insert(QStringLiteral("syncing"),    KVersionControlPlugin::UpdateRequiredVersion);
0057         m_itemVersions.insert(QStringLiteral("unsyncable"), KVersionControlPlugin::ConflictingVersion);
0058         m_itemVersions.insert(QStringLiteral("unwatched"),  KVersionControlPlugin::UnversionedVersion);
0059     }
0060 
0061     const QString dropboxDir = QDir::home().path() % QDir::separator() % fileName() % QDir::separator();
0062     d->controlSocketPath = QDir::toNativeSeparators(dropboxDir % QLatin1String("command_socket"));
0063     d->controlSocket->connectToServer(d->controlSocketPath);
0064 
0065     // Find and watch aggregation.dbx file
0066     QDir dir(dropboxDir);
0067     QStringList nameFilter(QStringLiteral("instance*"));
0068     const QStringList instanceDirs = dir.entryList(nameFilter);
0069     QString aggregationDB;
0070     for (const QString &instance : instanceDirs) {
0071         aggregationDB = dropboxDir + QLatin1Char('/') + instance + QLatin1String("/aggregation.dbx");
0072         if (QFile::exists(aggregationDB)) {
0073             d->databaseFileWatcher->addPath(aggregationDB);
0074             break;
0075         }
0076     }
0077 
0078     connect(d->databaseFileWatcher.data(), &QFileSystemWatcher::fileChanged, this, &KVersionControlPlugin::itemVersionsChanged);
0079     connect(d->contextActions.data(), &KActionCollection::actionTriggered, this, &FileViewDropboxPlugin::handleContextAction);
0080 }
0081 
0082 FileViewDropboxPlugin::~FileViewDropboxPlugin()
0083 {
0084     delete d;
0085 }
0086 
0087 QString FileViewDropboxPlugin::fileName() const
0088 {
0089     return QStringLiteral(".dropbox");
0090 }
0091 
0092 bool FileViewDropboxPlugin::beginRetrieval(const QString& directory)
0093 {
0094     Q_UNUSED(directory);
0095     Q_ASSERT(directory.endsWith(QLatin1Char('/')));
0096 
0097     qRegisterMetaType<QAbstractSocket::SocketError>("QAbstractSocket::SocketError");
0098     qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
0099 
0100     d->itemStateSocket = new QLocalSocket;
0101 
0102     return connectWithDropbox(d->itemStateSocket, LongTimeout);
0103 }
0104 
0105 KVersionControlPlugin::ItemVersion FileViewDropboxPlugin::itemVersion(const KFileItem& item) const
0106 {
0107     const QStringList reply = sendCommand(QStringLiteral("icon_overlay_file_status\npath\t"), QStringList() << QDir(item.localPath()).canonicalPath(),
0108                                           d->itemStateSocket, WaitForReply, LongTimeout);
0109     if(reply.count() < 2) {
0110         // file/dir is not served by dropbox
0111         return KVersionControlPlugin::UnversionedVersion;
0112     }
0113 
0114     return m_itemVersions.value(reply.at(1), KVersionControlPlugin::UnversionedVersion);
0115 }
0116 
0117 void FileViewDropboxPlugin::endRetrieval()
0118 {
0119     delete d->itemStateSocket;
0120 }
0121 
0122 QList<QAction*> FileViewDropboxPlugin::versionControlActions(const KFileItemList &items) const
0123 {
0124     Q_ASSERT(!items.isEmpty());
0125 
0126     d->contextActions->clear();
0127     d->contextFilePaths.clear();
0128 
0129     const KFileItemListProperties properties(items);
0130     if (!properties.isLocal()) {
0131         // not all files/dirs are local files/dirs
0132         return QList<QAction*>();
0133     }
0134 
0135     for (const KFileItem& item : items) {
0136         d->contextFilePaths << QDir(item.localPath()).canonicalPath();
0137     }
0138 
0139     const QStringList reply = sendCommand(QStringLiteral("icon_overlay_context_options\npaths\t"), d->contextFilePaths, d->controlSocket, WaitForReply);
0140     if (reply.count() < 2) {
0141         // files/dirs are not served by dropbox
0142         return QList<QAction*>();
0143     }
0144 
0145     // analyze item options and dynamically form a menu
0146     for (const QString& replyLine : reply) {
0147         const QStringList options = replyLine.split(QLatin1Char('~'));
0148 
0149         if (options.count() > 2) {
0150             QAction* action = d->contextActions->addAction(options.at(2));
0151             action->setText(options.at(0));
0152             action->setToolTip(options.at(1));
0153             action->setIcon(QIcon::fromTheme(QStringLiteral("dropbox")));
0154         }
0155     }
0156 
0157     return d->contextActions->actions();
0158 }
0159 
0160 QList<QAction*> FileViewDropboxPlugin::outOfVersionControlActions(const KFileItemList& items) const
0161 {
0162     Q_UNUSED(items)
0163 
0164     return {};
0165 }
0166 
0167 void FileViewDropboxPlugin::handleContextAction(QAction* action)
0168 {
0169     sendCommand(QLatin1String("icon_overlay_context_action\nverb\t") % action->objectName() % QLatin1String("\npaths\t"), d->contextFilePaths, d->controlSocket);
0170 }
0171 
0172 QStringList FileViewDropboxPlugin::sendCommand(const QString& command,
0173                                                const QStringList& paths,
0174                                                const QPointer<QLocalSocket>& socket,
0175                                                SendCommandMode mode,
0176                                                SendCommandTimeout timeout) const
0177 {
0178     if (!connectWithDropbox(socket, timeout)) {
0179         return QStringList();
0180     }
0181 
0182     static const QString parameterSeperator = QStringLiteral("\t");
0183     static const QString done = QStringLiteral("\ndone\n");
0184     static const QString ok = QStringLiteral("ok\n");
0185 
0186     const QString request = command % paths.join(parameterSeperator) % done;
0187 
0188     socket->readAll();
0189     socket->write(request.toUtf8());
0190     socket->flush();
0191 
0192     if (mode == SendCommandOnly) {
0193         return QStringList();
0194     }
0195 
0196     QString reply;
0197     while (socket->waitForReadyRead(timeout == ShortTimeout ? 100 : 500)) {
0198         reply.append(QString::fromUtf8(socket->readAll()));
0199 
0200         if (reply.endsWith(done)) {
0201             break;
0202         }
0203     }
0204 
0205     reply.remove(done);
0206     reply.remove(ok);
0207 
0208     return reply.split(parameterSeperator, Qt::SkipEmptyParts);
0209 }
0210 
0211 bool FileViewDropboxPlugin::connectWithDropbox(const QPointer<QLocalSocket>& socket, SendCommandTimeout timeout) const
0212 {
0213     if (socket->state() != QLocalSocket::ConnectedState) {
0214         socket->connectToServer(d->controlSocketPath);
0215 
0216         if (!socket->waitForConnected(timeout == ShortTimeout ? 100 : 500)) {
0217             socket->abort();
0218             return false;
0219         }
0220     }
0221 
0222     return true;
0223 }
0224 
0225 #include "fileviewdropboxplugin.moc"
0226 
0227 #include "moc_fileviewdropboxplugin.cpp"