socketapi.cpp 66 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734
  1. /*
  2. * Copyright (C) by Dominik Schmidt <dev@dominik-schmidt.de>
  3. * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
  4. * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 2 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  13. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14. * for more details.
  15. */
  16. #include "socketapi.h"
  17. #include "socketapi_p.h"
  18. #include "socketapi/socketuploadjob.h"
  19. #include "conflictdialog.h"
  20. #include "conflictsolver.h"
  21. #include "config.h"
  22. #include "configfile.h"
  23. #include "deletejob.h"
  24. #include "folderman.h"
  25. #include "folder.h"
  26. #include "encryptfolderjob.h"
  27. #include "theme.h"
  28. #include "common/syncjournalfilerecord.h"
  29. #include "syncengine.h"
  30. #include "syncfileitem.h"
  31. #include "filesystem.h"
  32. #include "version.h"
  33. #include "account.h"
  34. #include "accountstate.h"
  35. #include "account.h"
  36. #include "accountmanager.h"
  37. #include "capabilities.h"
  38. #include "common/asserts.h"
  39. #include "guiutility.h"
  40. #ifndef OWNCLOUD_TEST
  41. #include "sharemanager.h"
  42. #endif
  43. #include <array>
  44. #include <QBitArray>
  45. #include <QUrl>
  46. #include <QMetaMethod>
  47. #include <QMetaObject>
  48. #include <QStringList>
  49. #include <QScopedPointer>
  50. #include <QFile>
  51. #include <QDir>
  52. #include <QApplication>
  53. #include <QLocalSocket>
  54. #include <QStringBuilder>
  55. #include <QMessageBox>
  56. #include <QInputDialog>
  57. #include <QFileDialog>
  58. #include <QAction>
  59. #include <QJsonArray>
  60. #include <QJsonDocument>
  61. #include <QJsonObject>
  62. #include <QWidget>
  63. #include <QClipboard>
  64. #include <QDesktopServices>
  65. #include <QProcess>
  66. #include <QStandardPaths>
  67. #ifdef Q_OS_MAC
  68. #include <CoreFoundation/CoreFoundation.h>
  69. #endif
  70. // This is the version that is returned when the client asks for the VERSION.
  71. // The first number should be changed if there is an incompatible change that breaks old clients.
  72. // The second number should be changed when there are new features.
  73. #define MIRALL_SOCKET_API_VERSION "1.1"
  74. namespace {
  75. constexpr auto encryptJobPropertyFolder = "folder";
  76. constexpr auto encryptJobPropertyPath = "path";
  77. }
  78. namespace {
  79. const QLatin1Char RecordSeparator()
  80. {
  81. return QLatin1Char('\x1e');
  82. }
  83. QStringList split(const QString &data)
  84. {
  85. // TODO: string ref?
  86. return data.split(RecordSeparator());
  87. }
  88. #if GUI_TESTING
  89. using namespace OCC;
  90. QList<QObject *> allObjects(const QList<QWidget *> &widgets)
  91. {
  92. QList<QObject *> objects;
  93. std::copy(widgets.constBegin(), widgets.constEnd(), std::back_inserter(objects));
  94. objects << qApp;
  95. return objects;
  96. }
  97. QObject *findWidget(const QString &queryString, const QList<QWidget *> &widgets = QApplication::allWidgets())
  98. {
  99. auto objects = allObjects(widgets);
  100. QList<QObject *>::const_iterator foundWidget;
  101. if (queryString.contains('>')) {
  102. qCDebug(lcSocketApi) << "queryString contains >";
  103. auto subQueries = queryString.split('>', QString::SkipEmptyParts);
  104. Q_ASSERT(subQueries.count() == 2);
  105. auto parentQueryString = subQueries[0].trimmed();
  106. qCDebug(lcSocketApi) << "Find parent: " << parentQueryString;
  107. auto parent = findWidget(parentQueryString);
  108. if (!parent) {
  109. return nullptr;
  110. }
  111. auto childQueryString = subQueries[1].trimmed();
  112. auto child = findWidget(childQueryString, parent->findChildren<QWidget *>());
  113. qCDebug(lcSocketApi) << "found child: " << !!child;
  114. return child;
  115. } else if (queryString.startsWith('#')) {
  116. auto objectName = queryString.mid(1);
  117. qCDebug(lcSocketApi) << "find objectName: " << objectName;
  118. foundWidget = std::find_if(objects.constBegin(), objects.constEnd(), [&](QObject *widget) {
  119. return widget->objectName() == objectName;
  120. });
  121. } else {
  122. QList<QObject *> matches;
  123. std::copy_if(objects.constBegin(), objects.constEnd(), std::back_inserter(matches), [&](QObject *widget) {
  124. return widget->inherits(queryString.toLatin1());
  125. });
  126. std::for_each(matches.constBegin(), matches.constEnd(), [](QObject *w) {
  127. if (!w)
  128. return;
  129. qCDebug(lcSocketApi) << "WIDGET: " << w->objectName() << w->metaObject()->className();
  130. });
  131. if (matches.empty()) {
  132. return nullptr;
  133. }
  134. return matches[0];
  135. }
  136. if (foundWidget == objects.constEnd()) {
  137. return nullptr;
  138. }
  139. return *foundWidget;
  140. }
  141. #endif
  142. static inline QString removeTrailingSlash(QString path)
  143. {
  144. Q_ASSERT(path.endsWith(QLatin1Char('/')));
  145. path.truncate(path.length() - 1);
  146. return path;
  147. }
  148. static QString buildMessage(const QString &verb, const QString &path, const QString &status = QString())
  149. {
  150. QString msg(verb);
  151. if (!status.isEmpty()) {
  152. msg.append(QLatin1Char(':'));
  153. msg.append(status);
  154. }
  155. if (!path.isEmpty()) {
  156. msg.append(QLatin1Char(':'));
  157. QFileInfo fi(path);
  158. msg.append(QDir::toNativeSeparators(fi.absoluteFilePath()));
  159. }
  160. return msg;
  161. }
  162. }
  163. namespace OCC {
  164. Q_LOGGING_CATEGORY(lcSocketApi, "nextcloud.gui.socketapi", QtInfoMsg)
  165. Q_LOGGING_CATEGORY(lcPublicLink, "nextcloud.gui.socketapi.publiclink", QtInfoMsg)
  166. void SocketListener::sendMessage(const QString &message, bool doWait) const
  167. {
  168. if (!socket) {
  169. qCWarning(lcSocketApi) << "Not sending message to dead socket:" << message;
  170. return;
  171. }
  172. qCDebug(lcSocketApi) << "Sending SocketAPI message -->" << message << "to" << socket;
  173. QString localMessage = message;
  174. if (!localMessage.endsWith(QLatin1Char('\n'))) {
  175. localMessage.append(QLatin1Char('\n'));
  176. }
  177. QByteArray bytesToSend = localMessage.toUtf8();
  178. qint64 sent = socket->write(bytesToSend);
  179. if (doWait) {
  180. socket->waitForBytesWritten(1000);
  181. }
  182. if (sent != bytesToSend.length()) {
  183. qCWarning(lcSocketApi) << "Could not send all data on socket for " << localMessage;
  184. }
  185. }
  186. SocketApi::SocketApi(QObject *parent)
  187. : QObject(parent)
  188. {
  189. QString socketPath;
  190. qRegisterMetaType<SocketListener *>("SocketListener*");
  191. qRegisterMetaType<QSharedPointer<SocketApiJob>>("QSharedPointer<SocketApiJob>");
  192. qRegisterMetaType<QSharedPointer<SocketApiJobV2>>("QSharedPointer<SocketApiJobV2>");
  193. if (Utility::isWindows()) {
  194. socketPath = QLatin1String(R"(\\.\pipe\)")
  195. + QLatin1String(APPLICATION_EXECUTABLE)
  196. + QLatin1String("-")
  197. + QString::fromLocal8Bit(qgetenv("USERNAME"));
  198. // TODO: once the windows extension supports multiple
  199. // client connections, switch back to the theme name
  200. // See issue #2388
  201. // + Theme::instance()->appName();
  202. } else if (Utility::isMac()) {
  203. #ifdef Q_OS_MACOS
  204. socketPath = socketApiSocketPath();
  205. CFURLRef url = (CFURLRef)CFAutorelease((CFURLRef)CFBundleCopyBundleURL(CFBundleGetMainBundle()));
  206. QString bundlePath = QUrl::fromCFURL(url).path();
  207. auto _system = [](const QString &cmd, const QStringList &args) {
  208. QProcess process;
  209. process.setProcessChannelMode(QProcess::MergedChannels);
  210. process.start(cmd, args);
  211. if (!process.waitForFinished()) {
  212. qCWarning(lcSocketApi) << "Failed to load shell extension:" << cmd << args.join(" ") << process.errorString();
  213. } else {
  214. qCInfo(lcSocketApi) << (process.exitCode() != 0 ? "Failed to load" : "Loaded") << "shell extension:" << cmd << args.join(" ") << process.readAll();
  215. }
  216. };
  217. // Add it again. This was needed for Mojave to trigger a load.
  218. _system(QStringLiteral("pluginkit"), { QStringLiteral("-a"), QStringLiteral("%1Contents/PlugIns/FinderSyncExt.appex/").arg(bundlePath) });
  219. // Tell Finder to use the Extension (checking it from System Preferences -> Extensions)
  220. _system(QStringLiteral("pluginkit"), { QStringLiteral("-e"), QStringLiteral("use"), QStringLiteral("-i"), QStringLiteral(APPLICATION_REV_DOMAIN ".FinderSyncExt") });
  221. #endif
  222. } else if (Utility::isLinux() || Utility::isBSD()) {
  223. QString runtimeDir;
  224. runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
  225. socketPath = runtimeDir + "/" + Theme::instance()->appName() + "/socket";
  226. } else {
  227. qCWarning(lcSocketApi) << "An unexpected system detected, this probably won't work.";
  228. }
  229. QLocalServer::removeServer(socketPath);
  230. // Create the socket path:
  231. if (!Utility::isMac()) {
  232. // Not on macOS: there the directory is there, and created for us by the sandboxing
  233. // environment, because we belong to an App Group.
  234. QFileInfo info(socketPath);
  235. if (!info.dir().exists()) {
  236. bool result = info.dir().mkpath(".");
  237. qCDebug(lcSocketApi) << "creating" << info.dir().path() << result;
  238. if (result) {
  239. QFile::setPermissions(socketPath,
  240. QFile::Permissions(QFile::ReadOwner + QFile::WriteOwner + QFile::ExeOwner));
  241. }
  242. }
  243. }
  244. if (!_localServer.listen(socketPath)) {
  245. qCWarning(lcSocketApi) << "can't start server" << socketPath;
  246. } else {
  247. qCInfo(lcSocketApi) << "server started, listening at " << socketPath;
  248. }
  249. connect(&_localServer, &QLocalServer::newConnection, this, &SocketApi::slotNewConnection);
  250. // folder watcher
  251. connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, this, &SocketApi::slotUpdateFolderView);
  252. }
  253. SocketApi::~SocketApi()
  254. {
  255. qCDebug(lcSocketApi) << "dtor";
  256. _localServer.close();
  257. // All remaining sockets will be destroyed with _localServer, their parent
  258. ASSERT(_listeners.isEmpty() || _listeners.first()->socket->parent() == &_localServer)
  259. _listeners.clear();
  260. }
  261. void SocketApi::slotNewConnection()
  262. {
  263. QIODevice *socket = _localServer.nextPendingConnection();
  264. if (!socket) {
  265. return;
  266. }
  267. qCInfo(lcSocketApi) << "New connection" << socket;
  268. connect(socket, &QIODevice::readyRead, this, &SocketApi::slotReadSocket);
  269. connect(socket, SIGNAL(disconnected()), this, SLOT(onLostConnection()));
  270. connect(socket, &QObject::destroyed, this, &SocketApi::slotSocketDestroyed);
  271. ASSERT(socket->readAll().isEmpty());
  272. auto listener = QSharedPointer<SocketListener>::create(socket);
  273. _listeners.insert(socket, listener);
  274. for (Folder *f : FolderMan::instance()->map()) {
  275. if (f->canSync()) {
  276. QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
  277. qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket;
  278. listener->sendMessage(message);
  279. }
  280. }
  281. }
  282. void SocketApi::onLostConnection()
  283. {
  284. qCInfo(lcSocketApi) << "Lost connection " << sender();
  285. sender()->deleteLater();
  286. auto socket = qobject_cast<QIODevice *>(sender());
  287. ASSERT(socket);
  288. _listeners.remove(socket);
  289. }
  290. void SocketApi::slotSocketDestroyed(QObject *obj)
  291. {
  292. auto *socket = dynamic_cast<QIODevice *>(obj);
  293. _listeners.remove(socket);
  294. }
  295. void SocketApi::slotReadSocket()
  296. {
  297. auto *socket = qobject_cast<QIODevice *>(sender());
  298. ASSERT(socket);
  299. // Find the SocketListener
  300. //
  301. // It's possible for the disconnected() signal to be triggered before
  302. // the readyRead() signals are received - in that case there won't be a
  303. // valid listener. We execute the handler anyway, but it will work with
  304. // a SocketListener that doesn't send any messages.
  305. static auto invalidListener = QSharedPointer<SocketListener>::create(nullptr);
  306. const auto listener = _listeners.value(socket, invalidListener);
  307. while (socket->canReadLine()) {
  308. // Make sure to normalize the input from the socket to
  309. // make sure that the path will match, especially on OS X.
  310. const QString line = QString::fromUtf8(socket->readLine().trimmed()).normalized(QString::NormalizationForm_C);
  311. qCInfo(lcSocketApi) << "Received SocketAPI message <--" << line << "from" << socket;
  312. const int argPos = line.indexOf(QLatin1Char(':'));
  313. const QByteArray command = line.midRef(0, argPos).toUtf8().toUpper();
  314. const int indexOfMethod = [&] {
  315. QByteArray functionWithArguments = QByteArrayLiteral("command_");
  316. if (command.startsWith("ASYNC_")) {
  317. functionWithArguments += command + QByteArrayLiteral("(QSharedPointer<SocketApiJob>)");
  318. } else if (command.startsWith("V2/")) {
  319. functionWithArguments += QByteArrayLiteral("V2_") + command.mid(3) + QByteArrayLiteral("(QSharedPointer<SocketApiJobV2>)");
  320. } else {
  321. functionWithArguments += command + QByteArrayLiteral("(QString,SocketListener*)");
  322. }
  323. Q_ASSERT(staticQtMetaObject.normalizedSignature(functionWithArguments) == functionWithArguments);
  324. const auto out = staticMetaObject.indexOfMethod(functionWithArguments);
  325. if (out == -1) {
  326. listener->sendError(QStringLiteral("Function %1 not found").arg(QString::fromUtf8(functionWithArguments)));
  327. }
  328. ASSERT(out != -1)
  329. return out;
  330. }();
  331. const auto argument = argPos != -1 ? line.midRef(argPos + 1) : QStringRef();
  332. if (command.startsWith("ASYNC_")) {
  333. auto arguments = argument.split('|');
  334. if (arguments.size() != 2) {
  335. listener->sendError(QStringLiteral("argument count is wrong"));
  336. return;
  337. }
  338. auto json = QJsonDocument::fromJson(arguments[1].toUtf8()).object();
  339. auto jobId = arguments[0];
  340. auto socketApiJob = QSharedPointer<SocketApiJob>(
  341. new SocketApiJob(jobId.toString(), listener, json), &QObject::deleteLater);
  342. if (indexOfMethod != -1) {
  343. staticMetaObject.method(indexOfMethod)
  344. .invoke(this, Qt::QueuedConnection,
  345. Q_ARG(QSharedPointer<SocketApiJob>, socketApiJob));
  346. } else {
  347. qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command
  348. << "with argument:" << argument;
  349. socketApiJob->reject(QStringLiteral("command not found"));
  350. }
  351. } else if (command.startsWith("V2/")) {
  352. QJsonParseError error{};
  353. const auto json = QJsonDocument::fromJson(argument.toUtf8(), &error).object();
  354. if (error.error != QJsonParseError::NoError) {
  355. qCWarning(lcSocketApi()) << "Invalid json" << argument.toString() << error.errorString();
  356. listener->sendError(error.errorString());
  357. return;
  358. }
  359. auto socketApiJob = QSharedPointer<SocketApiJobV2>::create(listener, command, json);
  360. if (indexOfMethod != -1) {
  361. staticMetaObject.method(indexOfMethod)
  362. .invoke(this, Qt::QueuedConnection,
  363. Q_ARG(QSharedPointer<SocketApiJobV2>, socketApiJob));
  364. } else {
  365. qCWarning(lcSocketApi) << "The command is not supported by this version of the client:" << command
  366. << "with argument:" << argument;
  367. socketApiJob->failure(QStringLiteral("command not found"));
  368. }
  369. } else {
  370. if (indexOfMethod != -1) {
  371. // to ensure that listener is still valid we need to call it with Qt::DirectConnection
  372. ASSERT(thread() == QThread::currentThread())
  373. staticMetaObject.method(indexOfMethod)
  374. .invoke(this, Qt::DirectConnection, Q_ARG(QString, argument.toString()),
  375. Q_ARG(SocketListener *, listener.data()));
  376. }
  377. }
  378. }
  379. }
  380. void SocketApi::slotRegisterPath(const QString &alias)
  381. {
  382. // Make sure not to register twice to each connected client
  383. if (_registeredAliases.contains(alias))
  384. return;
  385. Folder *f = FolderMan::instance()->folder(alias);
  386. if (f) {
  387. const QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
  388. for (const auto &listener : qAsConst(_listeners)) {
  389. qCInfo(lcSocketApi) << "Trying to send SocketAPI Register Path Message -->" << message << "to" << listener->socket;
  390. listener->sendMessage(message);
  391. }
  392. }
  393. _registeredAliases.insert(alias);
  394. }
  395. void SocketApi::slotUnregisterPath(const QString &alias)
  396. {
  397. if (!_registeredAliases.contains(alias))
  398. return;
  399. Folder *f = FolderMan::instance()->folder(alias);
  400. if (f)
  401. broadcastMessage(buildMessage(QLatin1String("UNREGISTER_PATH"), removeTrailingSlash(f->path()), QString()), true);
  402. _registeredAliases.remove(alias);
  403. }
  404. void SocketApi::slotUpdateFolderView(Folder *f)
  405. {
  406. if (_listeners.isEmpty()) {
  407. return;
  408. }
  409. if (f) {
  410. // do only send UPDATE_VIEW for a couple of status
  411. if (f->syncResult().status() == SyncResult::SyncPrepare
  412. || f->syncResult().status() == SyncResult::Success
  413. || f->syncResult().status() == SyncResult::Paused
  414. || f->syncResult().status() == SyncResult::Problem
  415. || f->syncResult().status() == SyncResult::Error
  416. || f->syncResult().status() == SyncResult::SetupError) {
  417. QString rootPath = removeTrailingSlash(f->path());
  418. broadcastStatusPushMessage(rootPath, f->syncEngine().syncFileStatusTracker().fileStatus(""));
  419. broadcastMessage(buildMessage(QLatin1String("UPDATE_VIEW"), rootPath));
  420. } else {
  421. qCDebug(lcSocketApi) << "Not sending UPDATE_VIEW for" << f->alias() << "because status() is" << f->syncResult().status();
  422. }
  423. }
  424. }
  425. void SocketApi::broadcastMessage(const QString &msg, bool doWait)
  426. {
  427. for (const auto &listener : qAsConst(_listeners)) {
  428. listener->sendMessage(msg, doWait);
  429. }
  430. }
  431. void SocketApi::processFileActivityRequest(const QString &localFile)
  432. {
  433. const auto fileData = FileData::get(localFile);
  434. emit fileActivityCommandReceived(fileData.localPath);
  435. }
  436. void SocketApi::processEncryptRequest(const QString &localFile)
  437. {
  438. Q_ASSERT(QFileInfo(localFile).isDir());
  439. const auto fileData = FileData::get(localFile);
  440. const auto folder = fileData.folder;
  441. Q_ASSERT(folder);
  442. const auto account = folder->accountState()->account();
  443. Q_ASSERT(account);
  444. const auto rec = fileData.journalRecord();
  445. Q_ASSERT(rec.isValid());
  446. if (!account->e2e() || account->e2e()->_mnemonic.isEmpty()) {
  447. const int ret = QMessageBox::critical(nullptr,
  448. tr("Failed to encrypt folder at \"%1\"").arg(fileData.folderRelativePath),
  449. tr("The account %1 does not have end-to-end encryption configured. "
  450. "Please configure this in your account settings to enable folder encryption.").arg(account->prettyName()));
  451. Q_UNUSED(ret)
  452. return;
  453. }
  454. auto path = rec._path;
  455. // Folder records have directory paths in Foo/Bar/ convention...
  456. // But EncryptFolderJob expects directory path Foo/Bar convention
  457. auto choppedPath = path;
  458. if (choppedPath.endsWith('/') && choppedPath != QStringLiteral("/")) {
  459. choppedPath.chop(1);
  460. }
  461. if (choppedPath.startsWith('/') && choppedPath != QStringLiteral("/")) {
  462. choppedPath = choppedPath.mid(1);
  463. }
  464. auto job = new OCC::EncryptFolderJob(account, folder->journalDb(), choppedPath, rec.numericFileId(), this);
  465. connect(job, &OCC::EncryptFolderJob::finished, this, [fileData, job](const int status) {
  466. if (status == OCC::EncryptFolderJob::Error) {
  467. const int ret = QMessageBox::critical(nullptr,
  468. tr("Failed to encrypt folder"),
  469. tr("Could not encrypt the following folder: \"%1\".\n\n"
  470. "Server replied with error: %2").arg(fileData.folderRelativePath, job->errorString()));
  471. Q_UNUSED(ret)
  472. } else {
  473. const int ret = QMessageBox::information(nullptr,
  474. tr("Folder encrypted successfully").arg(fileData.folderRelativePath),
  475. tr("The following folder was encrypted successfully: \"%1\"").arg(fileData.folderRelativePath));
  476. Q_UNUSED(ret)
  477. }
  478. });
  479. job->setProperty(encryptJobPropertyFolder, QVariant::fromValue(folder));
  480. job->setProperty(encryptJobPropertyPath, QVariant::fromValue(path));
  481. job->start();
  482. }
  483. void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener)
  484. {
  485. auto theme = Theme::instance();
  486. auto fileData = FileData::get(localFile);
  487. auto shareFolder = fileData.folder;
  488. if (!shareFolder) {
  489. const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
  490. // files that are not within a sync folder are not synced.
  491. listener->sendMessage(message);
  492. } else if (!shareFolder->accountState()->isConnected()) {
  493. const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile);
  494. // if the folder isn't connected, don't open the share dialog
  495. listener->sendMessage(message);
  496. } else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
  497. const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
  498. listener->sendMessage(message);
  499. } else {
  500. // If the file doesn't have a journal record, it might not be uploaded yet
  501. if (!fileData.journalRecord().isValid()) {
  502. const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
  503. listener->sendMessage(message);
  504. return;
  505. }
  506. if (!fileData.journalRecord().e2eMangledName().isEmpty()) {
  507. // we can not share an encrypted file or a subfolder under encrypted root foolder
  508. const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
  509. listener->sendMessage(message);
  510. return;
  511. }
  512. auto &remotePath = fileData.serverRelativePath;
  513. // Can't share root folder
  514. if (remotePath == "/") {
  515. const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
  516. listener->sendMessage(message);
  517. return;
  518. }
  519. const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
  520. listener->sendMessage(message);
  521. emit shareCommandReceived(fileData.localPath);
  522. }
  523. }
  524. void SocketApi::processLeaveShareRequest(const QString &localFile, SocketListener *listener)
  525. {
  526. Q_UNUSED(listener)
  527. FolderMan::instance()->leaveShare(QDir::fromNativeSeparators(localFile));
  528. }
  529. void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus)
  530. {
  531. QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
  532. Q_ASSERT(!systemPath.endsWith('/'));
  533. uint directoryHash = qHash(systemPath.left(systemPath.lastIndexOf('/')));
  534. for (const auto &listener : qAsConst(_listeners)) {
  535. listener->sendMessageIfDirectoryMonitored(msg, directoryHash);
  536. }
  537. }
  538. void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener)
  539. {
  540. // This command is the same as RETRIEVE_FILE_STATUS
  541. command_RETRIEVE_FILE_STATUS(argument, listener);
  542. }
  543. void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener)
  544. {
  545. QString statusString;
  546. auto fileData = FileData::get(argument);
  547. if (!fileData.folder) {
  548. // this can happen in offline mode e.g.: nothing to worry about
  549. statusString = QLatin1String("NOP");
  550. } else {
  551. // The user probably visited this directory in the file shell.
  552. // Let the listener know that it should now send status pushes for sibblings of this file.
  553. QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/'));
  554. listener->registerMonitoredDirectory(qHash(directory));
  555. SyncFileStatus fileStatus = fileData.syncFileStatus();
  556. statusString = fileStatus.toSocketAPIString();
  557. }
  558. const QString message = QLatin1String("STATUS:") % statusString % QLatin1Char(':') % QDir::toNativeSeparators(argument);
  559. listener->sendMessage(message);
  560. }
  561. void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
  562. {
  563. processShareRequest(localFile, listener);
  564. }
  565. void SocketApi::command_LEAVESHARE(const QString &localFile, SocketListener *listener)
  566. {
  567. processLeaveShareRequest(localFile, listener);
  568. }
  569. void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener)
  570. {
  571. Q_UNUSED(listener);
  572. processFileActivityRequest(localFile);
  573. }
  574. void SocketApi::command_ENCRYPT(const QString &localFile, SocketListener *listener)
  575. {
  576. Q_UNUSED(listener);
  577. processEncryptRequest(localFile);
  578. }
  579. void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
  580. {
  581. processShareRequest(localFile, listener);
  582. }
  583. void SocketApi::command_VERSION(const QString &, SocketListener *listener)
  584. {
  585. listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
  586. }
  587. void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
  588. {
  589. //listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
  590. listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + Theme::instance()->appNameGUI());
  591. }
  592. void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
  593. {
  594. Q_UNUSED(listener)
  595. auto fileData = FileData::get(localFile);
  596. if (!fileData.folder) {
  597. qCWarning(lcSocketApi) << "Unknown path" << localFile;
  598. return;
  599. }
  600. auto record = fileData.journalRecord();
  601. if (!record.isValid())
  602. return;
  603. DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
  604. if (!editor)
  605. return;
  606. auto *job = new JsonApiJob(fileData.folder->accountState()->account(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing/open"), this);
  607. QUrlQuery params;
  608. params.addQueryItem("path", fileData.serverRelativePath);
  609. params.addQueryItem("editorId", editor->id());
  610. job->addQueryParams(params);
  611. job->setVerb(JsonApiJob::Verb::Post);
  612. QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
  613. auto data = json.object().value("ocs").toObject().value("data").toObject();
  614. auto url = QUrl(data.value("url").toString());
  615. if(!url.isEmpty())
  616. Utility::openBrowser(url);
  617. });
  618. job->start();
  619. }
  620. // don't pull the share manager into socketapi unittests
  621. #ifndef OWNCLOUD_TEST
  622. class GetOrCreatePublicLinkShare : public QObject
  623. {
  624. Q_OBJECT
  625. public:
  626. GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile, const bool isSecureFileDropOnlyFolder,
  627. QObject *parent)
  628. : QObject(parent)
  629. , _account(account)
  630. , _shareManager(account)
  631. , _localFile(localFile)
  632. , _isSecureFileDropOnlyFolder(isSecureFileDropOnlyFolder)
  633. {
  634. connect(&_shareManager, &ShareManager::sharesFetched,
  635. this, &GetOrCreatePublicLinkShare::sharesFetched);
  636. connect(&_shareManager, &ShareManager::linkShareCreated,
  637. this, &GetOrCreatePublicLinkShare::linkShareCreated);
  638. connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
  639. this, &GetOrCreatePublicLinkShare::linkShareRequiresPassword);
  640. connect(&_shareManager, &ShareManager::serverError,
  641. this, &GetOrCreatePublicLinkShare::serverError);
  642. }
  643. void run()
  644. {
  645. qCDebug(lcPublicLink) << "Fetching shares";
  646. _shareManager.fetchShares(_localFile);
  647. }
  648. private slots:
  649. void sharesFetched(const QList<OCC::SharePtr> &shares)
  650. {
  651. auto shareName = SocketApi::tr("Context menu share");
  652. // If there already is a context menu share, reuse it
  653. for (const auto &share : shares) {
  654. const auto linkShare = qSharedPointerDynamicCast<LinkShare>(share);
  655. if (!linkShare)
  656. continue;
  657. if (linkShare->getName() == shareName) {
  658. qCDebug(lcPublicLink) << "Found existing share, reusing";
  659. return success(linkShare->getLink().toString());
  660. }
  661. }
  662. // otherwise create a new one
  663. qCDebug(lcPublicLink) << "Creating new share";
  664. if (_isSecureFileDropOnlyFolder) {
  665. _shareManager.createSecureFileDropShare(_localFile, shareName, QString());
  666. } else {
  667. _shareManager.createLinkShare(_localFile, shareName, QString());
  668. }
  669. }
  670. void linkShareCreated(const QSharedPointer<OCC::LinkShare> &share)
  671. {
  672. qCDebug(lcPublicLink) << "New share created";
  673. success(share->getLink().toString());
  674. }
  675. void passwordRequired() {
  676. bool ok = false;
  677. QString password = QInputDialog::getText(nullptr,
  678. tr("Password for share required"),
  679. tr("Please enter a password for your link share:"),
  680. QLineEdit::Normal,
  681. QString(),
  682. &ok);
  683. if (!ok) {
  684. // The dialog was canceled so no need to do anything
  685. return;
  686. }
  687. // Try to create the link share again with the newly entered password
  688. _shareManager.createLinkShare(_localFile, QString(), password);
  689. }
  690. void linkShareRequiresPassword(const QString &message)
  691. {
  692. qCInfo(lcPublicLink) << "Could not create link share:" << message;
  693. emit error(message);
  694. deleteLater();
  695. }
  696. void serverError(int code, const QString &message)
  697. {
  698. qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
  699. QMessageBox::warning(
  700. nullptr,
  701. tr("Sharing error"),
  702. tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message),
  703. QMessageBox::Ok,
  704. QMessageBox::NoButton);
  705. emit error(message);
  706. deleteLater();
  707. }
  708. signals:
  709. void done(const QString &link);
  710. void error(const QString &message);
  711. private:
  712. void success(const QString &link)
  713. {
  714. emit done(link);
  715. deleteLater();
  716. }
  717. AccountPtr _account;
  718. ShareManager _shareManager;
  719. QString _localFile;
  720. bool _isSecureFileDropOnlyFolder = false;
  721. };
  722. #else
  723. class GetOrCreatePublicLinkShare : public QObject
  724. {
  725. Q_OBJECT
  726. public:
  727. GetOrCreatePublicLinkShare(const AccountPtr &, const QString &,
  728. std::function<void(const QString &link)>, QObject *)
  729. {
  730. }
  731. void run()
  732. {
  733. }
  734. };
  735. #endif
  736. void SocketApi::command_COPY_SECUREFILEDROP_LINK(const QString &localFile, SocketListener *)
  737. {
  738. const auto fileData = FileData::get(localFile);
  739. if (!fileData.folder) {
  740. return;
  741. }
  742. const auto account = fileData.folder->accountState()->account();
  743. const auto getOrCreatePublicLinkShareJob = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, true, this);
  744. connect(getOrCreatePublicLinkShareJob, &GetOrCreatePublicLinkShare::done, this, [](const QString &url) { copyUrlToClipboard(url); });
  745. connect(getOrCreatePublicLinkShareJob, &GetOrCreatePublicLinkShare::error, this, [=]() { emit shareCommandReceived(fileData.localPath); });
  746. getOrCreatePublicLinkShareJob->run();
  747. }
  748. void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *)
  749. {
  750. const auto fileData = FileData::get(localFile);
  751. if (!fileData.folder) {
  752. return;
  753. }
  754. const auto account = fileData.folder->accountState()->account();
  755. const auto getOrCreatePublicLinkShareJob = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, false, this);
  756. connect(getOrCreatePublicLinkShareJob, &GetOrCreatePublicLinkShare::done, this, [](const QString &url) {
  757. copyUrlToClipboard(url);
  758. });
  759. connect(getOrCreatePublicLinkShareJob, &GetOrCreatePublicLinkShare::error, this, [=]() {
  760. emit shareCommandReceived(fileData.localPath);
  761. });
  762. getOrCreatePublicLinkShareJob->run();
  763. }
  764. // Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
  765. #ifdef Q_OS_WIN
  766. void SocketApi::command_COPYASPATH(const QString &localFile, SocketListener *)
  767. {
  768. QApplication::clipboard()->setText(localFile);
  769. }
  770. void SocketApi::command_OPENNEWWINDOW(const QString &localFile, SocketListener *)
  771. {
  772. QDesktopServices::openUrl(QUrl::fromLocalFile(localFile));
  773. }
  774. void SocketApi::command_OPEN(const QString &localFile, SocketListener *socketListener)
  775. {
  776. command_OPENNEWWINDOW(localFile, socketListener);
  777. }
  778. #endif
  779. // Fetches the private link url asynchronously and then calls the target slot
  780. void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
  781. {
  782. auto fileData = FileData::get(localFile);
  783. if (!fileData.folder) {
  784. qCWarning(lcSocketApi) << "Unknown path" << localFile;
  785. return;
  786. }
  787. auto record = fileData.journalRecord();
  788. if (!record.isValid())
  789. return;
  790. fetchPrivateLinkUrl(
  791. fileData.folder->accountState()->account(),
  792. fileData.serverRelativePath,
  793. record.numericFileId(),
  794. this,
  795. targetFun);
  796. }
  797. void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
  798. {
  799. fetchPrivateLinkUrlHelper(localFile, &SocketApi::copyUrlToClipboard);
  800. }
  801. void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
  802. {
  803. fetchPrivateLinkUrlHelper(localFile, &SocketApi::emailPrivateLink);
  804. }
  805. void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *)
  806. {
  807. fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink);
  808. }
  809. void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketListener *)
  810. {
  811. const QStringList files = split(filesArg);
  812. for (const auto &file : files) {
  813. auto data = FileData::get(file);
  814. if (!data.folder)
  815. continue;
  816. // Update the pin state on all items
  817. if (!data.folder->vfs().setPinState(data.folderRelativePath, PinState::AlwaysLocal)) {
  818. qCWarning(lcSocketApi) << "Could not set pin state of" << data.folderRelativePath << "to always local";
  819. }
  820. // Trigger sync
  821. data.folder->schedulePathForLocalDiscovery(data.folderRelativePath);
  822. data.folder->scheduleThisFolderSoon();
  823. }
  824. }
  825. /* Go over all the files and replace them by a virtual file */
  826. void SocketApi::command_MAKE_ONLINE_ONLY(const QString &filesArg, SocketListener *)
  827. {
  828. const QStringList files = split(filesArg);
  829. for (const auto &file : files) {
  830. auto data = FileData::get(file);
  831. if (!data.folder)
  832. continue;
  833. // Update the pin state on all items
  834. if (!data.folder->vfs().setPinState(data.folderRelativePath, PinState::OnlineOnly)) {
  835. qCWarning(lcSocketApi) << "Could not set pin state of" << data.folderRelativePath << "to online only";
  836. }
  837. // Trigger sync
  838. data.folder->schedulePathForLocalDiscovery(data.folderRelativePath);
  839. data.folder->scheduleThisFolderSoon();
  840. }
  841. }
  842. void SocketApi::copyUrlToClipboard(const QString &link)
  843. {
  844. QApplication::clipboard()->setText(link);
  845. }
  846. void SocketApi::command_RESOLVE_CONFLICT(const QString &localFile, SocketListener *)
  847. {
  848. const auto fileData = FileData::get(localFile);
  849. if (!fileData.folder || !Utility::isConflictFile(fileData.folderRelativePath))
  850. return; // should not have shown menu item
  851. const auto conflictedRelativePath = fileData.folderRelativePath;
  852. const auto baseRelativePath = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8());
  853. const auto dir = QDir(fileData.folder->path());
  854. const auto conflictedPath = dir.filePath(conflictedRelativePath);
  855. const auto basePath = dir.filePath(baseRelativePath);
  856. const auto baseName = QFileInfo(basePath).fileName();
  857. #ifndef OWNCLOUD_TEST
  858. ConflictDialog dialog;
  859. dialog.setBaseFilename(baseName);
  860. dialog.setLocalVersionFilename(conflictedPath);
  861. dialog.setRemoteVersionFilename(basePath);
  862. if (dialog.exec() == ConflictDialog::Accepted) {
  863. fileData.folder->scheduleThisFolderSoon();
  864. }
  865. #endif
  866. }
  867. void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *)
  868. {
  869. ConflictSolver solver;
  870. solver.setLocalVersionFilename(localFile);
  871. solver.exec(ConflictSolver::KeepRemoteVersion);
  872. }
  873. void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *)
  874. {
  875. const auto fileData = FileData::get(localFile);
  876. const auto parentDir = fileData.parentFolder();
  877. if (!fileData.folder)
  878. return; // should not have shown menu item
  879. QString defaultDirAndName = fileData.folderRelativePath;
  880. // If it's a conflict, we want to save it under the base name by default
  881. if (Utility::isConflictFile(defaultDirAndName)) {
  882. defaultDirAndName = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8());
  883. }
  884. // If the parent doesn't accept new files, go to the root of the sync folder
  885. QFileInfo fileInfo(localFile);
  886. const auto parentRecord = parentDir.journalRecord();
  887. if ((fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile))
  888. || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) {
  889. defaultDirAndName = QFileInfo(defaultDirAndName).fileName();
  890. }
  891. // Add back the folder path
  892. defaultDirAndName = QDir(fileData.folder->path()).filePath(defaultDirAndName);
  893. const auto target = QFileDialog::getSaveFileName(
  894. nullptr,
  895. tr("Select new location …"),
  896. defaultDirAndName,
  897. QString(), nullptr, QFileDialog::HideNameFilterDetails);
  898. if (target.isEmpty())
  899. return;
  900. ConflictSolver solver;
  901. solver.setLocalVersionFilename(localFile);
  902. solver.setRemoteVersionFilename(target);
  903. }
  904. void SocketApi::command_LOCK_FILE(const QString &localFile, SocketListener *listener)
  905. {
  906. Q_UNUSED(listener)
  907. setFileLock(localFile, SyncFileItem::LockStatus::LockedItem);
  908. }
  909. void SocketApi::command_UNLOCK_FILE(const QString &localFile, SocketListener *listener)
  910. {
  911. Q_UNUSED(listener)
  912. setFileLock(localFile, SyncFileItem::LockStatus::UnlockedItem);
  913. }
  914. void SocketApi::setFileLock(const QString &localFile, const SyncFileItem::LockStatus lockState) const
  915. {
  916. const auto fileData = FileData::get(localFile);
  917. const auto shareFolder = fileData.folder;
  918. if (!shareFolder || !shareFolder->accountState()->isConnected()) {
  919. return;
  920. }
  921. shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath, shareFolder->journalDb(), lockState);
  922. shareFolder->journalDb()->schedulePathForRemoteDiscovery(fileData.serverRelativePath);
  923. shareFolder->scheduleThisFolderSoon();
  924. }
  925. void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const
  926. {
  927. QJsonArray out;
  928. const auto accounts = AccountManager::instance()->accounts();
  929. for (auto acc : accounts) {
  930. // TODO: Use uuid once https://github.com/owncloud/client/pull/8397 is merged
  931. out << QJsonObject({ { "name", acc->account()->displayName() }, { "id", acc->account()->id() } });
  932. }
  933. job->success({ { "accounts", out } });
  934. }
  935. void SocketApi::command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const
  936. {
  937. auto uploadJob = new SocketUploadJob(job);
  938. uploadJob->start();
  939. }
  940. void SocketApi::emailPrivateLink(const QString &link)
  941. {
  942. Utility::openEmailComposer(
  943. tr("I shared something with you"),
  944. link,
  945. nullptr);
  946. }
  947. void OCC::SocketApi::openPrivateLink(const QString &link)
  948. {
  949. Utility::openBrowser(link);
  950. }
  951. void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *listener)
  952. {
  953. static std::array<std::pair<const char *, QString>, 6> strings { {
  954. { "SHARE_MENU_TITLE", tr("Share options") },
  955. { "FILE_ACTIVITY_MENU_TITLE", tr("Activity") },
  956. { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() },
  957. { "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
  958. { "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") },
  959. { "CONTEXT_MENU_ICON", APPLICATION_ICON_NAME },
  960. } };
  961. listener->sendMessage(QString("GET_STRINGS:BEGIN"));
  962. for (const auto& key_value : strings) {
  963. if (argument.isEmpty() || argument == QLatin1String(key_value.first)) {
  964. listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second));
  965. }
  966. }
  967. listener->sendMessage(QString("GET_STRINGS:END"));
  968. }
  969. void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener, SharingContextItemEncryptedFlag itemEncryptionFlag, SharingContextItemRootEncryptedFolderFlag rootE2eeFolderFlag)
  970. {
  971. const auto record = fileData.journalRecord();
  972. const auto isOnTheServer = record.isValid();
  973. const auto isSecureFileDropSupported = rootE2eeFolderFlag == SharingContextItemRootEncryptedFolderFlag::RootEncryptedFolder && fileData.folder->accountState()->account()->secureFileDropSupported();
  974. const auto flagString = isOnTheServer && (itemEncryptionFlag == SharingContextItemEncryptedFlag::NotEncryptedItem || isSecureFileDropSupported) ? QLatin1String("::") : QLatin1String(":d:");
  975. auto capabilities = fileData.folder->accountState()->account()->capabilities();
  976. auto theme = Theme::instance();
  977. if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink())))
  978. return;
  979. if (record._isShared && !record._sharedByMe) {
  980. listener->sendMessage(QLatin1String("MENU_ITEM:LEAVESHARE") + flagString + tr("Leave this share"));
  981. }
  982. // If sharing is globally disabled, do not show any sharing entries.
  983. // If there is no permission to share for this file, add a disabled entry saying so
  984. if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
  985. listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + (!record.isDirectory() ? tr("Resharing this file is not allowed") : tr("Resharing this folder is not allowed")));
  986. } else {
  987. listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share options"));
  988. // Do we have public links?
  989. bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink();
  990. // Is is possible to create a public link without user choices?
  991. bool canCreateDefaultPublicLink = publicLinksEnabled
  992. && !capabilities.sharePublicLinkEnforceExpireDate()
  993. && !capabilities.sharePublicLinkAskOptionalPassword()
  994. && !capabilities.sharePublicLinkEnforcePassword();
  995. if (canCreateDefaultPublicLink) {
  996. if (isSecureFileDropSupported) {
  997. listener->sendMessage(QLatin1String("MENU_ITEM:COPY_SECUREFILEDROP_LINK") + QLatin1String("::") + tr("Copy secure filedrop link"));
  998. } else {
  999. listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Copy public link"));
  1000. }
  1001. } else if (publicLinksEnabled) {
  1002. if (isSecureFileDropSupported) {
  1003. listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + QLatin1String("::") + tr("Copy secure filedrop link"));
  1004. } else {
  1005. listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link"));
  1006. }
  1007. }
  1008. }
  1009. if (itemEncryptionFlag == SharingContextItemEncryptedFlag::NotEncryptedItem) {
  1010. listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy internal link"));
  1011. }
  1012. // Disabled: only providing email option for private links would look odd,
  1013. // and the copy option is more general.
  1014. //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email …"));
  1015. }
  1016. void SocketApi::sendEncryptFolderCommandMenuEntries(const QFileInfo &fileInfo,
  1017. const FileData &fileData,
  1018. const bool isE2eEncryptedPath,
  1019. const OCC::SocketListener* const listener) const
  1020. {
  1021. if (!listener ||
  1022. !fileData.folder ||
  1023. !fileData.folder->accountState() ||
  1024. !fileData.folder->accountState()->account() ||
  1025. !fileData.folder->accountState()->account()->capabilities().clientSideEncryptionAvailable() ||
  1026. !fileInfo.isDir() ||
  1027. isE2eEncryptedPath) {
  1028. return;
  1029. }
  1030. bool anyAncestorEncrypted = false;
  1031. auto ancestor = fileData.parentFolder();
  1032. while (ancestor.journalRecord().isValid()) {
  1033. if (ancestor.journalRecord()._isE2eEncrypted) {
  1034. anyAncestorEncrypted = true;
  1035. break;
  1036. }
  1037. ancestor = ancestor.parentFolder();
  1038. }
  1039. if (!anyAncestorEncrypted) {
  1040. const auto isOnTheServer = fileData.journalRecord().isValid();
  1041. const auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
  1042. listener->sendMessage(QStringLiteral("MENU_ITEM:ENCRYPT") + flagString + tr("Encrypt"));
  1043. }
  1044. }
  1045. void SocketApi::sendLockFileCommandMenuEntries(const QFileInfo &fileInfo,
  1046. Folder* const syncFolder,
  1047. const FileData &fileData,
  1048. const OCC::SocketListener* const listener) const
  1049. {
  1050. if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable()) {
  1051. if (syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::UnlockedItem) {
  1052. listener->sendMessage(QLatin1String("MENU_ITEM:LOCK_FILE::") + tr("Lock file"));
  1053. } else {
  1054. if (syncFolder->accountState()->account()->fileCanBeUnlocked(syncFolder->journalDb(), fileData.folderRelativePath)) {
  1055. listener->sendMessage(QLatin1String("MENU_ITEM:UNLOCK_FILE::") + tr("Unlock file"));
  1056. }
  1057. }
  1058. }
  1059. }
  1060. void SocketApi::sendLockFileInfoMenuEntries(const QFileInfo &fileInfo,
  1061. Folder * const syncFolder,
  1062. const FileData &fileData,
  1063. const SocketListener * const listener,
  1064. const SyncJournalFileRecord &record) const
  1065. {
  1066. static constexpr auto SECONDS_PER_MINUTE = 60;
  1067. if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable() &&
  1068. syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::LockedItem) {
  1069. listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_OWNER:d:") + tr("Locked by %1").arg(record._lockstate._lockOwnerDisplayName));
  1070. const auto lockExpirationTime = record._lockstate._lockTime + record._lockstate._lockTimeout;
  1071. const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
  1072. const auto remainingTimeInMinute = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
  1073. listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_DATE:d:") + tr("Expires in %1 minutes", "remaining time before lock expires", remainingTimeInMinute).arg(remainingTimeInMinute));
  1074. }
  1075. }
  1076. SocketApi::FileData SocketApi::FileData::get(const QString &localFile)
  1077. {
  1078. FileData data;
  1079. data.localPath = QDir::cleanPath(localFile);
  1080. if (data.localPath.endsWith(QLatin1Char('/')))
  1081. data.localPath.chop(1);
  1082. data.folder = FolderMan::instance()->folderForPath(data.localPath);
  1083. if (!data.folder)
  1084. return data;
  1085. data.folderRelativePath = data.localPath.mid(data.folder->cleanPath().length() + 1);
  1086. data.serverRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath);
  1087. QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
  1088. if (data.serverRelativePath.endsWith(virtualFileExt)) {
  1089. data.serverRelativePath.chop(virtualFileExt.size());
  1090. }
  1091. return data;
  1092. }
  1093. QString SocketApi::FileData::folderRelativePathNoVfsSuffix() const
  1094. {
  1095. auto result = folderRelativePath;
  1096. QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
  1097. if (result.endsWith(virtualFileExt)) {
  1098. result.chop(virtualFileExt.size());
  1099. }
  1100. return result;
  1101. }
  1102. SyncFileStatus SocketApi::FileData::syncFileStatus() const
  1103. {
  1104. if (!folder)
  1105. return SyncFileStatus::StatusNone;
  1106. return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath);
  1107. }
  1108. SyncJournalFileRecord SocketApi::FileData::journalRecord() const
  1109. {
  1110. SyncJournalFileRecord record;
  1111. if (!folder)
  1112. return record;
  1113. if (!folder->journalDb()->getFileRecord(folderRelativePath, &record)) {
  1114. qCWarning(lcSocketApi) << "Failed to get journal record for path" << folderRelativePath;
  1115. }
  1116. return record;
  1117. }
  1118. SocketApi::FileData SocketApi::FileData::parentFolder() const
  1119. {
  1120. return FileData::get(QFileInfo(localPath).dir().path().toUtf8());
  1121. }
  1122. void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener)
  1123. {
  1124. listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN"));
  1125. const QStringList files = split(argument);
  1126. // Find the common sync folder.
  1127. // syncFolder will be null if files are in different folders.
  1128. Folder *syncFolder = nullptr;
  1129. for (const auto &file : files) {
  1130. auto folder = FolderMan::instance()->folderForPath(file);
  1131. if (folder != syncFolder) {
  1132. if (!syncFolder) {
  1133. syncFolder = folder;
  1134. } else {
  1135. syncFolder = nullptr;
  1136. break;
  1137. }
  1138. }
  1139. }
  1140. // Sharing actions show for single files only
  1141. if (syncFolder && files.size() == 1 && syncFolder->accountState()->isConnected()) {
  1142. QString systemPath = QDir::cleanPath(argument);
  1143. if (systemPath.endsWith(QLatin1Char('/'))) {
  1144. systemPath.truncate(systemPath.length() - 1);
  1145. }
  1146. FileData fileData = FileData::get(argument);
  1147. const auto record = fileData.journalRecord();
  1148. const bool isOnTheServer = record.isValid();
  1149. const auto isE2eEncryptedPath = fileData.journalRecord()._isE2eEncrypted || !fileData.journalRecord()._e2eMangledName.isEmpty();
  1150. const auto isE2eEncryptedRootFolder = fileData.journalRecord()._isE2eEncrypted && fileData.journalRecord()._e2eMangledName.isEmpty();
  1151. auto flagString = isOnTheServer && !isE2eEncryptedPath ? QLatin1String("::") : QLatin1String(":d:");
  1152. const QFileInfo fileInfo(fileData.localPath);
  1153. sendLockFileInfoMenuEntries(fileInfo, syncFolder, fileData, listener, record);
  1154. if (!fileInfo.isDir()) {
  1155. listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity"));
  1156. }
  1157. DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
  1158. if (editor) {
  1159. //listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name());
  1160. listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit"));
  1161. } else {
  1162. listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
  1163. }
  1164. sendEncryptFolderCommandMenuEntries(fileInfo, fileData, isE2eEncryptedPath, listener);
  1165. sendLockFileCommandMenuEntries(fileInfo, syncFolder, fileData, listener);
  1166. const auto itemEncryptionFlag = isE2eEncryptedPath ? SharingContextItemEncryptedFlag::EncryptedItem : SharingContextItemEncryptedFlag::NotEncryptedItem;
  1167. const auto rootE2eeFolderFlag = isE2eEncryptedRootFolder ? SharingContextItemRootEncryptedFolderFlag::RootEncryptedFolder : SharingContextItemRootEncryptedFolderFlag::NonRootEncryptedFolder;
  1168. sendSharingContextMenuOptions(fileData, listener, itemEncryptionFlag, rootE2eeFolderFlag);
  1169. // Conflict files get conflict resolution actions
  1170. bool isConflict = Utility::isConflictFile(fileData.folderRelativePath);
  1171. if (isConflict || !isOnTheServer) {
  1172. // Check whether this new file is in a read-only directory
  1173. const auto parentDir = fileData.parentFolder();
  1174. const auto parentRecord = parentDir.journalRecord();
  1175. const bool canAddToDir =
  1176. !parentRecord.isValid() // We're likely at the root of the sync folder, got to assume we can add there
  1177. || (fileInfo.isFile() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile))
  1178. || (fileInfo.isDir() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories));
  1179. const bool canChangeFile =
  1180. !isOnTheServer
  1181. || (record._remotePerm.hasPermission(RemotePermissions::CanDelete)
  1182. && record._remotePerm.hasPermission(RemotePermissions::CanMove)
  1183. && record._remotePerm.hasPermission(RemotePermissions::CanRename));
  1184. if (isConflict && canChangeFile) {
  1185. if (canAddToDir) {
  1186. listener->sendMessage(QLatin1String("MENU_ITEM:RESOLVE_CONFLICT::") + tr("Resolve conflict …"));
  1187. } else {
  1188. if (isOnTheServer) {
  1189. // Uploaded conflict file in read-only directory
  1190. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename …"));
  1191. } else {
  1192. // Local-only conflict file in a read-only dir
  1193. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move, rename and upload …"));
  1194. }
  1195. listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete local changes"));
  1196. }
  1197. }
  1198. // File in a read-only directory?
  1199. if (!isConflict && !isOnTheServer && !canAddToDir) {
  1200. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and upload …"));
  1201. listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete"));
  1202. }
  1203. }
  1204. }
  1205. // File availability actions
  1206. if (syncFolder
  1207. && syncFolder->virtualFilesEnabled()
  1208. && syncFolder->vfs().socketApiPinStateActionsShown()) {
  1209. ENFORCE(!files.isEmpty());
  1210. // Determine the combined availability status of the files
  1211. auto combined = Optional<VfsItemAvailability>();
  1212. auto merge = [](VfsItemAvailability lhs, VfsItemAvailability rhs) {
  1213. if (lhs == rhs)
  1214. return lhs;
  1215. if (int(lhs) > int(rhs))
  1216. std::swap(lhs, rhs); // reduce cases ensuring lhs < rhs
  1217. if (lhs == VfsItemAvailability::AlwaysLocal && rhs == VfsItemAvailability::AllHydrated)
  1218. return VfsItemAvailability::AllHydrated;
  1219. if (lhs == VfsItemAvailability::AllDehydrated && rhs == VfsItemAvailability::OnlineOnly)
  1220. return VfsItemAvailability::AllDehydrated;
  1221. return VfsItemAvailability::Mixed;
  1222. };
  1223. for (const auto &file : files) {
  1224. auto fileData = FileData::get(file);
  1225. auto availability = syncFolder->vfs().availability(fileData.folderRelativePath);
  1226. if (!availability) {
  1227. if (availability.error() == Vfs::AvailabilityError::DbError)
  1228. availability = VfsItemAvailability::Mixed;
  1229. if (availability.error() == Vfs::AvailabilityError::NoSuchItem)
  1230. continue;
  1231. }
  1232. if (!combined) {
  1233. combined = *availability;
  1234. } else {
  1235. combined = merge(*combined, *availability);
  1236. }
  1237. }
  1238. // TODO: Should be a submenu, should use icons
  1239. auto makePinContextMenu = [&](bool makeAvailableLocally, bool freeSpace) {
  1240. listener->sendMessage(QLatin1String("MENU_ITEM:CURRENT_PIN:d:")
  1241. + Utility::vfsCurrentAvailabilityText(*combined));
  1242. if (!Theme::instance()->enforceVirtualFilesSyncFolder()) {
  1243. listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:")
  1244. + (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:")) + Utility::vfsPinActionText());
  1245. }
  1246. listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_ONLINE_ONLY:")
  1247. + (freeSpace ? QLatin1String(":") : QLatin1String("d:"))
  1248. + Utility::vfsFreeSpaceActionText());
  1249. };
  1250. if (combined) {
  1251. switch (*combined) {
  1252. case VfsItemAvailability::AlwaysLocal:
  1253. makePinContextMenu(false, true);
  1254. break;
  1255. case VfsItemAvailability::AllHydrated:
  1256. case VfsItemAvailability::Mixed:
  1257. makePinContextMenu(true, true);
  1258. break;
  1259. case VfsItemAvailability::AllDehydrated:
  1260. case VfsItemAvailability::OnlineOnly:
  1261. makePinContextMenu(true, false);
  1262. break;
  1263. }
  1264. }
  1265. }
  1266. listener->sendMessage(QString("GET_MENU_ITEMS:END"));
  1267. }
  1268. DirectEditor* SocketApi::getDirectEditorForLocalFile(const QString &localFile)
  1269. {
  1270. FileData fileData = FileData::get(localFile);
  1271. auto capabilities = fileData.folder->accountState()->account()->capabilities();
  1272. if (fileData.folder && fileData.folder->accountState()->isConnected()) {
  1273. const auto record = fileData.journalRecord();
  1274. const auto mimeMatchMode = record.isVirtualFile() ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
  1275. QMimeDatabase db;
  1276. QMimeType type = db.mimeTypeForFile(localFile, mimeMatchMode);
  1277. DirectEditor* editor = capabilities.getDirectEditorForMimetype(type);
  1278. if (!editor) {
  1279. editor = capabilities.getDirectEditorForOptionalMimetype(type);
  1280. }
  1281. return editor;
  1282. }
  1283. return nullptr;
  1284. }
  1285. #if GUI_TESTING
  1286. void SocketApi::command_ASYNC_LIST_WIDGETS(const QSharedPointer<SocketApiJob> &job)
  1287. {
  1288. QString response;
  1289. for (auto &widget : allObjects(QApplication::allWidgets())) {
  1290. auto objectName = widget->objectName();
  1291. if (!objectName.isEmpty()) {
  1292. response += objectName + ":" + widget->property("text").toString() + ", ";
  1293. }
  1294. }
  1295. job->resolve(response);
  1296. }
  1297. void SocketApi::command_ASYNC_INVOKE_WIDGET_METHOD(const QSharedPointer<SocketApiJob> &job)
  1298. {
  1299. auto &arguments = job->arguments();
  1300. auto widget = findWidget(arguments["objectName"].toString());
  1301. if (!widget) {
  1302. job->reject(QLatin1String("widget not found"));
  1303. return;
  1304. }
  1305. QMetaObject::invokeMethod(widget, arguments["method"].toString().toUtf8().constData());
  1306. job->resolve();
  1307. }
  1308. void SocketApi::command_ASYNC_GET_WIDGET_PROPERTY(const QSharedPointer<SocketApiJob> &job)
  1309. {
  1310. QString widgetName = job->arguments()[QLatin1String("objectName")].toString();
  1311. auto widget = findWidget(widgetName);
  1312. if (!widget) {
  1313. QString message = QString(QLatin1String("Widget not found: 2: %1")).arg(widgetName);
  1314. job->reject(message);
  1315. return;
  1316. }
  1317. auto propertyName = job->arguments()[QLatin1String("property")].toString();
  1318. auto segments = propertyName.split('.');
  1319. QObject *currentObject = widget;
  1320. QString value;
  1321. for (int i = 0; i < segments.count(); i++) {
  1322. auto segment = segments.at(i);
  1323. auto var = currentObject->property(segment.toUtf8().constData());
  1324. if (var.canConvert<QString>()) {
  1325. var.convert(QMetaType::QString);
  1326. value = var.value<QString>();
  1327. break;
  1328. }
  1329. auto tmpObject = var.value<QObject *>();
  1330. if (tmpObject) {
  1331. currentObject = tmpObject;
  1332. } else {
  1333. QString message = QString(QLatin1String("Widget not found: 3: %1")).arg(widgetName);
  1334. job->reject(message);
  1335. return;
  1336. }
  1337. }
  1338. job->resolve(value);
  1339. }
  1340. void SocketApi::command_ASYNC_SET_WIDGET_PROPERTY(const QSharedPointer<SocketApiJob> &job)
  1341. {
  1342. auto &arguments = job->arguments();
  1343. QString widgetName = arguments["objectName"].toString();
  1344. auto widget = findWidget(widgetName);
  1345. if (!widget) {
  1346. QString message = QString(QLatin1String("Widget not found: 4: %1")).arg(widgetName);
  1347. job->reject(message);
  1348. return;
  1349. }
  1350. widget->setProperty(arguments["property"].toString().toUtf8().constData(),
  1351. arguments["value"]);
  1352. job->resolve();
  1353. }
  1354. void SocketApi::command_ASYNC_WAIT_FOR_WIDGET_SIGNAL(const QSharedPointer<SocketApiJob> &job)
  1355. {
  1356. auto &arguments = job->arguments();
  1357. QString widgetName = arguments["objectName"].toString();
  1358. auto widget = findWidget(arguments["objectName"].toString());
  1359. if (!widget) {
  1360. QString message = QString(QLatin1String("Widget not found: 5: %1")).arg(widgetName);
  1361. job->reject(message);
  1362. return;
  1363. }
  1364. auto closure = new ListenerClosure([job]() { job->resolve("signal emitted"); });
  1365. auto signalSignature = arguments["signalSignature"].toString();
  1366. signalSignature.prepend("2");
  1367. auto utf8 = signalSignature.toUtf8();
  1368. auto signalSignatureFinal = utf8.constData();
  1369. connect(widget, signalSignatureFinal, closure, SLOT(closureSlot()), Qt::QueuedConnection);
  1370. }
  1371. void SocketApi::command_ASYNC_TRIGGER_MENU_ACTION(const QSharedPointer<SocketApiJob> &job)
  1372. {
  1373. auto &arguments = job->arguments();
  1374. auto objectName = arguments["objectName"].toString();
  1375. auto widget = findWidget(objectName);
  1376. if (!widget) {
  1377. QString message = QString(QLatin1String("Object not found: 1: %1")).arg(objectName);
  1378. job->reject(message);
  1379. return;
  1380. }
  1381. auto children = widget->findChildren<QWidget *>();
  1382. for (auto childWidget : children) {
  1383. // foo is the popupwidget!
  1384. auto actions = childWidget->actions();
  1385. for (auto action : actions) {
  1386. if (action->objectName() == arguments["actionName"].toString()) {
  1387. action->trigger();
  1388. job->resolve("action found");
  1389. return;
  1390. }
  1391. }
  1392. }
  1393. QString message = QString(QLatin1String("Action not found: 1: %1")).arg(arguments["actionName"].toString());
  1394. job->reject(message);
  1395. }
  1396. void SocketApi::command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer<SocketApiJob> &job)
  1397. {
  1398. auto widget = findWidget(job->arguments()[QLatin1String("queryString")].toString());
  1399. if (!widget) {
  1400. QString message = QString(QLatin1String("Object not found: 6: %1")).arg(job->arguments()["queryString"].toString());
  1401. job->reject(message);
  1402. return;
  1403. }
  1404. auto propertyName = job->arguments()[QLatin1String("propertyPath")].toString();
  1405. auto segments = propertyName.split('.');
  1406. QObject *currentObject = widget;
  1407. QIcon value;
  1408. for (int i = 0; i < segments.count(); i++) {
  1409. auto segment = segments.at(i);
  1410. auto var = currentObject->property(segment.toUtf8().constData());
  1411. if (var.canConvert<QIcon>()) {
  1412. var.convert(QMetaType::QIcon);
  1413. value = var.value<QIcon>();
  1414. break;
  1415. }
  1416. auto tmpObject = var.value<QObject *>();
  1417. if (tmpObject) {
  1418. currentObject = tmpObject;
  1419. } else {
  1420. job->reject(QString(QLatin1String("Icon not found: %1")).arg(propertyName));
  1421. }
  1422. }
  1423. auto iconName = job->arguments()[QLatin1String("iconName")].toString();
  1424. if (value.name() == iconName) {
  1425. job->resolve();
  1426. } else {
  1427. job->reject("iconName " + iconName + " does not match: " + value.name());
  1428. }
  1429. }
  1430. #endif
  1431. QString SocketApi::buildRegisterPathMessage(const QString &path)
  1432. {
  1433. QFileInfo fi(path);
  1434. QString message = QLatin1String("REGISTER_PATH:");
  1435. message.append(QDir::toNativeSeparators(fi.absoluteFilePath()));
  1436. return message;
  1437. }
  1438. void SocketApiJob::resolve(const QString &response)
  1439. {
  1440. _socketListener->sendMessage(QStringLiteral("RESOLVE|") + _jobId + QLatin1Char('|') + response);
  1441. }
  1442. void SocketApiJob::resolve(const QJsonObject &response)
  1443. {
  1444. resolve(QJsonDocument { response }.toJson());
  1445. }
  1446. void SocketApiJob::reject(const QString &response)
  1447. {
  1448. _socketListener->sendMessage(QStringLiteral("REJECT|") + _jobId + QLatin1Char('|') + response);
  1449. }
  1450. SocketApiJobV2::SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments)
  1451. : _socketListener(socketListener)
  1452. , _command(command)
  1453. , _jobId(arguments[QStringLiteral("id")].toString())
  1454. , _arguments(arguments[QStringLiteral("arguments")].toObject())
  1455. {
  1456. ASSERT(!_jobId.isEmpty())
  1457. }
  1458. void SocketApiJobV2::success(const QJsonObject &response) const
  1459. {
  1460. doFinish(response);
  1461. }
  1462. void SocketApiJobV2::failure(const QString &error) const
  1463. {
  1464. doFinish({ { QStringLiteral("error"), error } });
  1465. }
  1466. void SocketApiJobV2::doFinish(const QJsonObject &obj) const
  1467. {
  1468. _socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QJsonDocument({ { QStringLiteral("id"), _jobId }, { QStringLiteral("arguments"), obj } }).toJson(QJsonDocument::Compact));
  1469. Q_EMIT finished();
  1470. }
  1471. } // namespace OCC
  1472. #include "socketapi.moc"