socketapi.cpp 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690
  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. auto &remotePath = fileData.serverRelativePath;
  507. // Can't share root folder
  508. if (remotePath == "/") {
  509. const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
  510. listener->sendMessage(message);
  511. return;
  512. }
  513. const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
  514. listener->sendMessage(message);
  515. emit shareCommandReceived(fileData.localPath);
  516. }
  517. }
  518. void SocketApi::processLeaveShareRequest(const QString &localFile, SocketListener *listener)
  519. {
  520. Q_UNUSED(listener)
  521. FolderMan::instance()->leaveShare(QDir::fromNativeSeparators(localFile));
  522. }
  523. void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus)
  524. {
  525. QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
  526. Q_ASSERT(!systemPath.endsWith('/'));
  527. uint directoryHash = qHash(systemPath.left(systemPath.lastIndexOf('/')));
  528. for (const auto &listener : qAsConst(_listeners)) {
  529. listener->sendMessageIfDirectoryMonitored(msg, directoryHash);
  530. }
  531. }
  532. void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener)
  533. {
  534. // This command is the same as RETRIEVE_FILE_STATUS
  535. command_RETRIEVE_FILE_STATUS(argument, listener);
  536. }
  537. void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener)
  538. {
  539. QString statusString;
  540. auto fileData = FileData::get(argument);
  541. if (!fileData.folder) {
  542. // this can happen in offline mode e.g.: nothing to worry about
  543. statusString = QLatin1String("NOP");
  544. } else {
  545. // The user probably visited this directory in the file shell.
  546. // Let the listener know that it should now send status pushes for sibblings of this file.
  547. QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/'));
  548. listener->registerMonitoredDirectory(qHash(directory));
  549. SyncFileStatus fileStatus = fileData.syncFileStatus();
  550. statusString = fileStatus.toSocketAPIString();
  551. }
  552. const QString message = QLatin1String("STATUS:") % statusString % QLatin1Char(':') % QDir::toNativeSeparators(argument);
  553. listener->sendMessage(message);
  554. }
  555. void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
  556. {
  557. processShareRequest(localFile, listener);
  558. }
  559. void SocketApi::command_LEAVESHARE(const QString &localFile, SocketListener *listener)
  560. {
  561. processLeaveShareRequest(localFile, listener);
  562. }
  563. void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener)
  564. {
  565. Q_UNUSED(listener);
  566. processFileActivityRequest(localFile);
  567. }
  568. void SocketApi::command_ENCRYPT(const QString &localFile, SocketListener *listener)
  569. {
  570. Q_UNUSED(listener);
  571. processEncryptRequest(localFile);
  572. }
  573. void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
  574. {
  575. processShareRequest(localFile, listener);
  576. }
  577. void SocketApi::command_VERSION(const QString &, SocketListener *listener)
  578. {
  579. listener->sendMessage(QLatin1String("VERSION:" MIRALL_VERSION_STRING ":" MIRALL_SOCKET_API_VERSION));
  580. }
  581. void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
  582. {
  583. //listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
  584. listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + Theme::instance()->appNameGUI());
  585. }
  586. void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
  587. {
  588. Q_UNUSED(listener)
  589. auto fileData = FileData::get(localFile);
  590. if (!fileData.folder) {
  591. qCWarning(lcSocketApi) << "Unknown path" << localFile;
  592. return;
  593. }
  594. auto record = fileData.journalRecord();
  595. if (!record.isValid())
  596. return;
  597. DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
  598. if (!editor)
  599. return;
  600. auto *job = new JsonApiJob(fileData.folder->accountState()->account(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing/open"), this);
  601. QUrlQuery params;
  602. params.addQueryItem("path", fileData.serverRelativePath);
  603. params.addQueryItem("editorId", editor->id());
  604. job->addQueryParams(params);
  605. job->setVerb(JsonApiJob::Verb::Post);
  606. QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
  607. auto data = json.object().value("ocs").toObject().value("data").toObject();
  608. auto url = QUrl(data.value("url").toString());
  609. if(!url.isEmpty())
  610. Utility::openBrowser(url);
  611. });
  612. job->start();
  613. }
  614. // don't pull the share manager into socketapi unittests
  615. #ifndef OWNCLOUD_TEST
  616. class GetOrCreatePublicLinkShare : public QObject
  617. {
  618. Q_OBJECT
  619. public:
  620. GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile,
  621. QObject *parent)
  622. : QObject(parent)
  623. , _account(account)
  624. , _shareManager(account)
  625. , _localFile(localFile)
  626. {
  627. connect(&_shareManager, &ShareManager::sharesFetched,
  628. this, &GetOrCreatePublicLinkShare::sharesFetched);
  629. connect(&_shareManager, &ShareManager::linkShareCreated,
  630. this, &GetOrCreatePublicLinkShare::linkShareCreated);
  631. connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
  632. this, &GetOrCreatePublicLinkShare::linkShareRequiresPassword);
  633. connect(&_shareManager, &ShareManager::serverError,
  634. this, &GetOrCreatePublicLinkShare::serverError);
  635. }
  636. void run()
  637. {
  638. qCDebug(lcPublicLink) << "Fetching shares";
  639. _shareManager.fetchShares(_localFile);
  640. }
  641. private slots:
  642. void sharesFetched(const QList<OCC::SharePtr> &shares)
  643. {
  644. auto shareName = SocketApi::tr("Context menu share");
  645. // If there already is a context menu share, reuse it
  646. for (const auto &share : shares) {
  647. const auto linkShare = qSharedPointerDynamicCast<LinkShare>(share);
  648. if (!linkShare)
  649. continue;
  650. if (linkShare->getName() == shareName) {
  651. qCDebug(lcPublicLink) << "Found existing share, reusing";
  652. return success(linkShare->getLink().toString());
  653. }
  654. }
  655. // otherwise create a new one
  656. qCDebug(lcPublicLink) << "Creating new share";
  657. _shareManager.createLinkShare(_localFile, shareName, QString());
  658. }
  659. void linkShareCreated(const QSharedPointer<OCC::LinkShare> &share)
  660. {
  661. qCDebug(lcPublicLink) << "New share created";
  662. success(share->getLink().toString());
  663. }
  664. void passwordRequired() {
  665. bool ok = false;
  666. QString password = QInputDialog::getText(nullptr,
  667. tr("Password for share required"),
  668. tr("Please enter a password for your link share:"),
  669. QLineEdit::Normal,
  670. QString(),
  671. &ok);
  672. if (!ok) {
  673. // The dialog was canceled so no need to do anything
  674. return;
  675. }
  676. // Try to create the link share again with the newly entered password
  677. _shareManager.createLinkShare(_localFile, QString(), password);
  678. }
  679. void linkShareRequiresPassword(const QString &message)
  680. {
  681. qCInfo(lcPublicLink) << "Could not create link share:" << message;
  682. emit error(message);
  683. deleteLater();
  684. }
  685. void serverError(int code, const QString &message)
  686. {
  687. qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
  688. QMessageBox::warning(
  689. nullptr,
  690. tr("Sharing error"),
  691. tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message),
  692. QMessageBox::Ok,
  693. QMessageBox::NoButton);
  694. emit error(message);
  695. deleteLater();
  696. }
  697. signals:
  698. void done(const QString &link);
  699. void error(const QString &message);
  700. private:
  701. void success(const QString &link)
  702. {
  703. emit done(link);
  704. deleteLater();
  705. }
  706. AccountPtr _account;
  707. ShareManager _shareManager;
  708. QString _localFile;
  709. };
  710. #else
  711. class GetOrCreatePublicLinkShare : public QObject
  712. {
  713. Q_OBJECT
  714. public:
  715. GetOrCreatePublicLinkShare(const AccountPtr &, const QString &,
  716. std::function<void(const QString &link)>, QObject *)
  717. {
  718. }
  719. void run()
  720. {
  721. }
  722. };
  723. #endif
  724. void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *)
  725. {
  726. auto fileData = FileData::get(localFile);
  727. if (!fileData.folder)
  728. return;
  729. AccountPtr account = fileData.folder->accountState()->account();
  730. auto job = new GetOrCreatePublicLinkShare(account, fileData.serverRelativePath, this);
  731. connect(job, &GetOrCreatePublicLinkShare::done, this,
  732. [](const QString &url) { copyUrlToClipboard(url); });
  733. connect(job, &GetOrCreatePublicLinkShare::error, this,
  734. [=]() { emit shareCommandReceived(fileData.localPath); });
  735. job->run();
  736. }
  737. // Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
  738. #ifdef Q_OS_WIN
  739. void SocketApi::command_COPYASPATH(const QString &localFile, SocketListener *)
  740. {
  741. QApplication::clipboard()->setText(localFile);
  742. }
  743. void SocketApi::command_OPENNEWWINDOW(const QString &localFile, SocketListener *)
  744. {
  745. QDesktopServices::openUrl(QUrl::fromLocalFile(localFile));
  746. }
  747. void SocketApi::command_OPEN(const QString &localFile, SocketListener *socketListener)
  748. {
  749. command_OPENNEWWINDOW(localFile, socketListener);
  750. }
  751. #endif
  752. // Fetches the private link url asynchronously and then calls the target slot
  753. void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
  754. {
  755. auto fileData = FileData::get(localFile);
  756. if (!fileData.folder) {
  757. qCWarning(lcSocketApi) << "Unknown path" << localFile;
  758. return;
  759. }
  760. auto record = fileData.journalRecord();
  761. if (!record.isValid())
  762. return;
  763. fetchPrivateLinkUrl(
  764. fileData.folder->accountState()->account(),
  765. fileData.serverRelativePath,
  766. record.numericFileId(),
  767. this,
  768. targetFun);
  769. }
  770. void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
  771. {
  772. fetchPrivateLinkUrlHelper(localFile, &SocketApi::copyUrlToClipboard);
  773. }
  774. void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
  775. {
  776. fetchPrivateLinkUrlHelper(localFile, &SocketApi::emailPrivateLink);
  777. }
  778. void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *)
  779. {
  780. fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink);
  781. }
  782. void SocketApi::command_MAKE_AVAILABLE_LOCALLY(const QString &filesArg, SocketListener *)
  783. {
  784. const QStringList files = split(filesArg);
  785. for (const auto &file : files) {
  786. auto data = FileData::get(file);
  787. if (!data.folder)
  788. continue;
  789. // Update the pin state on all items
  790. if (!data.folder->vfs().setPinState(data.folderRelativePath, PinState::AlwaysLocal)) {
  791. qCWarning(lcSocketApi) << "Could not set pin state of" << data.folderRelativePath << "to always local";
  792. }
  793. // Trigger sync
  794. data.folder->schedulePathForLocalDiscovery(data.folderRelativePath);
  795. data.folder->scheduleThisFolderSoon();
  796. }
  797. }
  798. /* Go over all the files and replace them by a virtual file */
  799. void SocketApi::command_MAKE_ONLINE_ONLY(const QString &filesArg, SocketListener *)
  800. {
  801. const QStringList files = split(filesArg);
  802. for (const auto &file : files) {
  803. auto data = FileData::get(file);
  804. if (!data.folder)
  805. continue;
  806. // Update the pin state on all items
  807. if (!data.folder->vfs().setPinState(data.folderRelativePath, PinState::OnlineOnly)) {
  808. qCWarning(lcSocketApi) << "Could not set pin state of" << data.folderRelativePath << "to online only";
  809. }
  810. // Trigger sync
  811. data.folder->schedulePathForLocalDiscovery(data.folderRelativePath);
  812. data.folder->scheduleThisFolderSoon();
  813. }
  814. }
  815. void SocketApi::copyUrlToClipboard(const QString &link)
  816. {
  817. QApplication::clipboard()->setText(link);
  818. }
  819. void SocketApi::command_RESOLVE_CONFLICT(const QString &localFile, SocketListener *)
  820. {
  821. const auto fileData = FileData::get(localFile);
  822. if (!fileData.folder || !Utility::isConflictFile(fileData.folderRelativePath))
  823. return; // should not have shown menu item
  824. const auto conflictedRelativePath = fileData.folderRelativePath;
  825. const auto baseRelativePath = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8());
  826. const auto dir = QDir(fileData.folder->path());
  827. const auto conflictedPath = dir.filePath(conflictedRelativePath);
  828. const auto basePath = dir.filePath(baseRelativePath);
  829. const auto baseName = QFileInfo(basePath).fileName();
  830. #ifndef OWNCLOUD_TEST
  831. ConflictDialog dialog;
  832. dialog.setBaseFilename(baseName);
  833. dialog.setLocalVersionFilename(conflictedPath);
  834. dialog.setRemoteVersionFilename(basePath);
  835. if (dialog.exec() == ConflictDialog::Accepted) {
  836. fileData.folder->scheduleThisFolderSoon();
  837. }
  838. #endif
  839. }
  840. void SocketApi::command_DELETE_ITEM(const QString &localFile, SocketListener *)
  841. {
  842. ConflictSolver solver;
  843. solver.setLocalVersionFilename(localFile);
  844. solver.exec(ConflictSolver::KeepRemoteVersion);
  845. }
  846. void SocketApi::command_MOVE_ITEM(const QString &localFile, SocketListener *)
  847. {
  848. const auto fileData = FileData::get(localFile);
  849. const auto parentDir = fileData.parentFolder();
  850. if (!fileData.folder)
  851. return; // should not have shown menu item
  852. QString defaultDirAndName = fileData.folderRelativePath;
  853. // If it's a conflict, we want to save it under the base name by default
  854. if (Utility::isConflictFile(defaultDirAndName)) {
  855. defaultDirAndName = fileData.folder->journalDb()->conflictFileBaseName(fileData.folderRelativePath.toUtf8());
  856. }
  857. // If the parent doesn't accept new files, go to the root of the sync folder
  858. QFileInfo fileInfo(localFile);
  859. const auto parentRecord = parentDir.journalRecord();
  860. if ((fileInfo.isFile() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile))
  861. || (fileInfo.isDir() && !parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories))) {
  862. defaultDirAndName = QFileInfo(defaultDirAndName).fileName();
  863. }
  864. // Add back the folder path
  865. defaultDirAndName = QDir(fileData.folder->path()).filePath(defaultDirAndName);
  866. const auto target = QFileDialog::getSaveFileName(
  867. nullptr,
  868. tr("Select new location …"),
  869. defaultDirAndName,
  870. QString(), nullptr, QFileDialog::HideNameFilterDetails);
  871. if (target.isEmpty())
  872. return;
  873. ConflictSolver solver;
  874. solver.setLocalVersionFilename(localFile);
  875. solver.setRemoteVersionFilename(target);
  876. }
  877. void SocketApi::command_LOCK_FILE(const QString &localFile, SocketListener *listener)
  878. {
  879. Q_UNUSED(listener)
  880. setFileLock(localFile, SyncFileItem::LockStatus::LockedItem);
  881. }
  882. void SocketApi::command_UNLOCK_FILE(const QString &localFile, SocketListener *listener)
  883. {
  884. Q_UNUSED(listener)
  885. setFileLock(localFile, SyncFileItem::LockStatus::UnlockedItem);
  886. }
  887. void SocketApi::setFileLock(const QString &localFile, const SyncFileItem::LockStatus lockState) const
  888. {
  889. const auto fileData = FileData::get(localFile);
  890. const auto shareFolder = fileData.folder;
  891. if (!shareFolder || !shareFolder->accountState()->isConnected()) {
  892. return;
  893. }
  894. shareFolder->accountState()->account()->setLockFileState(fileData.serverRelativePath, shareFolder->journalDb(), lockState);
  895. shareFolder->journalDb()->schedulePathForRemoteDiscovery(fileData.serverRelativePath);
  896. shareFolder->scheduleThisFolderSoon();
  897. }
  898. void SocketApi::command_V2_LIST_ACCOUNTS(const QSharedPointer<SocketApiJobV2> &job) const
  899. {
  900. QJsonArray out;
  901. const auto accounts = AccountManager::instance()->accounts();
  902. for (auto acc : accounts) {
  903. // TODO: Use uuid once https://github.com/owncloud/client/pull/8397 is merged
  904. out << QJsonObject({ { "name", acc->account()->displayName() }, { "id", acc->account()->id() } });
  905. }
  906. job->success({ { "accounts", out } });
  907. }
  908. void SocketApi::command_V2_UPLOAD_FILES_FROM(const QSharedPointer<SocketApiJobV2> &job) const
  909. {
  910. auto uploadJob = new SocketUploadJob(job);
  911. uploadJob->start();
  912. }
  913. void SocketApi::emailPrivateLink(const QString &link)
  914. {
  915. Utility::openEmailComposer(
  916. tr("I shared something with you"),
  917. link,
  918. nullptr);
  919. }
  920. void OCC::SocketApi::openPrivateLink(const QString &link)
  921. {
  922. Utility::openBrowser(link);
  923. }
  924. void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *listener)
  925. {
  926. static std::array<std::pair<const char *, QString>, 6> strings { {
  927. { "SHARE_MENU_TITLE", tr("Share options") },
  928. { "FILE_ACTIVITY_MENU_TITLE", tr("Activity") },
  929. { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() },
  930. { "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
  931. { "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") },
  932. { "CONTEXT_MENU_ICON", APPLICATION_ICON_NAME },
  933. } };
  934. listener->sendMessage(QString("GET_STRINGS:BEGIN"));
  935. for (const auto& key_value : strings) {
  936. if (argument.isEmpty() || argument == QLatin1String(key_value.first)) {
  937. listener->sendMessage(QString("STRING:%1:%2").arg(key_value.first, key_value.second));
  938. }
  939. }
  940. listener->sendMessage(QString("GET_STRINGS:END"));
  941. }
  942. void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener, bool enabled)
  943. {
  944. auto record = fileData.journalRecord();
  945. bool isOnTheServer = record.isValid();
  946. auto flagString = isOnTheServer && enabled ? QLatin1String("::") : QLatin1String(":d:");
  947. auto capabilities = fileData.folder->accountState()->account()->capabilities();
  948. auto theme = Theme::instance();
  949. if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink())))
  950. return;
  951. if (record._isShared && !record._sharedByMe) {
  952. listener->sendMessage(QLatin1String("MENU_ITEM:LEAVESHARE") + flagString + tr("Leave this share"));
  953. }
  954. // If sharing is globally disabled, do not show any sharing entries.
  955. // If there is no permission to share for this file, add a disabled entry saying so
  956. if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
  957. listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + (!record.isDirectory() ? tr("Resharing this file is not allowed") : tr("Resharing this folder is not allowed")));
  958. } else {
  959. listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share options"));
  960. // Do we have public links?
  961. bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink();
  962. // Is is possible to create a public link without user choices?
  963. bool canCreateDefaultPublicLink = publicLinksEnabled
  964. && !capabilities.sharePublicLinkEnforceExpireDate()
  965. && !capabilities.sharePublicLinkAskOptionalPassword()
  966. && !capabilities.sharePublicLinkEnforcePassword();
  967. if (canCreateDefaultPublicLink) {
  968. listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Copy public link"));
  969. } else if (publicLinksEnabled) {
  970. listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link"));
  971. }
  972. }
  973. listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy internal link"));
  974. // Disabled: only providing email option for private links would look odd,
  975. // and the copy option is more general.
  976. //listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email …"));
  977. }
  978. void SocketApi::sendEncryptFolderCommandMenuEntries(const QFileInfo &fileInfo,
  979. const FileData &fileData,
  980. const bool isE2eEncryptedPath,
  981. const OCC::SocketListener* const listener) const
  982. {
  983. if (!listener ||
  984. !fileData.folder ||
  985. !fileData.folder->accountState() ||
  986. !fileData.folder->accountState()->account() ||
  987. !fileData.folder->accountState()->account()->capabilities().clientSideEncryptionAvailable() ||
  988. !fileInfo.isDir() ||
  989. isE2eEncryptedPath) {
  990. return;
  991. }
  992. bool anyAncestorEncrypted = false;
  993. auto ancestor = fileData.parentFolder();
  994. while (ancestor.journalRecord().isValid()) {
  995. if (ancestor.journalRecord()._isE2eEncrypted) {
  996. anyAncestorEncrypted = true;
  997. break;
  998. }
  999. ancestor = ancestor.parentFolder();
  1000. }
  1001. if (!anyAncestorEncrypted) {
  1002. const auto isOnTheServer = fileData.journalRecord().isValid();
  1003. const auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
  1004. listener->sendMessage(QStringLiteral("MENU_ITEM:ENCRYPT") + flagString + tr("Encrypt"));
  1005. }
  1006. }
  1007. void SocketApi::sendLockFileCommandMenuEntries(const QFileInfo &fileInfo,
  1008. Folder* const syncFolder,
  1009. const FileData &fileData,
  1010. const OCC::SocketListener* const listener) const
  1011. {
  1012. if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable()) {
  1013. if (syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::UnlockedItem) {
  1014. listener->sendMessage(QLatin1String("MENU_ITEM:LOCK_FILE::") + tr("Lock file"));
  1015. } else {
  1016. if (syncFolder->accountState()->account()->fileCanBeUnlocked(syncFolder->journalDb(), fileData.folderRelativePath)) {
  1017. listener->sendMessage(QLatin1String("MENU_ITEM:UNLOCK_FILE::") + tr("Unlock file"));
  1018. }
  1019. }
  1020. }
  1021. }
  1022. void SocketApi::sendLockFileInfoMenuEntries(const QFileInfo &fileInfo,
  1023. Folder * const syncFolder,
  1024. const FileData &fileData,
  1025. const SocketListener * const listener,
  1026. const SyncJournalFileRecord &record) const
  1027. {
  1028. static constexpr auto SECONDS_PER_MINUTE = 60;
  1029. if (!fileInfo.isDir() && syncFolder->accountState()->account()->capabilities().filesLockAvailable() &&
  1030. syncFolder->accountState()->account()->fileLockStatus(syncFolder->journalDb(), fileData.folderRelativePath) == SyncFileItem::LockStatus::LockedItem) {
  1031. listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_OWNER:d:") + tr("Locked by %1").arg(record._lockstate._lockOwnerDisplayName));
  1032. const auto lockExpirationTime = record._lockstate._lockTime + record._lockstate._lockTimeout;
  1033. const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
  1034. const auto remainingTimeInMinute = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
  1035. listener->sendMessage(QLatin1String("MENU_ITEM:LOCKED_FILE_DATE:d:") + tr("Expires in %1 minutes", "remaining time before lock expires", remainingTimeInMinute).arg(remainingTimeInMinute));
  1036. }
  1037. }
  1038. SocketApi::FileData SocketApi::FileData::get(const QString &localFile)
  1039. {
  1040. FileData data;
  1041. data.localPath = QDir::cleanPath(localFile);
  1042. if (data.localPath.endsWith(QLatin1Char('/')))
  1043. data.localPath.chop(1);
  1044. data.folder = FolderMan::instance()->folderForPath(data.localPath);
  1045. if (!data.folder)
  1046. return data;
  1047. data.folderRelativePath = data.localPath.mid(data.folder->cleanPath().length() + 1);
  1048. data.serverRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath);
  1049. QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
  1050. if (data.serverRelativePath.endsWith(virtualFileExt)) {
  1051. data.serverRelativePath.chop(virtualFileExt.size());
  1052. }
  1053. return data;
  1054. }
  1055. QString SocketApi::FileData::folderRelativePathNoVfsSuffix() const
  1056. {
  1057. auto result = folderRelativePath;
  1058. QString virtualFileExt = QStringLiteral(APPLICATION_DOTVIRTUALFILE_SUFFIX);
  1059. if (result.endsWith(virtualFileExt)) {
  1060. result.chop(virtualFileExt.size());
  1061. }
  1062. return result;
  1063. }
  1064. SyncFileStatus SocketApi::FileData::syncFileStatus() const
  1065. {
  1066. if (!folder)
  1067. return SyncFileStatus::StatusNone;
  1068. return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath);
  1069. }
  1070. SyncJournalFileRecord SocketApi::FileData::journalRecord() const
  1071. {
  1072. SyncJournalFileRecord record;
  1073. if (!folder)
  1074. return record;
  1075. if (!folder->journalDb()->getFileRecord(folderRelativePath, &record)) {
  1076. qCWarning(lcSocketApi) << "Failed to get journal record for path" << folderRelativePath;
  1077. }
  1078. return record;
  1079. }
  1080. SocketApi::FileData SocketApi::FileData::parentFolder() const
  1081. {
  1082. return FileData::get(QFileInfo(localPath).dir().path().toUtf8());
  1083. }
  1084. void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener)
  1085. {
  1086. listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN"));
  1087. const QStringList files = split(argument);
  1088. // Find the common sync folder.
  1089. // syncFolder will be null if files are in different folders.
  1090. Folder *syncFolder = nullptr;
  1091. for (const auto &file : files) {
  1092. auto folder = FolderMan::instance()->folderForPath(file);
  1093. if (folder != syncFolder) {
  1094. if (!syncFolder) {
  1095. syncFolder = folder;
  1096. } else {
  1097. syncFolder = nullptr;
  1098. break;
  1099. }
  1100. }
  1101. }
  1102. // Sharing actions show for single files only
  1103. if (syncFolder && files.size() == 1 && syncFolder->accountState()->isConnected()) {
  1104. QString systemPath = QDir::cleanPath(argument);
  1105. if (systemPath.endsWith(QLatin1Char('/'))) {
  1106. systemPath.truncate(systemPath.length() - 1);
  1107. }
  1108. FileData fileData = FileData::get(argument);
  1109. const auto record = fileData.journalRecord();
  1110. const bool isOnTheServer = record.isValid();
  1111. const auto isE2eEncryptedPath = fileData.journalRecord()._isE2eEncrypted || !fileData.journalRecord()._e2eMangledName.isEmpty();
  1112. auto flagString = isOnTheServer && !isE2eEncryptedPath ? QLatin1String("::") : QLatin1String(":d:");
  1113. const QFileInfo fileInfo(fileData.localPath);
  1114. sendLockFileInfoMenuEntries(fileInfo, syncFolder, fileData, listener, record);
  1115. if (!fileInfo.isDir()) {
  1116. listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity"));
  1117. }
  1118. DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
  1119. if (editor) {
  1120. //listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name());
  1121. listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit"));
  1122. } else {
  1123. listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
  1124. }
  1125. sendEncryptFolderCommandMenuEntries(fileInfo, fileData, isE2eEncryptedPath, listener);
  1126. sendLockFileCommandMenuEntries(fileInfo, syncFolder, fileData, listener);
  1127. sendSharingContextMenuOptions(fileData, listener, !isE2eEncryptedPath);
  1128. // Conflict files get conflict resolution actions
  1129. bool isConflict = Utility::isConflictFile(fileData.folderRelativePath);
  1130. if (isConflict || !isOnTheServer) {
  1131. // Check whether this new file is in a read-only directory
  1132. const auto parentDir = fileData.parentFolder();
  1133. const auto parentRecord = parentDir.journalRecord();
  1134. const bool canAddToDir =
  1135. !parentRecord.isValid() // We're likely at the root of the sync folder, got to assume we can add there
  1136. || (fileInfo.isFile() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddFile))
  1137. || (fileInfo.isDir() && parentRecord._remotePerm.hasPermission(RemotePermissions::CanAddSubDirectories));
  1138. const bool canChangeFile =
  1139. !isOnTheServer
  1140. || (record._remotePerm.hasPermission(RemotePermissions::CanDelete)
  1141. && record._remotePerm.hasPermission(RemotePermissions::CanMove)
  1142. && record._remotePerm.hasPermission(RemotePermissions::CanRename));
  1143. if (isConflict && canChangeFile) {
  1144. if (canAddToDir) {
  1145. listener->sendMessage(QLatin1String("MENU_ITEM:RESOLVE_CONFLICT::") + tr("Resolve conflict …"));
  1146. } else {
  1147. if (isOnTheServer) {
  1148. // Uploaded conflict file in read-only directory
  1149. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and rename …"));
  1150. } else {
  1151. // Local-only conflict file in a read-only dir
  1152. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move, rename and upload …"));
  1153. }
  1154. listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete local changes"));
  1155. }
  1156. }
  1157. // File in a read-only directory?
  1158. if (!isConflict && !isOnTheServer && !canAddToDir) {
  1159. listener->sendMessage(QLatin1String("MENU_ITEM:MOVE_ITEM::") + tr("Move and upload …"));
  1160. listener->sendMessage(QLatin1String("MENU_ITEM:DELETE_ITEM::") + tr("Delete"));
  1161. }
  1162. }
  1163. }
  1164. // File availability actions
  1165. if (syncFolder
  1166. && syncFolder->virtualFilesEnabled()
  1167. && syncFolder->vfs().socketApiPinStateActionsShown()) {
  1168. ENFORCE(!files.isEmpty());
  1169. // Determine the combined availability status of the files
  1170. auto combined = Optional<VfsItemAvailability>();
  1171. auto merge = [](VfsItemAvailability lhs, VfsItemAvailability rhs) {
  1172. if (lhs == rhs)
  1173. return lhs;
  1174. if (int(lhs) > int(rhs))
  1175. std::swap(lhs, rhs); // reduce cases ensuring lhs < rhs
  1176. if (lhs == VfsItemAvailability::AlwaysLocal && rhs == VfsItemAvailability::AllHydrated)
  1177. return VfsItemAvailability::AllHydrated;
  1178. if (lhs == VfsItemAvailability::AllDehydrated && rhs == VfsItemAvailability::OnlineOnly)
  1179. return VfsItemAvailability::AllDehydrated;
  1180. return VfsItemAvailability::Mixed;
  1181. };
  1182. for (const auto &file : files) {
  1183. auto fileData = FileData::get(file);
  1184. auto availability = syncFolder->vfs().availability(fileData.folderRelativePath);
  1185. if (!availability) {
  1186. if (availability.error() == Vfs::AvailabilityError::DbError)
  1187. availability = VfsItemAvailability::Mixed;
  1188. if (availability.error() == Vfs::AvailabilityError::NoSuchItem)
  1189. continue;
  1190. }
  1191. if (!combined) {
  1192. combined = *availability;
  1193. } else {
  1194. combined = merge(*combined, *availability);
  1195. }
  1196. }
  1197. // TODO: Should be a submenu, should use icons
  1198. auto makePinContextMenu = [&](bool makeAvailableLocally, bool freeSpace) {
  1199. listener->sendMessage(QLatin1String("MENU_ITEM:CURRENT_PIN:d:")
  1200. + Utility::vfsCurrentAvailabilityText(*combined));
  1201. if (!Theme::instance()->enforceVirtualFilesSyncFolder()) {
  1202. listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:")
  1203. + (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:")) + Utility::vfsPinActionText());
  1204. }
  1205. listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_ONLINE_ONLY:")
  1206. + (freeSpace ? QLatin1String(":") : QLatin1String("d:"))
  1207. + Utility::vfsFreeSpaceActionText());
  1208. };
  1209. if (combined) {
  1210. switch (*combined) {
  1211. case VfsItemAvailability::AlwaysLocal:
  1212. makePinContextMenu(false, true);
  1213. break;
  1214. case VfsItemAvailability::AllHydrated:
  1215. case VfsItemAvailability::Mixed:
  1216. makePinContextMenu(true, true);
  1217. break;
  1218. case VfsItemAvailability::AllDehydrated:
  1219. case VfsItemAvailability::OnlineOnly:
  1220. makePinContextMenu(true, false);
  1221. break;
  1222. }
  1223. }
  1224. }
  1225. listener->sendMessage(QString("GET_MENU_ITEMS:END"));
  1226. }
  1227. DirectEditor* SocketApi::getDirectEditorForLocalFile(const QString &localFile)
  1228. {
  1229. FileData fileData = FileData::get(localFile);
  1230. auto capabilities = fileData.folder->accountState()->account()->capabilities();
  1231. if (fileData.folder && fileData.folder->accountState()->isConnected()) {
  1232. const auto record = fileData.journalRecord();
  1233. const auto mimeMatchMode = record.isVirtualFile() ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault;
  1234. QMimeDatabase db;
  1235. QMimeType type = db.mimeTypeForFile(localFile, mimeMatchMode);
  1236. DirectEditor* editor = capabilities.getDirectEditorForMimetype(type);
  1237. if (!editor) {
  1238. editor = capabilities.getDirectEditorForOptionalMimetype(type);
  1239. }
  1240. return editor;
  1241. }
  1242. return nullptr;
  1243. }
  1244. #if GUI_TESTING
  1245. void SocketApi::command_ASYNC_LIST_WIDGETS(const QSharedPointer<SocketApiJob> &job)
  1246. {
  1247. QString response;
  1248. for (auto &widget : allObjects(QApplication::allWidgets())) {
  1249. auto objectName = widget->objectName();
  1250. if (!objectName.isEmpty()) {
  1251. response += objectName + ":" + widget->property("text").toString() + ", ";
  1252. }
  1253. }
  1254. job->resolve(response);
  1255. }
  1256. void SocketApi::command_ASYNC_INVOKE_WIDGET_METHOD(const QSharedPointer<SocketApiJob> &job)
  1257. {
  1258. auto &arguments = job->arguments();
  1259. auto widget = findWidget(arguments["objectName"].toString());
  1260. if (!widget) {
  1261. job->reject(QLatin1String("widget not found"));
  1262. return;
  1263. }
  1264. QMetaObject::invokeMethod(widget, arguments["method"].toString().toUtf8().constData());
  1265. job->resolve();
  1266. }
  1267. void SocketApi::command_ASYNC_GET_WIDGET_PROPERTY(const QSharedPointer<SocketApiJob> &job)
  1268. {
  1269. QString widgetName = job->arguments()[QLatin1String("objectName")].toString();
  1270. auto widget = findWidget(widgetName);
  1271. if (!widget) {
  1272. QString message = QString(QLatin1String("Widget not found: 2: %1")).arg(widgetName);
  1273. job->reject(message);
  1274. return;
  1275. }
  1276. auto propertyName = job->arguments()[QLatin1String("property")].toString();
  1277. auto segments = propertyName.split('.');
  1278. QObject *currentObject = widget;
  1279. QString value;
  1280. for (int i = 0; i < segments.count(); i++) {
  1281. auto segment = segments.at(i);
  1282. auto var = currentObject->property(segment.toUtf8().constData());
  1283. if (var.canConvert<QString>()) {
  1284. var.convert(QMetaType::QString);
  1285. value = var.value<QString>();
  1286. break;
  1287. }
  1288. auto tmpObject = var.value<QObject *>();
  1289. if (tmpObject) {
  1290. currentObject = tmpObject;
  1291. } else {
  1292. QString message = QString(QLatin1String("Widget not found: 3: %1")).arg(widgetName);
  1293. job->reject(message);
  1294. return;
  1295. }
  1296. }
  1297. job->resolve(value);
  1298. }
  1299. void SocketApi::command_ASYNC_SET_WIDGET_PROPERTY(const QSharedPointer<SocketApiJob> &job)
  1300. {
  1301. auto &arguments = job->arguments();
  1302. QString widgetName = arguments["objectName"].toString();
  1303. auto widget = findWidget(widgetName);
  1304. if (!widget) {
  1305. QString message = QString(QLatin1String("Widget not found: 4: %1")).arg(widgetName);
  1306. job->reject(message);
  1307. return;
  1308. }
  1309. widget->setProperty(arguments["property"].toString().toUtf8().constData(),
  1310. arguments["value"]);
  1311. job->resolve();
  1312. }
  1313. void SocketApi::command_ASYNC_WAIT_FOR_WIDGET_SIGNAL(const QSharedPointer<SocketApiJob> &job)
  1314. {
  1315. auto &arguments = job->arguments();
  1316. QString widgetName = arguments["objectName"].toString();
  1317. auto widget = findWidget(arguments["objectName"].toString());
  1318. if (!widget) {
  1319. QString message = QString(QLatin1String("Widget not found: 5: %1")).arg(widgetName);
  1320. job->reject(message);
  1321. return;
  1322. }
  1323. ListenerClosure *closure = new ListenerClosure([job]() { job->resolve("signal emitted"); });
  1324. auto signalSignature = arguments["signalSignature"].toString();
  1325. signalSignature.prepend("2");
  1326. auto utf8 = signalSignature.toUtf8();
  1327. auto signalSignatureFinal = utf8.constData();
  1328. connect(widget, signalSignatureFinal, closure, SLOT(closureSlot()), Qt::QueuedConnection);
  1329. }
  1330. void SocketApi::command_ASYNC_TRIGGER_MENU_ACTION(const QSharedPointer<SocketApiJob> &job)
  1331. {
  1332. auto &arguments = job->arguments();
  1333. auto objectName = arguments["objectName"].toString();
  1334. auto widget = findWidget(objectName);
  1335. if (!widget) {
  1336. QString message = QString(QLatin1String("Object not found: 1: %1")).arg(objectName);
  1337. job->reject(message);
  1338. return;
  1339. }
  1340. auto children = widget->findChildren<QWidget *>();
  1341. for (auto childWidget : children) {
  1342. // foo is the popupwidget!
  1343. auto actions = childWidget->actions();
  1344. for (auto action : actions) {
  1345. if (action->objectName() == arguments["actionName"].toString()) {
  1346. action->trigger();
  1347. job->resolve("action found");
  1348. return;
  1349. }
  1350. }
  1351. }
  1352. QString message = QString(QLatin1String("Action not found: 1: %1")).arg(arguments["actionName"].toString());
  1353. job->reject(message);
  1354. }
  1355. void SocketApi::command_ASYNC_ASSERT_ICON_IS_EQUAL(const QSharedPointer<SocketApiJob> &job)
  1356. {
  1357. auto widget = findWidget(job->arguments()[QLatin1String("queryString")].toString());
  1358. if (!widget) {
  1359. QString message = QString(QLatin1String("Object not found: 6: %1")).arg(job->arguments()["queryString"].toString());
  1360. job->reject(message);
  1361. return;
  1362. }
  1363. auto propertyName = job->arguments()[QLatin1String("propertyPath")].toString();
  1364. auto segments = propertyName.split('.');
  1365. QObject *currentObject = widget;
  1366. QIcon value;
  1367. for (int i = 0; i < segments.count(); i++) {
  1368. auto segment = segments.at(i);
  1369. auto var = currentObject->property(segment.toUtf8().constData());
  1370. if (var.canConvert<QIcon>()) {
  1371. var.convert(QMetaType::QIcon);
  1372. value = var.value<QIcon>();
  1373. break;
  1374. }
  1375. auto tmpObject = var.value<QObject *>();
  1376. if (tmpObject) {
  1377. currentObject = tmpObject;
  1378. } else {
  1379. job->reject(QString(QLatin1String("Icon not found: %1")).arg(propertyName));
  1380. }
  1381. }
  1382. auto iconName = job->arguments()[QLatin1String("iconName")].toString();
  1383. if (value.name() == iconName) {
  1384. job->resolve();
  1385. } else {
  1386. job->reject("iconName " + iconName + " does not match: " + value.name());
  1387. }
  1388. }
  1389. #endif
  1390. QString SocketApi::buildRegisterPathMessage(const QString &path)
  1391. {
  1392. QFileInfo fi(path);
  1393. QString message = QLatin1String("REGISTER_PATH:");
  1394. message.append(QDir::toNativeSeparators(fi.absoluteFilePath()));
  1395. return message;
  1396. }
  1397. void SocketApiJob::resolve(const QString &response)
  1398. {
  1399. _socketListener->sendMessage(QStringLiteral("RESOLVE|") + _jobId + QLatin1Char('|') + response);
  1400. }
  1401. void SocketApiJob::resolve(const QJsonObject &response)
  1402. {
  1403. resolve(QJsonDocument { response }.toJson());
  1404. }
  1405. void SocketApiJob::reject(const QString &response)
  1406. {
  1407. _socketListener->sendMessage(QStringLiteral("REJECT|") + _jobId + QLatin1Char('|') + response);
  1408. }
  1409. SocketApiJobV2::SocketApiJobV2(const QSharedPointer<SocketListener> &socketListener, const QByteArray &command, const QJsonObject &arguments)
  1410. : _socketListener(socketListener)
  1411. , _command(command)
  1412. , _jobId(arguments[QStringLiteral("id")].toString())
  1413. , _arguments(arguments[QStringLiteral("arguments")].toObject())
  1414. {
  1415. ASSERT(!_jobId.isEmpty())
  1416. }
  1417. void SocketApiJobV2::success(const QJsonObject &response) const
  1418. {
  1419. doFinish(response);
  1420. }
  1421. void SocketApiJobV2::failure(const QString &error) const
  1422. {
  1423. doFinish({ { QStringLiteral("error"), error } });
  1424. }
  1425. void SocketApiJobV2::doFinish(const QJsonObject &obj) const
  1426. {
  1427. _socketListener->sendMessage(_command + QStringLiteral("_RESULT:") + QJsonDocument({ { QStringLiteral("id"), _jobId }, { QStringLiteral("arguments"), obj } }).toJson(QJsonDocument::Compact));
  1428. Q_EMIT finished();
  1429. }
  1430. } // namespace OCC
  1431. #include "socketapi.moc"