discoveryphase.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. /*
  2. * Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * for more details.
  13. */
  14. #include "discoveryphase.h"
  15. #include "account.h"
  16. #include "theme.h"
  17. #include "asserts.h"
  18. #include <csync_private.h>
  19. #include <csync_rename.h>
  20. #include <QLoggingCategory>
  21. #include <QUrl>
  22. #include <QFileInfo>
  23. #include <cstring>
  24. namespace OCC {
  25. Q_LOGGING_CATEGORY(lcDiscovery, "sync.discovery", QtInfoMsg)
  26. /* Given a sorted list of paths ending with '/', return whether or not the given path is within one of the paths of the list*/
  27. static bool findPathInList(const QStringList &list, const QString &path)
  28. {
  29. Q_ASSERT(std::is_sorted(list.begin(), list.end()));
  30. if (list.size() == 1 && list.first() == QLatin1String("/")) {
  31. // Special case for the case "/" is there, it matches everything
  32. return true;
  33. }
  34. QString pathSlash = path + QLatin1Char('/');
  35. // Since the list is sorted, we can do a binary search.
  36. // If the path is a prefix of another item or right after in the lexical order.
  37. auto it = std::lower_bound(list.begin(), list.end(), pathSlash);
  38. if (it != list.end() && *it == pathSlash) {
  39. return true;
  40. }
  41. if (it == list.begin()) {
  42. return false;
  43. }
  44. --it;
  45. Q_ASSERT(it->endsWith(QLatin1Char('/'))); // Folder::setSelectiveSyncBlackList makes sure of that
  46. return pathSlash.startsWith(*it);
  47. }
  48. bool DiscoveryJob::isInSelectiveSyncBlackList(const char *path) const
  49. {
  50. if (_selectiveSyncBlackList.isEmpty()) {
  51. // If there is no black list, everything is allowed
  52. return false;
  53. }
  54. // Block if it is in the black list
  55. if (findPathInList(_selectiveSyncBlackList, QString::fromUtf8(path))) {
  56. return true;
  57. }
  58. // Also try to adjust the path if there was renames
  59. if (csync_rename_count(_csync_ctx)) {
  60. QScopedPointer<char, QScopedPointerPodDeleter> adjusted(
  61. csync_rename_adjust_path_source(_csync_ctx, path));
  62. if (strcmp(adjusted.data(), path) != 0) {
  63. return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted.data()));
  64. }
  65. }
  66. return false;
  67. }
  68. int DiscoveryJob::isInSelectiveSyncBlackListCallback(void *data, const char *path)
  69. {
  70. return static_cast<DiscoveryJob*>(data)->isInSelectiveSyncBlackList(path);
  71. }
  72. bool DiscoveryJob::checkSelectiveSyncNewFolder(const QString& path, const char *remotePerm)
  73. {
  74. if (_syncOptions._confirmExternalStorage && std::strchr(remotePerm, 'M')) {
  75. // 'M' in the permission means external storage.
  76. /* Note: DiscoverySingleDirectoryJob::directoryListingIteratedSlot make sure that only the
  77. * root of a mounted storage has 'M', all sub entries have 'm' */
  78. // Only allow it if the white list contains exactly this path (not parents)
  79. // We want to ask confirmation for external storage even if the parents where selected
  80. if (_selectiveSyncWhiteList.contains(path + QLatin1Char('/'))) {
  81. return false;
  82. }
  83. emit newBigFolder(path, true);
  84. return true;
  85. }
  86. // If this path or the parent is in the white list, then we do not block this file
  87. if (findPathInList(_selectiveSyncWhiteList, path)) {
  88. return false;
  89. }
  90. auto limit = _syncOptions._newBigFolderSizeLimit;
  91. if (limit < 0) {
  92. // no limit, everything is allowed;
  93. return false;
  94. }
  95. // Go in the main thread to do a PROPFIND to know the size of this folder
  96. qint64 result = -1;
  97. {
  98. QMutexLocker locker(&_vioMutex);
  99. emit doGetSizeSignal(path, &result);
  100. _vioWaitCondition.wait(&_vioMutex);
  101. }
  102. if (result >= limit) {
  103. // we tell the UI there is a new folder
  104. emit newBigFolder(path, false);
  105. return true;
  106. } else {
  107. // it is not too big, put it in the white list (so we will not do more query for the children)
  108. // and and do not block.
  109. auto p = path;
  110. if (!p.endsWith(QLatin1Char('/'))) { p += QLatin1Char('/'); }
  111. _selectiveSyncWhiteList.insert(std::upper_bound(_selectiveSyncWhiteList.begin(),
  112. _selectiveSyncWhiteList.end(), p), p);
  113. return false;
  114. }
  115. }
  116. int DiscoveryJob::checkSelectiveSyncNewFolderCallback(void *data, const char *path, const char *remotePerm)
  117. {
  118. return static_cast<DiscoveryJob*>(data)->checkSelectiveSyncNewFolder(QString::fromUtf8(path), remotePerm);
  119. }
  120. void DiscoveryJob::update_job_update_callback (bool local,
  121. const char *dirUrl,
  122. void *userdata)
  123. {
  124. DiscoveryJob *updateJob = static_cast<DiscoveryJob*>(userdata);
  125. if (updateJob) {
  126. // Don't wanna overload the UI
  127. if (!updateJob->_lastUpdateProgressCallbackCall.isValid()) {
  128. updateJob->_lastUpdateProgressCallbackCall.restart(); // first call
  129. } else if (updateJob->_lastUpdateProgressCallbackCall.elapsed() < 200) {
  130. return;
  131. } else {
  132. updateJob->_lastUpdateProgressCallbackCall.restart();
  133. }
  134. QByteArray pPath(dirUrl);
  135. int indx = pPath.lastIndexOf('/');
  136. if(indx>-1) {
  137. const QString path = QUrl::fromPercentEncoding( pPath.mid(indx+1));
  138. emit updateJob->folderDiscovered(local, path);
  139. }
  140. }
  141. }
  142. // Only use for error cases! It will always set an error errno
  143. int get_errno_from_http_errcode( int err, const QString & reason ) {
  144. int new_errno = EIO;
  145. switch(err) {
  146. case 401: /* Unauthorized */
  147. case 402: /* Payment Required */
  148. case 407: /* Proxy Authentication Required */
  149. case 405:
  150. new_errno = EPERM;
  151. break;
  152. case 301: /* Moved Permanently */
  153. case 303: /* See Other */
  154. case 404: /* Not Found */
  155. case 410: /* Gone */
  156. new_errno = ENOENT;
  157. break;
  158. case 408: /* Request Timeout */
  159. case 504: /* Gateway Timeout */
  160. new_errno = EAGAIN;
  161. break;
  162. case 423: /* Locked */
  163. new_errno = EACCES;
  164. break;
  165. case 403: /* Forbidden */
  166. new_errno = ERRNO_FORBIDDEN;
  167. break;
  168. case 400: /* Bad Request */
  169. case 409: /* Conflict */
  170. case 411: /* Length Required */
  171. case 412: /* Precondition Failed */
  172. case 414: /* Request-URI Too Long */
  173. case 415: /* Unsupported Media Type */
  174. case 424: /* Failed Dependency */
  175. case 501: /* Not Implemented */
  176. new_errno = EINVAL;
  177. break;
  178. case 507: /* Insufficient Storage */
  179. new_errno = ENOSPC;
  180. break;
  181. case 206: /* Partial Content */
  182. case 300: /* Multiple Choices */
  183. case 302: /* Found */
  184. case 305: /* Use Proxy */
  185. case 306: /* (Unused) */
  186. case 307: /* Temporary Redirect */
  187. case 406: /* Not Acceptable */
  188. case 416: /* Requested Range Not Satisfiable */
  189. case 417: /* Expectation Failed */
  190. case 422: /* Unprocessable Entity */
  191. case 500: /* Internal Server Error */
  192. case 502: /* Bad Gateway */
  193. case 505: /* HTTP Version Not Supported */
  194. new_errno = EIO;
  195. break;
  196. case 503: /* Service Unavailable */
  197. // https://github.com/owncloud/core/pull/26145/files
  198. if (reason == "Storage not available" || reason == "Storage is temporarily not available") {
  199. new_errno = ERRNO_STORAGE_UNAVAILABLE;
  200. } else {
  201. new_errno = ERRNO_SERVICE_UNAVAILABLE;
  202. }
  203. break;
  204. case 413: /* Request Entity too Large */
  205. new_errno = EFBIG;
  206. break;
  207. default:
  208. new_errno = EIO;
  209. }
  210. return new_errno;
  211. }
  212. DiscoverySingleDirectoryJob::DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent)
  213. : QObject(parent), _subPath(path), _account(account), _ignoredFirst(false), _isRootPath(false), _isExternalStorage(false)
  214. {
  215. }
  216. void DiscoverySingleDirectoryJob::start()
  217. {
  218. // Start the actual HTTP job
  219. LsColJob *lsColJob = new LsColJob(_account, _subPath, this);
  220. QList<QByteArray> props;
  221. props << "resourcetype" << "getlastmodified" << "getcontentlength" << "getetag"
  222. << "http://owncloud.org/ns:id" << "http://owncloud.org/ns:downloadURL"
  223. << "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions";
  224. if (_isRootPath)
  225. props << "http://owncloud.org/ns:data-fingerprint";
  226. lsColJob->setProperties(props);
  227. QObject::connect(lsColJob, SIGNAL(directoryListingIterated(QString,QMap<QString,QString>)),
  228. this, SLOT(directoryListingIteratedSlot(QString,QMap<QString,QString>)));
  229. QObject::connect(lsColJob, SIGNAL(finishedWithError(QNetworkReply*)), this, SLOT(lsJobFinishedWithErrorSlot(QNetworkReply*)));
  230. QObject::connect(lsColJob, SIGNAL(finishedWithoutError()), this, SLOT(lsJobFinishedWithoutErrorSlot()));
  231. lsColJob->start();
  232. _lsColJob = lsColJob;
  233. }
  234. void DiscoverySingleDirectoryJob::abort()
  235. {
  236. if (_lsColJob && _lsColJob->reply()) {
  237. _lsColJob->reply()->abort();
  238. }
  239. }
  240. static csync_vio_file_stat_t* propertyMapToFileStat(const QMap<QString,QString> &map)
  241. {
  242. csync_vio_file_stat_t* file_stat = csync_vio_file_stat_new();
  243. for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
  244. QString property = it.key();
  245. QString value = it.value();
  246. if (property == "resourcetype") {
  247. if (value.contains("collection")) {
  248. file_stat->type = CSYNC_VIO_FILE_TYPE_DIRECTORY;
  249. } else {
  250. file_stat->type = CSYNC_VIO_FILE_TYPE_REGULAR;
  251. }
  252. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE;
  253. } else if (property == "getlastmodified") {
  254. file_stat->mtime = oc_httpdate_parse(value.toUtf8());
  255. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME;
  256. } else if (property == "getcontentlength") {
  257. bool ok = false;
  258. qlonglong ll = value.toLongLong(&ok);
  259. if (ok && ll >= 0) {
  260. file_stat->size = ll;
  261. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE;
  262. }
  263. } else if (property == "getetag") {
  264. file_stat->etag = csync_normalize_etag(value.toUtf8());
  265. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG;
  266. } else if (property == "id") {
  267. csync_vio_file_stat_set_file_id(file_stat, value.toUtf8());
  268. } else if (property == "downloadURL") {
  269. file_stat->directDownloadUrl = strdup(value.toUtf8());
  270. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_DIRECTDOWNLOADURL;
  271. } else if (property == "dDC") {
  272. file_stat->directDownloadCookies = strdup(value.toUtf8());
  273. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_DIRECTDOWNLOADCOOKIES;
  274. } else if (property == "permissions") {
  275. auto v = value.toUtf8();
  276. if (value.isEmpty()) {
  277. // special meaning for our code: server returned permissions but are empty
  278. // meaning only reading is allowed for this resource
  279. file_stat->remotePerm[0] = ' ';
  280. // see _csync_detect_update()
  281. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERM;
  282. } else if (v.length() < int(sizeof(file_stat->remotePerm))) {
  283. strcpy(file_stat->remotePerm, v.constData());
  284. file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERM;
  285. } else {
  286. qCWarning(lcDiscovery) << "permissions too large" << v;
  287. }
  288. }
  289. }
  290. return file_stat;
  291. }
  292. void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, const QMap<QString,QString> &map)
  293. {
  294. if (!_ignoredFirst) {
  295. // The first entry is for the folder itself, we should process it differently.
  296. _ignoredFirst = true;
  297. if (map.contains("permissions")) {
  298. auto perm = map.value("permissions");
  299. emit firstDirectoryPermissions(perm);
  300. _isExternalStorage = perm.contains(QLatin1Char('M'));
  301. }
  302. if (map.contains("data-fingerprint")) {
  303. _dataFingerprint = map.value("data-fingerprint").toUtf8();
  304. }
  305. } else {
  306. // Remove <webDAV-Url>/folder/ from <webDAV-Url>/folder/subfile.txt
  307. file.remove(0, _lsColJob->reply()->request().url().path().length());
  308. // remove trailing slash
  309. while (file.endsWith('/')) {
  310. file.chop(1);
  311. }
  312. // remove leading slash
  313. while (file.startsWith('/')) {
  314. file = file.remove(0, 1);
  315. }
  316. FileStatPointer file_stat(propertyMapToFileStat(map));
  317. file_stat->name = strdup(file.toUtf8());
  318. if (!file_stat->etag || strlen(file_stat->etag) == 0) {
  319. qCCritical(lcDiscovery) << "etag of" << file_stat->name << "is" << file_stat->etag << " This must not happen.";
  320. }
  321. if (_isExternalStorage) {
  322. /* All the entries in a external storage have 'M' in their permission. However, for all
  323. purposes in the desktop client, we only need to know about the mount points.
  324. So replace the 'M' by a 'm' for every sub entries in an external storage */
  325. std::replace(file_stat->remotePerm, file_stat->remotePerm + strlen(file_stat->remotePerm),
  326. 'M', 'm');
  327. }
  328. QStringRef fileRef(&file);
  329. int slashPos = file.lastIndexOf(QLatin1Char('/'));
  330. if( slashPos > -1 ) {
  331. fileRef = file.midRef(slashPos+1);
  332. }
  333. _results.append(file_stat);
  334. }
  335. //This works in concerto with the RequestEtagJob and the Folder object to check if the remote folder changed.
  336. if (map.contains("getetag")) {
  337. _etagConcatenation += map.value("getetag");
  338. if (_firstEtag.isEmpty()) {
  339. _firstEtag = map.value("getetag"); // for directory itself
  340. }
  341. }
  342. }
  343. void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot()
  344. {
  345. if (!_ignoredFirst) {
  346. // This is a sanity check, if we haven't _ignoredFirst then it means we never received any directoryListingIteratedSlot
  347. // which means somehow the server XML was bogus
  348. emit finishedWithError(ERRNO_WRONG_CONTENT, QLatin1String("Server error: PROPFIND reply is not XML formatted!"));
  349. deleteLater();
  350. return;
  351. }
  352. emit etag(_firstEtag);
  353. emit etagConcatenation(_etagConcatenation);
  354. emit finishedWithResult(_results);
  355. deleteLater();
  356. }
  357. void DiscoverySingleDirectoryJob::lsJobFinishedWithErrorSlot(QNetworkReply *r)
  358. {
  359. QString contentType = r->header(QNetworkRequest::ContentTypeHeader).toString();
  360. int httpCode = r->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  361. QString httpReason = r->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
  362. QString msg = r->errorString();
  363. int errnoCode = EIO; // Something went wrong
  364. qCWarning(lcDiscovery) << "LSCOL job error" << r->errorString() << httpCode << r->error();
  365. if (httpCode != 0 && httpCode != 207) {
  366. errnoCode = get_errno_from_http_errcode(httpCode, httpReason);
  367. } else if (r->error() != QNetworkReply::NoError) {
  368. errnoCode = EIO;
  369. } else if (!contentType.contains("application/xml; charset=utf-8")) {
  370. msg = QLatin1String("Server error: PROPFIND reply is not XML formatted!");
  371. errnoCode = ERRNO_WRONG_CONTENT;
  372. } else {
  373. // Default keep at EIO, see above
  374. }
  375. emit finishedWithError(errnoCode == 0 ? EIO : errnoCode, msg);
  376. deleteLater();
  377. }
  378. void DiscoveryMainThread::setupHooks(DiscoveryJob *discoveryJob, const QString &pathPrefix)
  379. {
  380. _discoveryJob = discoveryJob;
  381. _pathPrefix = pathPrefix;
  382. connect(discoveryJob, SIGNAL(doOpendirSignal(QString,DiscoveryDirectoryResult*)),
  383. this, SLOT(doOpendirSlot(QString,DiscoveryDirectoryResult*)),
  384. Qt::QueuedConnection);
  385. connect(discoveryJob, SIGNAL(doGetSizeSignal(QString,qint64*)),
  386. this, SLOT(doGetSizeSlot(QString,qint64*)),
  387. Qt::QueuedConnection);
  388. }
  389. // Coming from owncloud_opendir -> DiscoveryJob::vio_opendir_hook -> doOpendirSignal
  390. void DiscoveryMainThread::doOpendirSlot(const QString &subPath, DiscoveryDirectoryResult *r)
  391. {
  392. QString fullPath = _pathPrefix;
  393. if (!_pathPrefix.endsWith('/')) {
  394. fullPath += '/';
  395. }
  396. fullPath += subPath;
  397. // remove trailing slash
  398. while (fullPath.endsWith('/')) {
  399. fullPath.chop(1);
  400. }
  401. // emit _discoveryJob->folderDiscovered(false, subPath);
  402. _discoveryJob->update_job_update_callback (false, subPath.toUtf8(), _discoveryJob);
  403. // Result gets written in there
  404. _currentDiscoveryDirectoryResult = r;
  405. _currentDiscoveryDirectoryResult->path = fullPath;
  406. // Schedule the DiscoverySingleDirectoryJob
  407. _singleDirJob = new DiscoverySingleDirectoryJob(_account, fullPath, this);
  408. QObject::connect(_singleDirJob, SIGNAL(finishedWithResult(const QList<FileStatPointer> &)),
  409. this, SLOT(singleDirectoryJobResultSlot(const QList<FileStatPointer> &)));
  410. QObject::connect(_singleDirJob, SIGNAL(finishedWithError(int,QString)),
  411. this, SLOT(singleDirectoryJobFinishedWithErrorSlot(int,QString)));
  412. QObject::connect(_singleDirJob, SIGNAL(firstDirectoryPermissions(QString)),
  413. this, SLOT(singleDirectoryJobFirstDirectoryPermissionsSlot(QString)));
  414. QObject::connect(_singleDirJob, SIGNAL(etagConcatenation(QString)),
  415. this, SIGNAL(etagConcatenation(QString)));
  416. QObject::connect(_singleDirJob, SIGNAL(etag(QString)),
  417. this, SIGNAL(etag(QString)));
  418. if (!_firstFolderProcessed) {
  419. _singleDirJob->setIsRootPath();
  420. }
  421. _singleDirJob->start();
  422. }
  423. void DiscoveryMainThread::singleDirectoryJobResultSlot(const QList<FileStatPointer> & result)
  424. {
  425. if (!_currentDiscoveryDirectoryResult) {
  426. return; // possibly aborted
  427. }
  428. qCDebug(lcDiscovery) << "Have" << result.count() << "results for " << _currentDiscoveryDirectoryResult->path;
  429. _currentDiscoveryDirectoryResult->list = result;
  430. _currentDiscoveryDirectoryResult->code = 0;
  431. _currentDiscoveryDirectoryResult->listIndex = 0;
  432. _currentDiscoveryDirectoryResult = 0; // the sync thread owns it now
  433. if (!_firstFolderProcessed) {
  434. _firstFolderProcessed = true;
  435. _dataFingerprint = _singleDirJob->_dataFingerprint;
  436. }
  437. _discoveryJob->_vioMutex.lock();
  438. _discoveryJob->_vioWaitCondition.wakeAll();
  439. _discoveryJob->_vioMutex.unlock();
  440. }
  441. void DiscoveryMainThread::singleDirectoryJobFinishedWithErrorSlot(int csyncErrnoCode, const QString &msg)
  442. {
  443. if (!_currentDiscoveryDirectoryResult) {
  444. return; // possibly aborted
  445. }
  446. qCDebug(lcDiscovery) << csyncErrnoCode << msg;
  447. _currentDiscoveryDirectoryResult->code = csyncErrnoCode;
  448. _currentDiscoveryDirectoryResult->msg = msg;
  449. _currentDiscoveryDirectoryResult = 0; // the sync thread owns it now
  450. _discoveryJob->_vioMutex.lock();
  451. _discoveryJob->_vioWaitCondition.wakeAll();
  452. _discoveryJob->_vioMutex.unlock();
  453. }
  454. void DiscoveryMainThread::singleDirectoryJobFirstDirectoryPermissionsSlot(const QString &p)
  455. {
  456. // Should be thread safe since the sync thread is blocked
  457. if (!_discoveryJob->_csync_ctx->remote.root_perms) {
  458. qCDebug(lcDiscovery) << "Permissions for root dir:" << p;
  459. _discoveryJob->_csync_ctx->remote.root_perms = strdup(p.toUtf8());
  460. }
  461. }
  462. void DiscoveryMainThread::doGetSizeSlot(const QString& path, qint64* result)
  463. {
  464. QString fullPath = _pathPrefix;
  465. if (!_pathPrefix.endsWith('/')) {
  466. fullPath += '/';
  467. }
  468. fullPath += path;
  469. // remove trailing slash
  470. while (fullPath.endsWith('/')) {
  471. fullPath.chop(1);
  472. }
  473. _currentGetSizeResult = result;
  474. // Schedule the DiscoverySingleDirectoryJob
  475. auto propfindJob = new PropfindJob(_account, fullPath, this);
  476. propfindJob->setProperties(QList<QByteArray>() << "resourcetype" << "http://owncloud.org/ns:size");
  477. QObject::connect(propfindJob, SIGNAL(finishedWithError()),
  478. this, SLOT(slotGetSizeFinishedWithError()));
  479. QObject::connect(propfindJob, SIGNAL(result(QVariantMap)),
  480. this, SLOT(slotGetSizeResult(QVariantMap)));
  481. propfindJob->start();
  482. }
  483. void DiscoveryMainThread::slotGetSizeFinishedWithError()
  484. {
  485. if (! _currentGetSizeResult) {
  486. return; // possibly aborted
  487. }
  488. qCWarning(lcDiscovery) << "Error getting the size of the directory";
  489. // just let let the discovery job continue then
  490. _currentGetSizeResult = 0;
  491. QMutexLocker locker(&_discoveryJob->_vioMutex);
  492. _discoveryJob->_vioWaitCondition.wakeAll();
  493. }
  494. void DiscoveryMainThread::slotGetSizeResult(const QVariantMap &map)
  495. {
  496. if (! _currentGetSizeResult) {
  497. return; // possibly aborted
  498. }
  499. *_currentGetSizeResult = map.value(QLatin1String("size")).toLongLong();
  500. qCDebug(lcDiscovery) << "Size of folder:" << *_currentGetSizeResult;
  501. _currentGetSizeResult = 0;
  502. QMutexLocker locker(&_discoveryJob->_vioMutex);
  503. _discoveryJob->_vioWaitCondition.wakeAll();
  504. }
  505. // called from SyncEngine
  506. void DiscoveryMainThread::abort() {
  507. if (_singleDirJob) {
  508. _singleDirJob->disconnect(SIGNAL(finishedWithError(int,QString)), this);
  509. _singleDirJob->disconnect(SIGNAL(firstDirectoryPermissions(QString)), this);
  510. _singleDirJob->disconnect(SIGNAL(finishedWithResult(const QList<FileStatPointer> &)), this);
  511. _singleDirJob->abort();
  512. }
  513. if (_currentDiscoveryDirectoryResult) {
  514. if (_discoveryJob->_vioMutex.tryLock()) {
  515. _currentDiscoveryDirectoryResult->msg = tr("Aborted by the user"); // Actually also created somewhere else by sync engine
  516. _currentDiscoveryDirectoryResult->code = EIO;
  517. _currentDiscoveryDirectoryResult = 0;
  518. _discoveryJob->_vioWaitCondition.wakeAll();
  519. _discoveryJob->_vioMutex.unlock();
  520. }
  521. }
  522. if (_currentGetSizeResult) {
  523. _currentGetSizeResult = 0;
  524. QMutexLocker locker(&_discoveryJob->_vioMutex);
  525. _discoveryJob->_vioWaitCondition.wakeAll();
  526. }
  527. }
  528. csync_vio_handle_t* DiscoveryJob::remote_vio_opendir_hook (const char *url,
  529. void *userdata)
  530. {
  531. DiscoveryJob *discoveryJob = static_cast<DiscoveryJob*>(userdata);
  532. if (discoveryJob) {
  533. qCDebug(lcDiscovery) << discoveryJob << url << "Calling into main thread...";
  534. QScopedPointer<DiscoveryDirectoryResult> directoryResult(new DiscoveryDirectoryResult());
  535. directoryResult->code = EIO;
  536. discoveryJob->_vioMutex.lock();
  537. const QString qurl = QString::fromUtf8(url);
  538. emit discoveryJob->doOpendirSignal(qurl, directoryResult.data());
  539. discoveryJob->_vioWaitCondition.wait(&discoveryJob->_vioMutex, ULONG_MAX); // FIXME timeout?
  540. discoveryJob->_vioMutex.unlock();
  541. qCDebug(lcDiscovery) << discoveryJob << url << "...Returned from main thread";
  542. // Upon awakening from the _vioWaitCondition, iterator should be a valid iterator.
  543. if (directoryResult->code != 0) {
  544. qCDebug(lcDiscovery) << directoryResult->code << "when opening" << url << "msg=" << directoryResult->msg;
  545. errno = directoryResult->code;
  546. // save the error string to the context
  547. discoveryJob->_csync_ctx->error_string = qstrdup( directoryResult->msg.toUtf8().constData() );
  548. return NULL;
  549. }
  550. return directoryResult.take();
  551. }
  552. return NULL;
  553. }
  554. csync_vio_file_stat_t* DiscoveryJob::remote_vio_readdir_hook (csync_vio_handle_t *dhandle,
  555. void *userdata)
  556. {
  557. DiscoveryJob *discoveryJob = static_cast<DiscoveryJob*>(userdata);
  558. if (discoveryJob) {
  559. DiscoveryDirectoryResult *directoryResult = static_cast<DiscoveryDirectoryResult*>(dhandle);
  560. if (directoryResult->listIndex < directoryResult->list.size()) {
  561. csync_vio_file_stat_t *file_stat = directoryResult->list.at(directoryResult->listIndex++).data();
  562. // Make a copy, csync_update will delete the copy
  563. return csync_vio_file_stat_copy(file_stat);
  564. }
  565. }
  566. return NULL;
  567. }
  568. void DiscoveryJob::remote_vio_closedir_hook (csync_vio_handle_t *dhandle, void *userdata)
  569. {
  570. DiscoveryJob *discoveryJob = static_cast<DiscoveryJob*>(userdata);
  571. if (discoveryJob) {
  572. DiscoveryDirectoryResult *directoryResult = static_cast<DiscoveryDirectoryResult*> (dhandle);
  573. QString path = directoryResult->path;
  574. qCDebug(lcDiscovery) << discoveryJob << path;
  575. delete directoryResult; // just deletes the struct and the iterator, the data itself is owned by the SyncEngine/DiscoveryMainThread
  576. }
  577. }
  578. void DiscoveryJob::start() {
  579. _selectiveSyncBlackList.sort();
  580. _selectiveSyncWhiteList.sort();
  581. _csync_ctx->callbacks.update_callback_userdata = this;
  582. _csync_ctx->callbacks.update_callback = update_job_update_callback;
  583. _csync_ctx->callbacks.checkSelectiveSyncBlackListHook = isInSelectiveSyncBlackListCallback;
  584. _csync_ctx->callbacks.checkSelectiveSyncNewFolderHook = checkSelectiveSyncNewFolderCallback;
  585. _csync_ctx->callbacks.remote_opendir_hook = remote_vio_opendir_hook;
  586. _csync_ctx->callbacks.remote_readdir_hook = remote_vio_readdir_hook;
  587. _csync_ctx->callbacks.remote_closedir_hook = remote_vio_closedir_hook;
  588. _csync_ctx->callbacks.vio_userdata = this;
  589. csync_set_log_callback(_log_callback);
  590. csync_set_log_level(_log_level);
  591. _lastUpdateProgressCallbackCall.invalidate();
  592. int ret = csync_update(_csync_ctx);
  593. _csync_ctx->callbacks.checkSelectiveSyncNewFolderHook = 0;
  594. _csync_ctx->callbacks.checkSelectiveSyncBlackListHook = 0;
  595. _csync_ctx->callbacks.update_callback = 0;
  596. _csync_ctx->callbacks.update_callback_userdata = 0;
  597. emit finished(ret);
  598. deleteLater();
  599. }
  600. }