folderman.cpp 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819
  1. /*
  2. * Copyright (C) by Klaas Freitag <freitag@owncloud.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 "folderman.h"
  15. #include "configfile.h"
  16. #include "folder.h"
  17. #include "syncresult.h"
  18. #include "theme.h"
  19. #include "socketapi/socketapi.h"
  20. #include "account.h"
  21. #include "accountstate.h"
  22. #include "accountmanager.h"
  23. #include "filesystem.h"
  24. #include "lockwatcher.h"
  25. #include "common/asserts.h"
  26. #include <pushnotifications.h>
  27. #include <syncengine.h>
  28. #ifdef Q_OS_MAC
  29. #include <CoreServices/CoreServices.h>
  30. #endif
  31. #include <QMessageBox>
  32. #include <QtCore>
  33. #include <QMutableSetIterator>
  34. #include <QSet>
  35. #include <QNetworkProxy>
  36. #include <QDesktopServices>
  37. #include <QtConcurrent>
  38. static const char versionC[] = "version";
  39. static const int maxFoldersVersion = 1;
  40. namespace OCC {
  41. Q_LOGGING_CATEGORY(lcFolderMan, "nextcloud.gui.folder.manager", QtInfoMsg)
  42. FolderMan *FolderMan::_instance = nullptr;
  43. FolderMan::FolderMan(QObject *parent)
  44. : QObject(parent)
  45. , _lockWatcher(new LockWatcher)
  46. , _navigationPaneHelper(this)
  47. {
  48. ASSERT(!_instance);
  49. _instance = this;
  50. _socketApi.reset(new SocketApi);
  51. ConfigFile cfg;
  52. std::chrono::milliseconds polltime = cfg.remotePollInterval();
  53. qCInfo(lcFolderMan) << "setting remote poll timer interval to" << polltime.count() << "msec";
  54. _etagPollTimer.setInterval(polltime.count());
  55. QObject::connect(&_etagPollTimer, &QTimer::timeout, this, &FolderMan::slotEtagPollTimerTimeout);
  56. _etagPollTimer.start();
  57. _startScheduledSyncTimer.setSingleShot(true);
  58. connect(&_startScheduledSyncTimer, &QTimer::timeout,
  59. this, &FolderMan::slotStartScheduledFolderSync);
  60. _timeScheduler.setInterval(5000);
  61. _timeScheduler.setSingleShot(false);
  62. connect(&_timeScheduler, &QTimer::timeout,
  63. this, &FolderMan::slotScheduleFolderByTime);
  64. _timeScheduler.start();
  65. connect(AccountManager::instance(), &AccountManager::removeAccountFolders,
  66. this, &FolderMan::slotRemoveFoldersForAccount);
  67. connect(AccountManager::instance(), &AccountManager::accountSyncConnectionRemoved,
  68. this, &FolderMan::slotAccountRemoved);
  69. connect(_lockWatcher.data(), &LockWatcher::fileUnlocked,
  70. this, &FolderMan::slotWatchedFileUnlocked);
  71. connect(this, &FolderMan::folderListChanged, this, &FolderMan::slotSetupPushNotifications);
  72. }
  73. FolderMan *FolderMan::instance()
  74. {
  75. return _instance;
  76. }
  77. FolderMan::~FolderMan()
  78. {
  79. qDeleteAll(_folderMap);
  80. _instance = nullptr;
  81. }
  82. const OCC::Folder::Map &FolderMan::map() const
  83. {
  84. return _folderMap;
  85. }
  86. void FolderMan::unloadFolder(Folder *f)
  87. {
  88. if (!f) {
  89. return;
  90. }
  91. _socketApi->slotUnregisterPath(f->alias());
  92. _folderMap.remove(f->alias());
  93. disconnect(f, &Folder::syncStarted,
  94. this, &FolderMan::slotFolderSyncStarted);
  95. disconnect(f, &Folder::syncFinished,
  96. this, &FolderMan::slotFolderSyncFinished);
  97. disconnect(f, &Folder::syncStateChange,
  98. this, &FolderMan::slotForwardFolderSyncStateChange);
  99. disconnect(f, &Folder::syncPausedChanged,
  100. this, &FolderMan::slotFolderSyncPaused);
  101. disconnect(&f->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged,
  102. _socketApi.data(), &SocketApi::broadcastStatusPushMessage);
  103. disconnect(f, &Folder::watchedFileChangedExternally,
  104. &f->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched);
  105. }
  106. int FolderMan::unloadAndDeleteAllFolders()
  107. {
  108. int cnt = 0;
  109. // clear the list of existing folders.
  110. Folder::MapIterator i(_folderMap);
  111. while (i.hasNext()) {
  112. i.next();
  113. Folder *f = i.value();
  114. unloadFolder(f);
  115. delete f;
  116. cnt++;
  117. }
  118. ASSERT(_folderMap.isEmpty());
  119. _lastSyncFolder = nullptr;
  120. _currentSyncFolder = nullptr;
  121. _scheduledFolders.clear();
  122. emit folderListChanged(_folderMap);
  123. emit scheduleQueueChanged();
  124. return cnt;
  125. }
  126. void FolderMan::registerFolderWithSocketApi(Folder *folder)
  127. {
  128. if (!folder)
  129. return;
  130. if (!QDir(folder->path()).exists())
  131. return;
  132. // register the folder with the socket API
  133. if (folder->canSync())
  134. _socketApi->slotRegisterPath(folder->alias());
  135. }
  136. int FolderMan::setupFolders()
  137. {
  138. Utility::registerUriHandlerForLocalEditing();
  139. unloadAndDeleteAllFolders();
  140. QStringList skipSettingsKeys;
  141. backwardMigrationSettingsKeys(&skipSettingsKeys, &skipSettingsKeys);
  142. auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts"));
  143. const auto accountsWithSettings = settings->childGroups();
  144. if (accountsWithSettings.isEmpty()) {
  145. int r = setupFoldersMigration();
  146. if (r > 0) {
  147. AccountManager::instance()->save(false); // don't save credentials, they had not been loaded from keychain
  148. }
  149. return r;
  150. }
  151. qCInfo(lcFolderMan) << "Setup folders from settings file";
  152. for (const auto &account : AccountManager::instance()->accounts()) {
  153. const auto id = account->account()->id();
  154. if (!accountsWithSettings.contains(id)) {
  155. continue;
  156. }
  157. settings->beginGroup(id);
  158. // The "backwardsCompatible" flag here is related to migrating old
  159. // database locations
  160. auto process = [&](const QString &groupName, bool backwardsCompatible, bool foldersWithPlaceholders) {
  161. settings->beginGroup(groupName);
  162. if (skipSettingsKeys.contains(settings->group())) {
  163. // Should not happen: bad container keys should have been deleted
  164. qCWarning(lcFolderMan) << "Folder structure" << groupName << "is too new, ignoring";
  165. } else {
  166. setupFoldersHelper(*settings, account, skipSettingsKeys, backwardsCompatible, foldersWithPlaceholders);
  167. }
  168. settings->endGroup();
  169. };
  170. process(QStringLiteral("Folders"), true, false);
  171. // See Folder::saveToSettings for details about why these exists.
  172. process(QStringLiteral("Multifolders"), false, false);
  173. process(QStringLiteral("FoldersWithPlaceholders"), false, true);
  174. settings->endGroup(); // <account>
  175. }
  176. emit folderListChanged(_folderMap);
  177. for (const auto folder : _folderMap) {
  178. folder->processSwitchedToVirtualFiles();
  179. }
  180. return _folderMap.size();
  181. }
  182. void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, const QStringList &ignoreKeys, bool backwardsCompatible, bool foldersWithPlaceholders)
  183. {
  184. for (const auto &folderAlias : settings.childGroups()) {
  185. // Skip folders with too-new version
  186. settings.beginGroup(folderAlias);
  187. if (ignoreKeys.contains(settings.group())) {
  188. qCInfo(lcFolderMan) << "Folder" << folderAlias << "is too new, ignoring";
  189. _additionalBlockedFolderAliases.insert(folderAlias);
  190. settings.endGroup();
  191. continue;
  192. }
  193. settings.endGroup();
  194. FolderDefinition folderDefinition;
  195. settings.beginGroup(folderAlias);
  196. if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) {
  197. auto defaultJournalPath = folderDefinition.defaultJournalPath(account->account());
  198. // Migration: Old settings don't have journalPath
  199. if (folderDefinition.journalPath.isEmpty()) {
  200. folderDefinition.journalPath = defaultJournalPath;
  201. }
  202. // Migration #2: journalPath might be absolute (in DataAppDir most likely) move it back to the root of local tree
  203. if (folderDefinition.journalPath.at(0) != QChar('.')) {
  204. QFile oldJournal(folderDefinition.journalPath);
  205. QFile oldJournalShm(folderDefinition.journalPath + QStringLiteral("-shm"));
  206. QFile oldJournalWal(folderDefinition.journalPath + QStringLiteral("-wal"));
  207. folderDefinition.journalPath = defaultJournalPath;
  208. socketApi()->slotUnregisterPath(folderAlias);
  209. auto settings = account->settings();
  210. auto journalFileMoveSuccess = true;
  211. // Due to db logic can't be sure which of these file exist.
  212. if (oldJournal.exists()) {
  213. journalFileMoveSuccess &= oldJournal.rename(folderDefinition.localPath + "/" + folderDefinition.journalPath);
  214. }
  215. if (oldJournalShm.exists()) {
  216. journalFileMoveSuccess &= oldJournalShm.rename(folderDefinition.localPath + "/" + folderDefinition.journalPath + QStringLiteral("-shm"));
  217. }
  218. if (oldJournalWal.exists()) {
  219. journalFileMoveSuccess &= oldJournalWal.rename(folderDefinition.localPath + "/" + folderDefinition.journalPath + QStringLiteral("-wal"));
  220. }
  221. if (!journalFileMoveSuccess) {
  222. qCWarning(lcFolderMan) << "Wasn't able to move 3.0 syncjournal database files to new location. One-time loss off sync settings possible.";
  223. } else {
  224. qCInfo(lcFolderMan) << "Successfully migrated syncjournal database.";
  225. }
  226. auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode);
  227. if (!vfs && folderDefinition.virtualFilesMode != Vfs::Off) {
  228. qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode;
  229. }
  230. Folder *f = addFolderInternal(folderDefinition, account.data(), std::move(vfs));
  231. f->saveToSettings();
  232. continue;
  233. }
  234. // Migration: ._ files sometimes can't be created.
  235. // So if the configured journalPath has a dot-underscore ("._sync_*.db")
  236. // but the current default doesn't have the underscore, switch to the
  237. // new default if no db exists yet.
  238. if (folderDefinition.journalPath.startsWith("._sync_")
  239. && defaultJournalPath.startsWith(".sync_")
  240. && !QFile::exists(folderDefinition.absoluteJournalPath())) {
  241. folderDefinition.journalPath = defaultJournalPath;
  242. }
  243. // Migration: If an old db is found, move it to the new name.
  244. if (backwardsCompatible) {
  245. SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath());
  246. }
  247. const auto switchToVfs = isSwitchToVfsNeeded(folderDefinition);
  248. if (switchToVfs) {
  249. folderDefinition.virtualFilesMode = bestAvailableVfsMode();
  250. }
  251. auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode);
  252. if (!vfs) {
  253. // TODO: Must do better error handling
  254. qFatal("Could not load plugin");
  255. }
  256. Folder *f = addFolderInternal(std::move(folderDefinition), account.data(), std::move(vfs));
  257. if (f) {
  258. if (switchToVfs) {
  259. f->switchToVirtualFiles();
  260. }
  261. // Migrate the old "usePlaceholders" setting to the root folder pin state
  262. if (settings.value(QLatin1String(versionC), 1).toInt() == 1
  263. && settings.value(QLatin1String("usePlaceholders"), false).toBool()) {
  264. qCInfo(lcFolderMan) << "Migrate: From usePlaceholders to PinState::OnlineOnly";
  265. f->setRootPinState(PinState::OnlineOnly);
  266. }
  267. // Migration: Mark folders that shall be saved in a backwards-compatible way
  268. if (backwardsCompatible)
  269. f->setSaveBackwardsCompatible(true);
  270. if (foldersWithPlaceholders)
  271. f->setSaveInFoldersWithPlaceholders();
  272. scheduleFolder(f);
  273. emit folderSyncStateChange(f);
  274. }
  275. }
  276. settings.endGroup();
  277. }
  278. }
  279. int FolderMan::setupFoldersMigration()
  280. {
  281. ConfigFile cfg;
  282. QDir storageDir(cfg.configPath());
  283. _folderConfigPath = cfg.configPath() + QLatin1String("folders");
  284. qCInfo(lcFolderMan) << "Setup folders from " << _folderConfigPath << "(migration)";
  285. QDir dir(_folderConfigPath);
  286. //We need to include hidden files just in case the alias starts with '.'
  287. dir.setFilter(QDir::Files | QDir::Hidden);
  288. const auto list = dir.entryList();
  289. // Normally there should be only one account when migrating.
  290. AccountState *accountState = AccountManager::instance()->accounts().value(0).data();
  291. for (const auto &alias : list) {
  292. Folder *f = setupFolderFromOldConfigFile(alias, accountState);
  293. if (f) {
  294. scheduleFolder(f);
  295. emit folderSyncStateChange(f);
  296. }
  297. }
  298. emit folderListChanged(_folderMap);
  299. // return the number of valid folders.
  300. return _folderMap.size();
  301. }
  302. void FolderMan::backwardMigrationSettingsKeys(QStringList *deleteKeys, QStringList *ignoreKeys)
  303. {
  304. auto settings = ConfigFile::settingsWithGroup(QLatin1String("Accounts"));
  305. auto processSubgroup = [&](const QString &name) {
  306. settings->beginGroup(name);
  307. const int foldersVersion = settings->value(QLatin1String(versionC), 1).toInt();
  308. if (foldersVersion <= maxFoldersVersion) {
  309. foreach (const auto &folderAlias, settings->childGroups()) {
  310. settings->beginGroup(folderAlias);
  311. const int folderVersion = settings->value(QLatin1String(versionC), 1).toInt();
  312. if (folderVersion > FolderDefinition::maxSettingsVersion()) {
  313. ignoreKeys->append(settings->group());
  314. }
  315. settings->endGroup();
  316. }
  317. } else {
  318. deleteKeys->append(settings->group());
  319. }
  320. settings->endGroup();
  321. };
  322. for (const auto &accountId : settings->childGroups()) {
  323. settings->beginGroup(accountId);
  324. processSubgroup("Folders");
  325. processSubgroup("Multifolders");
  326. processSubgroup("FoldersWithPlaceholders");
  327. settings->endGroup();
  328. }
  329. }
  330. bool FolderMan::ensureJournalGone(const QString &journalDbFile)
  331. {
  332. // remove the old journal file
  333. while (QFile::exists(journalDbFile) && !QFile::remove(journalDbFile)) {
  334. qCWarning(lcFolderMan) << "Could not remove old db file at" << journalDbFile;
  335. int ret = QMessageBox::warning(nullptr, tr("Could not reset folder state"),
  336. tr("An old sync journal \"%1\" was found, "
  337. "but could not be removed. Please make sure "
  338. "that no application is currently using it.")
  339. .arg(QDir::fromNativeSeparators(QDir::cleanPath(journalDbFile))),
  340. QMessageBox::Retry | QMessageBox::Abort);
  341. if (ret == QMessageBox::Abort) {
  342. return false;
  343. }
  344. }
  345. return true;
  346. }
  347. #define SLASH_TAG QLatin1String("__SLASH__")
  348. #define BSLASH_TAG QLatin1String("__BSLASH__")
  349. #define QMARK_TAG QLatin1String("__QMARK__")
  350. #define PERCENT_TAG QLatin1String("__PERCENT__")
  351. #define STAR_TAG QLatin1String("__STAR__")
  352. #define COLON_TAG QLatin1String("__COLON__")
  353. #define PIPE_TAG QLatin1String("__PIPE__")
  354. #define QUOTE_TAG QLatin1String("__QUOTE__")
  355. #define LT_TAG QLatin1String("__LESS_THAN__")
  356. #define GT_TAG QLatin1String("__GREATER_THAN__")
  357. #define PAR_O_TAG QLatin1String("__PAR_OPEN__")
  358. #define PAR_C_TAG QLatin1String("__PAR_CLOSE__")
  359. QString FolderMan::escapeAlias(const QString &alias)
  360. {
  361. QString a(alias);
  362. a.replace(QLatin1Char('/'), SLASH_TAG);
  363. a.replace(QLatin1Char('\\'), BSLASH_TAG);
  364. a.replace(QLatin1Char('?'), QMARK_TAG);
  365. a.replace(QLatin1Char('%'), PERCENT_TAG);
  366. a.replace(QLatin1Char('*'), STAR_TAG);
  367. a.replace(QLatin1Char(':'), COLON_TAG);
  368. a.replace(QLatin1Char('|'), PIPE_TAG);
  369. a.replace(QLatin1Char('"'), QUOTE_TAG);
  370. a.replace(QLatin1Char('<'), LT_TAG);
  371. a.replace(QLatin1Char('>'), GT_TAG);
  372. a.replace(QLatin1Char('['), PAR_O_TAG);
  373. a.replace(QLatin1Char(']'), PAR_C_TAG);
  374. return a;
  375. }
  376. SocketApi *FolderMan::socketApi()
  377. {
  378. return this->_socketApi.data();
  379. }
  380. QString FolderMan::unescapeAlias(const QString &alias)
  381. {
  382. QString a(alias);
  383. a.replace(SLASH_TAG, QLatin1String("/"));
  384. a.replace(BSLASH_TAG, QLatin1String("\\"));
  385. a.replace(QMARK_TAG, QLatin1String("?"));
  386. a.replace(PERCENT_TAG, QLatin1String("%"));
  387. a.replace(STAR_TAG, QLatin1String("*"));
  388. a.replace(COLON_TAG, QLatin1String(":"));
  389. a.replace(PIPE_TAG, QLatin1String("|"));
  390. a.replace(QUOTE_TAG, QLatin1String("\""));
  391. a.replace(LT_TAG, QLatin1String("<"));
  392. a.replace(GT_TAG, QLatin1String(">"));
  393. a.replace(PAR_O_TAG, QLatin1String("["));
  394. a.replace(PAR_C_TAG, QLatin1String("]"));
  395. return a;
  396. }
  397. // filename is the name of the file only, it does not include
  398. // the configuration directory path
  399. // WARNING: Do not remove this code, it is used for predefined/automated deployments (2016)
  400. Folder *FolderMan::setupFolderFromOldConfigFile(const QString &file, AccountState *accountState)
  401. {
  402. Folder *folder = nullptr;
  403. qCInfo(lcFolderMan) << " ` -> setting up:" << file;
  404. QString escapedAlias(file);
  405. // check the unescaped variant (for the case when the filename comes out
  406. // of the directory listing). If the file does not exist, escape the
  407. // file and try again.
  408. QFileInfo cfgFile(_folderConfigPath, file);
  409. if (!cfgFile.exists()) {
  410. // try the escaped variant.
  411. escapedAlias = escapeAlias(file);
  412. cfgFile.setFile(_folderConfigPath, escapedAlias);
  413. }
  414. if (!cfgFile.isReadable()) {
  415. qCWarning(lcFolderMan) << "Cannot read folder definition for alias " << cfgFile.filePath();
  416. return folder;
  417. }
  418. QSettings settings(_folderConfigPath + QLatin1Char('/') + escapedAlias, QSettings::IniFormat);
  419. qCInfo(lcFolderMan) << " -> file path: " << settings.fileName();
  420. // Check if the filename is equal to the group setting. If not, use the group
  421. // name as an alias.
  422. QStringList groups = settings.childGroups();
  423. if (!groups.contains(escapedAlias) && groups.count() > 0) {
  424. escapedAlias = groups.first();
  425. }
  426. settings.beginGroup(escapedAlias); // read the group with the same name as the file which is the folder alias
  427. QString path = settings.value(QLatin1String("localPath")).toString();
  428. QString backend = settings.value(QLatin1String("backend")).toString();
  429. QString targetPath = settings.value(QLatin1String("targetPath")).toString();
  430. bool paused = settings.value(QLatin1String("paused"), false).toBool();
  431. // QString connection = settings.value( QLatin1String("connection") ).toString();
  432. QString alias = unescapeAlias(escapedAlias);
  433. if (backend.isEmpty() || backend != QLatin1String("owncloud")) {
  434. qCWarning(lcFolderMan) << "obsolete configuration of type" << backend;
  435. return nullptr;
  436. }
  437. // cut off the leading slash, oCUrl always has a trailing.
  438. if (targetPath.startsWith(QLatin1Char('/'))) {
  439. targetPath.remove(0, 1);
  440. }
  441. if (!accountState) {
  442. qCCritical(lcFolderMan) << "can't create folder without an account";
  443. return nullptr;
  444. }
  445. FolderDefinition folderDefinition;
  446. folderDefinition.alias = alias;
  447. folderDefinition.localPath = path;
  448. folderDefinition.targetPath = targetPath;
  449. folderDefinition.paused = paused;
  450. folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles();
  451. folder = addFolderInternal(folderDefinition, accountState, std::make_unique<VfsOff>());
  452. if (folder) {
  453. QStringList blackList = settings.value(QLatin1String("blackList")).toStringList();
  454. if (!blackList.empty()) {
  455. //migrate settings
  456. folder->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, blackList);
  457. settings.remove(QLatin1String("blackList"));
  458. // FIXME: If you remove this codepath, you need to provide another way to do
  459. // this via theme.h or the normal FolderMan::setupFolders
  460. }
  461. folder->saveToSettings();
  462. }
  463. qCInfo(lcFolderMan) << "Migrated!" << folder;
  464. settings.sync();
  465. return folder;
  466. }
  467. void FolderMan::slotFolderSyncPaused(Folder *f, bool paused)
  468. {
  469. if (!f) {
  470. qCCritical(lcFolderMan) << "slotFolderSyncPaused called with empty folder";
  471. return;
  472. }
  473. if (!paused) {
  474. _disabledFolders.remove(f);
  475. scheduleFolder(f);
  476. } else {
  477. _disabledFolders.insert(f);
  478. }
  479. }
  480. void FolderMan::slotFolderCanSyncChanged()
  481. {
  482. auto *f = qobject_cast<Folder *>(sender());
  483. ASSERT(f);
  484. if (f->canSync()) {
  485. _socketApi->slotRegisterPath(f->alias());
  486. } else {
  487. _socketApi->slotUnregisterPath(f->alias());
  488. }
  489. }
  490. Folder *FolderMan::folder(const QString &alias)
  491. {
  492. if (!alias.isEmpty()) {
  493. if (_folderMap.contains(alias)) {
  494. return _folderMap[alias];
  495. }
  496. }
  497. return nullptr;
  498. }
  499. void FolderMan::scheduleAllFolders()
  500. {
  501. for (Folder *f : _folderMap.values()) {
  502. if (f && f->canSync()) {
  503. scheduleFolder(f);
  504. }
  505. }
  506. }
  507. void FolderMan::slotScheduleAppRestart()
  508. {
  509. _appRestartRequired = true;
  510. qCInfo(lcFolderMan) << "Application restart requested!";
  511. }
  512. void FolderMan::slotSyncOnceFileUnlocks(const QString &path)
  513. {
  514. _lockWatcher->addFile(path);
  515. }
  516. /*
  517. * if a folder wants to be synced, it calls this slot and is added
  518. * to the queue. The slot to actually start a sync is called afterwards.
  519. */
  520. void FolderMan::scheduleFolder(Folder *f)
  521. {
  522. if (!f) {
  523. qCCritical(lcFolderMan) << "slotScheduleSync called with null folder";
  524. return;
  525. }
  526. auto alias = f->alias();
  527. qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync!";
  528. if (!_scheduledFolders.contains(f)) {
  529. if (!f->canSync()) {
  530. qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!";
  531. _socketApi->slotUpdateFolderView(f);
  532. return;
  533. }
  534. f->prepareToSync();
  535. emit folderSyncStateChange(f);
  536. _scheduledFolders.enqueue(f);
  537. emit scheduleQueueChanged();
  538. } else {
  539. qCInfo(lcFolderMan) << "Sync for folder " << alias << " already scheduled, do not enqueue!";
  540. }
  541. startScheduledSyncSoon();
  542. }
  543. void FolderMan::scheduleFolderNext(Folder *f)
  544. {
  545. auto alias = f->alias();
  546. qCInfo(lcFolderMan) << "Schedule folder " << alias << " to sync! Front-of-queue.";
  547. if (!f->canSync()) {
  548. qCInfo(lcFolderMan) << "Folder is not ready to sync, not scheduled!";
  549. return;
  550. }
  551. _scheduledFolders.removeAll(f);
  552. f->prepareToSync();
  553. emit folderSyncStateChange(f);
  554. _scheduledFolders.prepend(f);
  555. emit scheduleQueueChanged();
  556. startScheduledSyncSoon();
  557. }
  558. void FolderMan::slotScheduleETagJob(const QString & /*alias*/, RequestEtagJob *job)
  559. {
  560. QObject::connect(job, &QObject::destroyed, this, &FolderMan::slotEtagJobDestroyed);
  561. QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection);
  562. // maybe: add to queue
  563. }
  564. void FolderMan::slotEtagJobDestroyed(QObject * /*o*/)
  565. {
  566. // _currentEtagJob is automatically cleared
  567. // maybe: remove from queue
  568. QMetaObject::invokeMethod(this, "slotRunOneEtagJob", Qt::QueuedConnection);
  569. }
  570. void FolderMan::slotRunOneEtagJob()
  571. {
  572. if (_currentEtagJob.isNull()) {
  573. Folder *folder = nullptr;
  574. for (Folder *f : qAsConst(_folderMap)) {
  575. if (f->etagJob()) {
  576. // Caveat: always grabs the first folder with a job, but we think this is Ok for now and avoids us having a seperate queue.
  577. _currentEtagJob = f->etagJob();
  578. folder = f;
  579. break;
  580. }
  581. }
  582. if (_currentEtagJob.isNull()) {
  583. //qCDebug(lcFolderMan) << "No more remote ETag check jobs to schedule.";
  584. /* now it might be a good time to check for restarting... */
  585. if (!isAnySyncRunning() && _appRestartRequired) {
  586. restartApplication();
  587. }
  588. } else {
  589. qCDebug(lcFolderMan) << "Scheduling" << folder->remoteUrl().toString() << "to check remote ETag";
  590. _currentEtagJob->start(); // on destroy/end it will continue the queue via slotEtagJobDestroyed
  591. }
  592. }
  593. }
  594. void FolderMan::slotAccountStateChanged()
  595. {
  596. auto *accountState = qobject_cast<AccountState *>(sender());
  597. if (!accountState) {
  598. return;
  599. }
  600. QString accountName = accountState->account()->displayName();
  601. if (accountState->isConnected()) {
  602. qCInfo(lcFolderMan) << "Account" << accountName << "connected, scheduling its folders";
  603. for (Folder *f : _folderMap.values()) {
  604. if (f
  605. && f->canSync()
  606. && f->accountState() == accountState) {
  607. scheduleFolder(f);
  608. }
  609. }
  610. } else {
  611. qCInfo(lcFolderMan) << "Account" << accountName << "disconnected or paused, "
  612. "terminating or descheduling sync folders";
  613. foreach (Folder *f, _folderMap.values()) {
  614. if (f
  615. && f->isSyncRunning()
  616. && f->accountState() == accountState) {
  617. f->slotTerminateSync();
  618. }
  619. }
  620. QMutableListIterator<Folder *> it(_scheduledFolders);
  621. while (it.hasNext()) {
  622. Folder *f = it.next();
  623. if (f->accountState() == accountState) {
  624. it.remove();
  625. }
  626. }
  627. emit scheduleQueueChanged();
  628. }
  629. }
  630. // only enable or disable foldermans will schedule and do syncs.
  631. // this is not the same as Pause and Resume of folders.
  632. void FolderMan::setSyncEnabled(bool enabled)
  633. {
  634. if (!_syncEnabled && enabled && !_scheduledFolders.isEmpty()) {
  635. // We have things in our queue that were waiting for the connection to come back on.
  636. startScheduledSyncSoon();
  637. }
  638. _syncEnabled = enabled;
  639. // force a redraw in case the network connect status changed
  640. emit(folderSyncStateChange(nullptr));
  641. }
  642. void FolderMan::startScheduledSyncSoon()
  643. {
  644. if (_startScheduledSyncTimer.isActive()) {
  645. return;
  646. }
  647. if (_scheduledFolders.empty()) {
  648. return;
  649. }
  650. if (isAnySyncRunning()) {
  651. return;
  652. }
  653. qint64 msDelay = 100; // 100ms minimum delay
  654. qint64 msSinceLastSync = 0;
  655. // Require a pause based on the duration of the last sync run.
  656. if (Folder *lastFolder = _lastSyncFolder) {
  657. msSinceLastSync = lastFolder->msecSinceLastSync().count();
  658. // 1s -> 1.5s pause
  659. // 10s -> 5s pause
  660. // 1min -> 12s pause
  661. // 1h -> 90s pause
  662. qint64 pause = qSqrt(lastFolder->msecLastSyncDuration().count()) / 20.0 * 1000.0;
  663. msDelay = qMax(msDelay, pause);
  664. }
  665. // Delays beyond one minute seem too big, particularly since there
  666. // could be things later in the queue that shouldn't be punished by a
  667. // long delay!
  668. msDelay = qMin(msDelay, 60 * 1000ll);
  669. // Time since the last sync run counts against the delay
  670. msDelay = qMax(1ll, msDelay - msSinceLastSync);
  671. qCInfo(lcFolderMan) << "Starting the next scheduled sync in" << (msDelay / 1000) << "seconds";
  672. _startScheduledSyncTimer.start(msDelay);
  673. }
  674. /*
  675. * slot to start folder syncs.
  676. * It is either called from the slot where folders enqueue themselves for
  677. * syncing or after a folder sync was finished.
  678. */
  679. void FolderMan::slotStartScheduledFolderSync()
  680. {
  681. if (isAnySyncRunning()) {
  682. for (auto f : _folderMap) {
  683. if (f->isSyncRunning())
  684. qCInfo(lcFolderMan) << "Currently folder " << f->remoteUrl().toString() << " is running, wait for finish!";
  685. }
  686. return;
  687. }
  688. if (!_syncEnabled) {
  689. qCInfo(lcFolderMan) << "FolderMan: Syncing is disabled, no scheduling.";
  690. return;
  691. }
  692. qCDebug(lcFolderMan) << "folderQueue size: " << _scheduledFolders.count();
  693. if (_scheduledFolders.isEmpty()) {
  694. return;
  695. }
  696. // Find the first folder in the queue that can be synced.
  697. Folder *folder = nullptr;
  698. while (!_scheduledFolders.isEmpty()) {
  699. Folder *g = _scheduledFolders.dequeue();
  700. if (g->canSync()) {
  701. folder = g;
  702. break;
  703. }
  704. }
  705. emit scheduleQueueChanged();
  706. // Start syncing this folder!
  707. if (folder) {
  708. // Safe to call several times, and necessary to try again if
  709. // the folder path didn't exist previously.
  710. folder->registerFolderWatcher();
  711. registerFolderWithSocketApi(folder);
  712. _currentSyncFolder = folder;
  713. folder->startSync(QStringList());
  714. }
  715. }
  716. bool FolderMan::pushNotificationsFilesReady(Account *account)
  717. {
  718. const auto pushNotifications = account->pushNotifications();
  719. const auto pushFilesAvailable = account->capabilities().availablePushNotifications() & PushNotificationType::Files;
  720. return pushFilesAvailable && pushNotifications && pushNotifications->isReady();
  721. }
  722. bool FolderMan::isSwitchToVfsNeeded(const FolderDefinition &folderDefinition) const
  723. {
  724. auto result = false;
  725. if (ENFORCE_VIRTUAL_FILES_SYNC_FOLDER &&
  726. folderDefinition.virtualFilesMode != bestAvailableVfsMode() &&
  727. folderDefinition.virtualFilesMode == Vfs::Off &&
  728. OCC::Theme::instance()->showVirtualFilesOption()) {
  729. result = true;
  730. }
  731. return result;
  732. }
  733. void FolderMan::slotEtagPollTimerTimeout()
  734. {
  735. qCInfo(lcFolderMan) << "Etag poll timer timeout";
  736. const auto folderMapValues = _folderMap.values();
  737. qCInfo(lcFolderMan) << "Folders to sync:" << folderMapValues.size();
  738. QList<Folder *> foldersToRun;
  739. // Some folders need not to be checked because they use the push notifications
  740. std::copy_if(folderMapValues.begin(), folderMapValues.end(), std::back_inserter(foldersToRun), [this](Folder *folder) -> bool {
  741. const auto account = folder->accountState()->account();
  742. return !pushNotificationsFilesReady(account.data());
  743. });
  744. qCInfo(lcFolderMan) << "Number of folders that don't use push notifications:" << foldersToRun.size();
  745. runEtagJobsIfPossible(foldersToRun);
  746. }
  747. void FolderMan::runEtagJobsIfPossible(const QList<Folder *> &folderMap)
  748. {
  749. for (auto folder : folderMap) {
  750. runEtagJobIfPossible(folder);
  751. }
  752. }
  753. void FolderMan::runEtagJobIfPossible(Folder *folder)
  754. {
  755. const ConfigFile cfg;
  756. const auto polltime = cfg.remotePollInterval();
  757. qCInfo(lcFolderMan) << "Run etag job on folder" << folder;
  758. if (!folder) {
  759. return;
  760. }
  761. if (folder->isSyncRunning()) {
  762. qCInfo(lcFolderMan) << "Can not run etag job: Sync is running";
  763. return;
  764. }
  765. if (_scheduledFolders.contains(folder)) {
  766. qCInfo(lcFolderMan) << "Can not run etag job: Folder is alreday scheduled";
  767. return;
  768. }
  769. if (_disabledFolders.contains(folder)) {
  770. qCInfo(lcFolderMan) << "Can not run etag job: Folder is disabled";
  771. return;
  772. }
  773. if (folder->etagJob() || folder->isBusy() || !folder->canSync()) {
  774. qCInfo(lcFolderMan) << "Can not run etag job: Folder is busy";
  775. return;
  776. }
  777. // When not using push notifications, make sure polltime is reached
  778. if (!pushNotificationsFilesReady(folder->accountState()->account().data())) {
  779. if (folder->msecSinceLastSync() < polltime) {
  780. qCInfo(lcFolderMan) << "Can not run etag job: Polltime not reached";
  781. return;
  782. }
  783. }
  784. QMetaObject::invokeMethod(folder, "slotRunEtagJob", Qt::QueuedConnection);
  785. }
  786. void FolderMan::slotAccountRemoved(AccountState *accountState)
  787. {
  788. for (const auto &folder : qAsConst(_folderMap)) {
  789. if (folder->accountState() == accountState) {
  790. folder->onAssociatedAccountRemoved();
  791. }
  792. }
  793. }
  794. void FolderMan::slotRemoveFoldersForAccount(AccountState *accountState)
  795. {
  796. QVarLengthArray<Folder *, 16> foldersToRemove;
  797. Folder::MapIterator i(_folderMap);
  798. while (i.hasNext()) {
  799. i.next();
  800. Folder *folder = i.value();
  801. if (folder->accountState() == accountState) {
  802. foldersToRemove.append(folder);
  803. }
  804. }
  805. for (const auto &f : qAsConst(foldersToRemove)) {
  806. removeFolder(f);
  807. }
  808. emit folderListChanged(_folderMap);
  809. }
  810. void FolderMan::slotForwardFolderSyncStateChange()
  811. {
  812. if (auto *f = qobject_cast<Folder *>(sender())) {
  813. emit folderSyncStateChange(f);
  814. }
  815. }
  816. void FolderMan::slotServerVersionChanged(Account *account)
  817. {
  818. // Pause folders if the server version is unsupported
  819. if (account->serverVersionUnsupported()) {
  820. qCWarning(lcFolderMan) << "The server version is unsupported:" << account->serverVersion()
  821. << "pausing all folders on the account";
  822. for (auto &f : qAsConst(_folderMap)) {
  823. if (f->accountState()->account().data() == account) {
  824. f->setSyncPaused(true);
  825. }
  826. }
  827. }
  828. }
  829. void FolderMan::slotWatchedFileUnlocked(const QString &path)
  830. {
  831. if (Folder *f = folderForPath(path)) {
  832. // Treat this equivalently to the file being reported by the file watcher
  833. f->slotWatchedPathChanged(path, Folder::ChangeReason::UnLock);
  834. }
  835. }
  836. void FolderMan::slotScheduleFolderByTime()
  837. {
  838. for (const auto &f : qAsConst(_folderMap)) {
  839. // Never schedule if syncing is disabled or when we're currently
  840. // querying the server for etags
  841. if (!f->canSync() || f->etagJob()) {
  842. continue;
  843. }
  844. auto msecsSinceSync = f->msecSinceLastSync();
  845. // Possibly it's just time for a new sync run
  846. bool forceSyncIntervalExpired = msecsSinceSync > ConfigFile().forceSyncInterval();
  847. if (forceSyncIntervalExpired) {
  848. qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
  849. << "because it has been" << msecsSinceSync.count() << "ms "
  850. << "since the last sync";
  851. scheduleFolder(f);
  852. continue;
  853. }
  854. // Retry a couple of times after failure; or regularly if requested
  855. bool syncAgain =
  856. (f->consecutiveFailingSyncs() > 0 && f->consecutiveFailingSyncs() < 3)
  857. || f->syncEngine().isAnotherSyncNeeded() == DelayedFollowUp;
  858. auto syncAgainDelay = std::chrono::seconds(10); // 10s for the first retry-after-fail
  859. if (f->consecutiveFailingSyncs() > 1)
  860. syncAgainDelay = std::chrono::seconds(60); // 60s for each further attempt
  861. if (syncAgain && msecsSinceSync > syncAgainDelay) {
  862. qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
  863. << ", the last" << f->consecutiveFailingSyncs() << "syncs failed"
  864. << ", anotherSyncNeeded" << f->syncEngine().isAnotherSyncNeeded()
  865. << ", last status:" << f->syncResult().statusString()
  866. << ", time since last sync:" << msecsSinceSync.count();
  867. scheduleFolder(f);
  868. continue;
  869. }
  870. // Do we want to retry failing syncs or another-sync-needed runs more often?
  871. }
  872. }
  873. bool FolderMan::isAnySyncRunning() const
  874. {
  875. if (_currentSyncFolder)
  876. return true;
  877. for (auto f : _folderMap) {
  878. if (f->isSyncRunning())
  879. return true;
  880. }
  881. return false;
  882. }
  883. void FolderMan::slotFolderSyncStarted()
  884. {
  885. auto f = qobject_cast<Folder *>(sender());
  886. ASSERT(f);
  887. if (!f)
  888. return;
  889. qCInfo(lcFolderMan, ">========== Sync started for folder [%s] of account [%s] with remote [%s]",
  890. qPrintable(f->shortGuiLocalPath()),
  891. qPrintable(f->accountState()->account()->displayName()),
  892. qPrintable(f->remoteUrl().toString()));
  893. }
  894. /*
  895. * a folder indicates that its syncing is finished.
  896. * Start the next sync after the system had some milliseconds to breath.
  897. * This delay is particularly useful to avoid late file change notifications
  898. * (that we caused ourselves by syncing) from triggering another spurious sync.
  899. */
  900. void FolderMan::slotFolderSyncFinished(const SyncResult &)
  901. {
  902. auto f = qobject_cast<Folder *>(sender());
  903. ASSERT(f);
  904. if (!f)
  905. return;
  906. qCInfo(lcFolderMan, "<========== Sync finished for folder [%s] of account [%s] with remote [%s]",
  907. qPrintable(f->shortGuiLocalPath()),
  908. qPrintable(f->accountState()->account()->displayName()),
  909. qPrintable(f->remoteUrl().toString()));
  910. if (f == _currentSyncFolder) {
  911. _lastSyncFolder = _currentSyncFolder;
  912. _currentSyncFolder = nullptr;
  913. }
  914. if (!isAnySyncRunning())
  915. startScheduledSyncSoon();
  916. }
  917. Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition &folderDefinition)
  918. {
  919. // Choose a db filename
  920. auto definition = folderDefinition;
  921. definition.journalPath = definition.defaultJournalPath(accountState->account());
  922. if (!ensureJournalGone(definition.absoluteJournalPath())) {
  923. return nullptr;
  924. }
  925. auto vfs = createVfsFromPlugin(folderDefinition.virtualFilesMode);
  926. if (!vfs) {
  927. qCWarning(lcFolderMan) << "Could not load plugin for mode" << folderDefinition.virtualFilesMode;
  928. return nullptr;
  929. }
  930. auto folder = addFolderInternal(definition, accountState, std::move(vfs));
  931. // Migration: The first account that's configured for a local folder shall
  932. // be saved in a backwards-compatible way.
  933. const auto folderList = FolderMan::instance()->map();
  934. const auto oneAccountOnly = std::none_of(folderList.cbegin(), folderList.cend(), [folder](const auto *other) {
  935. return other != folder && other->cleanPath() == folder->cleanPath();
  936. });
  937. folder->setSaveBackwardsCompatible(oneAccountOnly);
  938. if (folder) {
  939. folder->setSaveBackwardsCompatible(oneAccountOnly);
  940. folder->saveToSettings();
  941. emit folderSyncStateChange(folder);
  942. emit folderListChanged(_folderMap);
  943. }
  944. _navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
  945. return folder;
  946. }
  947. Folder *FolderMan::addFolderInternal(
  948. FolderDefinition folderDefinition,
  949. AccountState *accountState,
  950. std::unique_ptr<Vfs> vfs)
  951. {
  952. auto alias = folderDefinition.alias;
  953. int count = 0;
  954. while (folderDefinition.alias.isEmpty()
  955. || _folderMap.contains(folderDefinition.alias)
  956. || _additionalBlockedFolderAliases.contains(folderDefinition.alias)) {
  957. // There is already a folder configured with this name and folder names need to be unique
  958. folderDefinition.alias = alias + QString::number(++count);
  959. }
  960. auto folder = new Folder(folderDefinition, accountState, std::move(vfs), this);
  961. if (_navigationPaneHelper.showInExplorerNavigationPane() && folderDefinition.navigationPaneClsid.isNull()) {
  962. folder->setNavigationPaneClsid(QUuid::createUuid());
  963. folder->saveToSettings();
  964. }
  965. qCInfo(lcFolderMan) << "Adding folder to Folder Map " << folder << folder->alias();
  966. _folderMap[folder->alias()] = folder;
  967. if (folder->syncPaused()) {
  968. _disabledFolders.insert(folder);
  969. }
  970. // See matching disconnects in unloadFolder().
  971. connect(folder, &Folder::syncStarted, this, &FolderMan::slotFolderSyncStarted);
  972. connect(folder, &Folder::syncFinished, this, &FolderMan::slotFolderSyncFinished);
  973. connect(folder, &Folder::syncStateChange, this, &FolderMan::slotForwardFolderSyncStateChange);
  974. connect(folder, &Folder::syncPausedChanged, this, &FolderMan::slotFolderSyncPaused);
  975. connect(folder, &Folder::canSyncChanged, this, &FolderMan::slotFolderCanSyncChanged);
  976. connect(&folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::fileStatusChanged,
  977. _socketApi.data(), &SocketApi::broadcastStatusPushMessage);
  978. connect(folder, &Folder::watchedFileChangedExternally,
  979. &folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched);
  980. folder->registerFolderWatcher();
  981. registerFolderWithSocketApi(folder);
  982. return folder;
  983. }
  984. Folder *FolderMan::folderForPath(const QString &path)
  985. {
  986. QString absolutePath = QDir::cleanPath(path) + QLatin1Char('/');
  987. const auto folders = this->map().values();
  988. const auto it = std::find_if(folders.cbegin(), folders.cend(), [absolutePath](const auto *folder) {
  989. const QString folderPath = folder->cleanPath() + QLatin1Char('/');
  990. return absolutePath.startsWith(folderPath, (Utility::isWindows() || Utility::isMac()) ? Qt::CaseInsensitive : Qt::CaseSensitive);
  991. });
  992. return it != folders.cend() ? *it : nullptr;
  993. }
  994. QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const AccountPtr acc)
  995. {
  996. QStringList re;
  997. // We'll be comparing against Folder::remotePath which always starts with /
  998. QString serverPath = relPath;
  999. if (!serverPath.startsWith('/'))
  1000. serverPath.prepend('/');
  1001. for (Folder *folder : this->map().values()) {
  1002. if (acc && folder->accountState()->account() != acc) {
  1003. continue;
  1004. }
  1005. if (!serverPath.startsWith(folder->remotePath()))
  1006. continue;
  1007. QString path = folder->cleanPath() + '/';
  1008. path += serverPath.midRef(folder->remotePathTrailingSlash().length());
  1009. if (QFile::exists(path)) {
  1010. re.append(path);
  1011. }
  1012. }
  1013. return re;
  1014. }
  1015. void FolderMan::removeFolder(Folder *f)
  1016. {
  1017. if (!f) {
  1018. qCCritical(lcFolderMan) << "Can not remove null folder";
  1019. return;
  1020. }
  1021. qCInfo(lcFolderMan) << "Removing " << f->alias();
  1022. const bool currentlyRunning = f->isSyncRunning();
  1023. if (currentlyRunning) {
  1024. // abort the sync now
  1025. f->slotTerminateSync();
  1026. }
  1027. if (_scheduledFolders.removeAll(f) > 0) {
  1028. emit scheduleQueueChanged();
  1029. }
  1030. f->setSyncPaused(true);
  1031. f->wipeForRemoval();
  1032. // remove the folder configuration
  1033. f->removeFromSettings();
  1034. unloadFolder(f);
  1035. if (currentlyRunning) {
  1036. // We want to schedule the next folder once this is done
  1037. connect(f, &Folder::syncFinished,
  1038. this, &FolderMan::slotFolderSyncFinished);
  1039. // Let the folder delete itself when done.
  1040. connect(f, &Folder::syncFinished, f, &QObject::deleteLater);
  1041. } else {
  1042. delete f;
  1043. }
  1044. _navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
  1045. emit folderListChanged(_folderMap);
  1046. }
  1047. QString FolderMan::getBackupName(QString fullPathName) const
  1048. {
  1049. if (fullPathName.endsWith("/"))
  1050. fullPathName.chop(1);
  1051. if (fullPathName.isEmpty())
  1052. return QString();
  1053. QString newName = fullPathName + tr(" (backup)");
  1054. QFileInfo fi(newName);
  1055. int cnt = 2;
  1056. do {
  1057. if (fi.exists()) {
  1058. newName = fullPathName + tr(" (backup %1)").arg(cnt++);
  1059. fi.setFile(newName);
  1060. }
  1061. } while (fi.exists());
  1062. return newName;
  1063. }
  1064. bool FolderMan::startFromScratch(const QString &localFolder)
  1065. {
  1066. if (localFolder.isEmpty()) {
  1067. return false;
  1068. }
  1069. QFileInfo fi(localFolder);
  1070. QDir parentDir(fi.dir());
  1071. QString folderName = fi.fileName();
  1072. // Adjust for case where localFolder ends with a /
  1073. if (fi.isDir()) {
  1074. folderName = parentDir.dirName();
  1075. parentDir.cdUp();
  1076. }
  1077. if (fi.exists()) {
  1078. // It exists, but is empty -> just reuse it.
  1079. if (fi.isDir() && fi.dir().count() == 0) {
  1080. qCDebug(lcFolderMan) << "startFromScratch: Directory is empty!";
  1081. return true;
  1082. }
  1083. // Disconnect the socket api from the database to avoid that locking of the
  1084. // db file does not allow to move this dir.
  1085. Folder *f = folderForPath(localFolder);
  1086. if (f) {
  1087. if (localFolder.startsWith(f->path())) {
  1088. _socketApi->slotUnregisterPath(f->alias());
  1089. }
  1090. f->journalDb()->close();
  1091. f->slotTerminateSync(); // Normally it should not be running, but viel hilft viel
  1092. }
  1093. // Make a backup of the folder/file.
  1094. QString newName = getBackupName(parentDir.absoluteFilePath(folderName));
  1095. QString renameError;
  1096. if (!FileSystem::rename(fi.absoluteFilePath(), newName, &renameError)) {
  1097. qCWarning(lcFolderMan) << "startFromScratch: Could not rename" << fi.absoluteFilePath()
  1098. << "to" << newName << "error:" << renameError;
  1099. return false;
  1100. }
  1101. }
  1102. if (!parentDir.mkdir(fi.absoluteFilePath())) {
  1103. qCWarning(lcFolderMan) << "startFromScratch: Could not mkdir" << fi.absoluteFilePath();
  1104. return false;
  1105. }
  1106. return true;
  1107. }
  1108. void FolderMan::slotWipeFolderForAccount(AccountState *accountState)
  1109. {
  1110. QVarLengthArray<Folder *, 16> foldersToRemove;
  1111. Folder::MapIterator i(_folderMap);
  1112. while (i.hasNext()) {
  1113. i.next();
  1114. Folder *folder = i.value();
  1115. if (folder->accountState() == accountState) {
  1116. foldersToRemove.append(folder);
  1117. }
  1118. }
  1119. bool success = false;
  1120. for (const auto &f : qAsConst(foldersToRemove)) {
  1121. if (!f) {
  1122. qCCritical(lcFolderMan) << "Can not remove null folder";
  1123. return;
  1124. }
  1125. qCInfo(lcFolderMan) << "Removing " << f->alias();
  1126. const bool currentlyRunning = (_currentSyncFolder == f);
  1127. if (currentlyRunning) {
  1128. // abort the sync now
  1129. _currentSyncFolder->slotTerminateSync();
  1130. }
  1131. if (_scheduledFolders.removeAll(f) > 0) {
  1132. emit scheduleQueueChanged();
  1133. }
  1134. // wipe database
  1135. f->wipeForRemoval();
  1136. // wipe data
  1137. QDir userFolder(f->path());
  1138. if (userFolder.exists()) {
  1139. success = userFolder.removeRecursively();
  1140. if (!success) {
  1141. qCWarning(lcFolderMan) << "Failed to remove existing folder " << f->path();
  1142. } else {
  1143. qCInfo(lcFolderMan) << "wipe: Removed file " << f->path();
  1144. }
  1145. } else {
  1146. success = true;
  1147. qCWarning(lcFolderMan) << "folder does not exist, can not remove.";
  1148. }
  1149. f->setSyncPaused(true);
  1150. // remove the folder configuration
  1151. f->removeFromSettings();
  1152. unloadFolder(f);
  1153. if (currentlyRunning) {
  1154. delete f;
  1155. }
  1156. _navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
  1157. }
  1158. emit folderListChanged(_folderMap);
  1159. emit wipeDone(accountState, success);
  1160. }
  1161. void FolderMan::setDirtyProxy()
  1162. {
  1163. for (const Folder *f : _folderMap.values()) {
  1164. if (f) {
  1165. if (f->accountState() && f->accountState()->account()
  1166. && f->accountState()->account()->networkAccessManager()) {
  1167. // Need to do this so we do not use the old determined system proxy
  1168. f->accountState()->account()->networkAccessManager()->setProxy(
  1169. QNetworkProxy(QNetworkProxy::DefaultProxy));
  1170. }
  1171. }
  1172. }
  1173. }
  1174. void FolderMan::setDirtyNetworkLimits()
  1175. {
  1176. for (Folder *f : _folderMap.values()) {
  1177. // set only in busy folders. Otherwise they read the config anyway.
  1178. if (f && f->isBusy()) {
  1179. f->setDirtyNetworkLimits();
  1180. }
  1181. }
  1182. }
  1183. void FolderMan::editFileLocally(const QString &accountDisplayName, const QString &relPath)
  1184. {
  1185. const auto showError = [this](const OCC::AccountStatePtr accountState, const QString &errorMessage, const QString &subject) {
  1186. if (accountState && accountState->account()) {
  1187. const auto foundFolder = std::find_if(std::cbegin(map()), std::cend(map()), [accountState](const auto &folder) {
  1188. return accountState->account()->davUrl() == folder->remoteUrl();
  1189. });
  1190. if (foundFolder != std::cend(map())) {
  1191. (*foundFolder)->syncEngine().addErrorToGui(SyncFileItem::SoftError, errorMessage, subject);
  1192. }
  1193. }
  1194. // to make sure the error is not missed, show a message box in addition
  1195. const auto messageBox = new QMessageBox;
  1196. messageBox->setAttribute(Qt::WA_DeleteOnClose);
  1197. messageBox->setText(errorMessage);
  1198. messageBox->setInformativeText(subject);
  1199. messageBox->setIcon(QMessageBox::Warning);
  1200. messageBox->addButton(QMessageBox::StandardButton::Ok);
  1201. messageBox->show();
  1202. messageBox->activateWindow();
  1203. messageBox->raise();
  1204. };
  1205. const auto accountFound = AccountManager::instance()->account(accountDisplayName);
  1206. if (!accountFound) {
  1207. qCWarning(lcFolderMan) << "Could not find an account " << accountDisplayName << " to edit file " << relPath << " locally.";
  1208. showError(accountFound, tr("Could not find an account for local editing"), accountDisplayName);
  1209. return;
  1210. }
  1211. const auto foundFiles = findFileInLocalFolders(relPath, accountFound->account());
  1212. if (foundFiles.isEmpty()) {
  1213. for (const auto &folder : map()) {
  1214. bool result = false;
  1215. const auto excludedThroughSelectiveSync = folder->journalDb()->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &result);
  1216. for (const auto &excludedPath : excludedThroughSelectiveSync) {
  1217. if (relPath.startsWith(excludedPath)) {
  1218. showError(accountFound, tr("Could not find a file for local editing. Make sure it is not excluded via selective sync."), relPath);
  1219. return;
  1220. }
  1221. }
  1222. }
  1223. showError(accountFound, tr("Could not find a file for local editing. Make sure its path is valid and it is synced locally."), relPath);
  1224. return;
  1225. }
  1226. // In case the VFS mode is enabled and a file is not yet hydrated, we must call QDesktopServices::openUrl from a separate thread, or, there will be a freeze.
  1227. // To avoid searching for a specific folder and checking if the VFS is enabled - we just always call it from a separate thread.
  1228. QtConcurrent::run([foundFiles] {
  1229. QDesktopServices::openUrl(QUrl::fromLocalFile(foundFiles.first()));
  1230. });
  1231. }
  1232. void FolderMan::trayOverallStatus(const QList<Folder *> &folders,
  1233. SyncResult::Status *status, bool *unresolvedConflicts)
  1234. {
  1235. *status = SyncResult::Undefined;
  1236. *unresolvedConflicts = false;
  1237. int cnt = folders.count();
  1238. // if one folder: show the state of the one folder.
  1239. // if more folders:
  1240. // if one of them has an error -> show error
  1241. // if one is paused, but others ok, show ok
  1242. // do not show "problem" in the tray
  1243. //
  1244. if (cnt == 1) {
  1245. Folder *folder = folders.at(0);
  1246. if (folder) {
  1247. auto syncResult = folder->syncResult();
  1248. if (folder->syncPaused()) {
  1249. *status = SyncResult::Paused;
  1250. } else {
  1251. SyncResult::Status syncStatus = syncResult.status();
  1252. switch (syncStatus) {
  1253. case SyncResult::Undefined:
  1254. *status = SyncResult::Error;
  1255. break;
  1256. case SyncResult::Problem: // don't show the problem icon in tray.
  1257. *status = SyncResult::Success;
  1258. break;
  1259. default:
  1260. *status = syncStatus;
  1261. break;
  1262. }
  1263. }
  1264. *unresolvedConflicts = syncResult.hasUnresolvedConflicts();
  1265. }
  1266. } else {
  1267. int errorsSeen = 0;
  1268. int goodSeen = 0;
  1269. int abortOrPausedSeen = 0;
  1270. int runSeen = 0;
  1271. int various = 0;
  1272. for (const Folder *folder : qAsConst(folders)) {
  1273. SyncResult folderResult = folder->syncResult();
  1274. if (folder->syncPaused()) {
  1275. abortOrPausedSeen++;
  1276. } else {
  1277. SyncResult::Status syncStatus = folderResult.status();
  1278. switch (syncStatus) {
  1279. case SyncResult::Undefined:
  1280. case SyncResult::NotYetStarted:
  1281. various++;
  1282. break;
  1283. case SyncResult::SyncPrepare:
  1284. case SyncResult::SyncRunning:
  1285. runSeen++;
  1286. break;
  1287. case SyncResult::Problem: // don't show the problem icon in tray.
  1288. case SyncResult::Success:
  1289. goodSeen++;
  1290. break;
  1291. case SyncResult::Error:
  1292. case SyncResult::SetupError:
  1293. errorsSeen++;
  1294. break;
  1295. case SyncResult::SyncAbortRequested:
  1296. case SyncResult::Paused:
  1297. abortOrPausedSeen++;
  1298. // no default case on purpose, check compiler warnings
  1299. }
  1300. }
  1301. if (folderResult.hasUnresolvedConflicts())
  1302. *unresolvedConflicts = true;
  1303. }
  1304. if (errorsSeen > 0) {
  1305. *status = SyncResult::Error;
  1306. } else if (abortOrPausedSeen > 0 && abortOrPausedSeen == cnt) {
  1307. // only if all folders are paused
  1308. *status = SyncResult::Paused;
  1309. } else if (runSeen > 0) {
  1310. *status = SyncResult::SyncRunning;
  1311. } else if (goodSeen > 0) {
  1312. *status = SyncResult::Success;
  1313. }
  1314. }
  1315. }
  1316. QString FolderMan::trayTooltipStatusString(
  1317. SyncResult::Status syncStatus, bool hasUnresolvedConflicts, bool paused)
  1318. {
  1319. QString folderMessage;
  1320. switch (syncStatus) {
  1321. case SyncResult::Undefined:
  1322. folderMessage = tr("Undefined state.");
  1323. break;
  1324. case SyncResult::NotYetStarted:
  1325. folderMessage = tr("Waiting to start syncing.");
  1326. break;
  1327. case SyncResult::SyncPrepare:
  1328. folderMessage = tr("Preparing for sync.");
  1329. break;
  1330. case SyncResult::SyncRunning:
  1331. folderMessage = tr("Sync is running.");
  1332. break;
  1333. case SyncResult::Success:
  1334. case SyncResult::Problem:
  1335. if (hasUnresolvedConflicts) {
  1336. folderMessage = tr("Sync finished with unresolved conflicts.");
  1337. } else {
  1338. folderMessage = tr("Last sync was successful.");
  1339. }
  1340. break;
  1341. case SyncResult::Error:
  1342. break;
  1343. case SyncResult::SetupError:
  1344. folderMessage = tr("Setup error.");
  1345. break;
  1346. case SyncResult::SyncAbortRequested:
  1347. folderMessage = tr("Sync request was cancelled.");
  1348. break;
  1349. case SyncResult::Paused:
  1350. folderMessage = tr("Sync is paused.");
  1351. break;
  1352. // no default case on purpose, check compiler warnings
  1353. }
  1354. if (paused) {
  1355. // sync is disabled.
  1356. folderMessage = tr("%1 (Sync is paused)").arg(folderMessage);
  1357. }
  1358. return folderMessage;
  1359. }
  1360. static QString checkPathValidityRecursive(const QString &path)
  1361. {
  1362. if (path.isEmpty()) {
  1363. return FolderMan::tr("No valid folder selected!");
  1364. }
  1365. #ifdef Q_OS_WIN
  1366. Utility::NtfsPermissionLookupRAII ntfs_perm;
  1367. #endif
  1368. const QFileInfo selFile(path);
  1369. if (!selFile.exists()) {
  1370. QString parentPath = selFile.dir().path();
  1371. if (parentPath != path)
  1372. return checkPathValidityRecursive(parentPath);
  1373. return FolderMan::tr("The selected path does not exist!");
  1374. }
  1375. if (!selFile.isDir()) {
  1376. return FolderMan::tr("The selected path is not a folder!");
  1377. }
  1378. if (!selFile.isWritable()) {
  1379. return FolderMan::tr("You have no permission to write to the selected folder!");
  1380. }
  1381. return QString();
  1382. }
  1383. // QFileInfo::canonicalPath returns an empty string if the file does not exist.
  1384. // This function also works with files that does not exist and resolve the symlinks in the
  1385. // parent directories.
  1386. static QString canonicalPath(const QString &path)
  1387. {
  1388. QFileInfo selFile(path);
  1389. if (!selFile.exists()) {
  1390. const auto parentPath = selFile.dir().path();
  1391. // It's possible for the parentPath to match the path
  1392. // (possibly we've arrived at a non-existant drive root on Windows)
  1393. // and recursing would be fatal.
  1394. if (parentPath == path) {
  1395. return path;
  1396. }
  1397. return canonicalPath(parentPath) + '/' + selFile.fileName();
  1398. }
  1399. return selFile.canonicalFilePath();
  1400. }
  1401. QString FolderMan::checkPathValidityForNewFolder(const QString &path, const QUrl &serverUrl) const
  1402. {
  1403. QString recursiveValidity = checkPathValidityRecursive(path);
  1404. if (!recursiveValidity.isEmpty()) {
  1405. qCDebug(lcFolderMan) << path << recursiveValidity;
  1406. return recursiveValidity;
  1407. }
  1408. // check if the local directory isn't used yet in another ownCloud sync
  1409. Qt::CaseSensitivity cs = Qt::CaseSensitive;
  1410. if (Utility::fsCasePreserving()) {
  1411. cs = Qt::CaseInsensitive;
  1412. }
  1413. const QString userDir = QDir::cleanPath(canonicalPath(path)) + '/';
  1414. for (auto i = _folderMap.constBegin(); i != _folderMap.constEnd(); ++i) {
  1415. auto *f = static_cast<Folder *>(i.value());
  1416. QString folderDir = QDir::cleanPath(canonicalPath(f->path())) + '/';
  1417. bool differentPaths = QString::compare(folderDir, userDir, cs) != 0;
  1418. if (differentPaths && folderDir.startsWith(userDir, cs)) {
  1419. return tr("The local folder %1 already contains a folder used in a folder sync connection. "
  1420. "Please pick another one!")
  1421. .arg(QDir::toNativeSeparators(path));
  1422. }
  1423. if (differentPaths && userDir.startsWith(folderDir, cs)) {
  1424. return tr("The local folder %1 is already contained in a folder used in a folder sync connection. "
  1425. "Please pick another one!")
  1426. .arg(QDir::toNativeSeparators(path));
  1427. }
  1428. // if both pathes are equal, the server url needs to be different
  1429. // otherwise it would mean that a new connection from the same local folder
  1430. // to the same account is added which is not wanted. The account must differ.
  1431. if (serverUrl.isValid() && !differentPaths) {
  1432. QUrl folderUrl = f->accountState()->account()->url();
  1433. QString user = f->accountState()->account()->credentials()->user();
  1434. folderUrl.setUserName(user);
  1435. if (serverUrl == folderUrl) {
  1436. return tr("There is already a sync from the server to this local folder. "
  1437. "Please pick another local folder!");
  1438. }
  1439. }
  1440. }
  1441. return QString();
  1442. }
  1443. QString FolderMan::findGoodPathForNewSyncFolder(const QString &basePath, const QUrl &serverUrl) const
  1444. {
  1445. QString folder = basePath;
  1446. // If the parent folder is a sync folder or contained in one, we can't
  1447. // possibly find a valid sync folder inside it.
  1448. // Example: Someone syncs their home directory. Then ~/foobar is not
  1449. // going to be an acceptable sync folder path for any value of foobar.
  1450. QString parentFolder = QFileInfo(folder).dir().canonicalPath();
  1451. if (FolderMan::instance()->folderForPath(parentFolder)) {
  1452. // Any path with that parent is going to be unacceptable,
  1453. // so just keep it as-is.
  1454. return basePath;
  1455. }
  1456. int attempt = 1;
  1457. forever {
  1458. const bool isGood =
  1459. !QFileInfo(folder).exists()
  1460. && FolderMan::instance()->checkPathValidityForNewFolder(folder, serverUrl).isEmpty();
  1461. if (isGood) {
  1462. break;
  1463. }
  1464. // Count attempts and give up eventually
  1465. attempt++;
  1466. if (attempt > 100) {
  1467. return basePath;
  1468. }
  1469. folder = basePath + QString::number(attempt);
  1470. }
  1471. return folder;
  1472. }
  1473. bool FolderMan::ignoreHiddenFiles() const
  1474. {
  1475. if (_folderMap.empty()) {
  1476. // Currently no folders in the manager -> return default
  1477. return false;
  1478. }
  1479. // Since the hiddenFiles settings is the same for all folders, just return the settings of the first folder
  1480. return _folderMap.begin().value()->ignoreHiddenFiles();
  1481. }
  1482. void FolderMan::setIgnoreHiddenFiles(bool ignore)
  1483. {
  1484. // Note that the setting will revert to 'true' if all folders
  1485. // are deleted...
  1486. for (Folder *folder : qAsConst(_folderMap)) {
  1487. folder->setIgnoreHiddenFiles(ignore);
  1488. folder->saveToSettings();
  1489. }
  1490. }
  1491. QQueue<Folder *> FolderMan::scheduleQueue() const
  1492. {
  1493. return _scheduledFolders;
  1494. }
  1495. Folder *FolderMan::currentSyncFolder() const
  1496. {
  1497. return _currentSyncFolder;
  1498. }
  1499. void FolderMan::restartApplication()
  1500. {
  1501. if (Utility::isLinux()) {
  1502. // restart:
  1503. qCInfo(lcFolderMan) << "Restarting application NOW, PID" << qApp->applicationPid() << "is ending.";
  1504. qApp->quit();
  1505. QStringList args = qApp->arguments();
  1506. QString prg = args.takeFirst();
  1507. QProcess::startDetached(prg, args);
  1508. } else {
  1509. qCDebug(lcFolderMan) << "On this platform we do not restart.";
  1510. }
  1511. }
  1512. void FolderMan::slotSetupPushNotifications(const Folder::Map &folderMap)
  1513. {
  1514. for (auto folder : folderMap) {
  1515. const auto account = folder->accountState()->account();
  1516. // See if the account already provides the PushNotifications object and if yes connect to it.
  1517. // If we can't connect at this point, the signals will be connected in slotPushNotificationsReady()
  1518. // after the PushNotification object emitted the ready signal
  1519. slotConnectToPushNotifications(account.data());
  1520. connect(account.data(), &Account::pushNotificationsReady, this, &FolderMan::slotConnectToPushNotifications, Qt::UniqueConnection);
  1521. }
  1522. }
  1523. void FolderMan::slotProcessFilesPushNotification(Account *account)
  1524. {
  1525. qCInfo(lcFolderMan) << "Got files push notification for account" << account;
  1526. for (auto folder : _folderMap) {
  1527. // Just run on the folders that belong to this account
  1528. if (folder->accountState()->account() != account) {
  1529. continue;
  1530. }
  1531. qCInfo(lcFolderMan) << "Schedule folder" << folder << "for sync";
  1532. scheduleFolder(folder);
  1533. }
  1534. }
  1535. void FolderMan::slotConnectToPushNotifications(Account *account)
  1536. {
  1537. const auto pushNotifications = account->pushNotifications();
  1538. if (pushNotificationsFilesReady(account)) {
  1539. qCInfo(lcFolderMan) << "Push notifications ready";
  1540. connect(pushNotifications, &PushNotifications::filesChanged, this, &FolderMan::slotProcessFilesPushNotification, Qt::UniqueConnection);
  1541. }
  1542. }
  1543. } // namespace OCC