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"