syncstatussummary.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.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 "syncstatussummary.h"
  15. #include "accountfwd.h"
  16. #include "folderman.h"
  17. #include "navigationpanehelper.h"
  18. #include "networkjobs.h"
  19. #include "syncresult.h"
  20. #include "tray/usermodel.h"
  21. #include <theme.h>
  22. namespace {
  23. OCC::SyncResult::Status determineSyncStatus(const OCC::SyncResult &syncResult)
  24. {
  25. const auto status = syncResult.status();
  26. if (status == OCC::SyncResult::Success || status == OCC::SyncResult::Problem) {
  27. if (syncResult.hasUnresolvedConflicts()) {
  28. return OCC::SyncResult::Problem;
  29. }
  30. return OCC::SyncResult::Success;
  31. } else if (status == OCC::SyncResult::SyncPrepare || status == OCC::SyncResult::Undefined) {
  32. return OCC::SyncResult::SyncRunning;
  33. }
  34. return status;
  35. }
  36. }
  37. namespace OCC {
  38. Q_LOGGING_CATEGORY(lcSyncStatusModel, "nextcloud.gui.syncstatusmodel", QtInfoMsg)
  39. SyncStatusSummary::SyncStatusSummary(QObject *parent)
  40. : QObject(parent)
  41. {
  42. const auto folderMan = FolderMan::instance();
  43. connect(folderMan, &FolderMan::folderListChanged, this, &SyncStatusSummary::onFolderListChanged);
  44. connect(folderMan, &FolderMan::folderSyncStateChange, this, &SyncStatusSummary::onFolderSyncStateChanged);
  45. }
  46. bool SyncStatusSummary::reloadNeeded(AccountState *accountState) const
  47. {
  48. if (_accountState.data() == accountState) {
  49. return false;
  50. }
  51. return true;
  52. }
  53. void SyncStatusSummary::load()
  54. {
  55. const auto currentUser = UserModel::instance()->currentUser();
  56. if (!currentUser) {
  57. return;
  58. }
  59. setAccountState(currentUser->accountState());
  60. clearFolderErrors();
  61. connectToFoldersProgress(FolderMan::instance()->map());
  62. initSyncState();
  63. }
  64. double SyncStatusSummary::syncProgress() const
  65. {
  66. return _progress;
  67. }
  68. QUrl SyncStatusSummary::syncIcon() const
  69. {
  70. return _syncIcon;
  71. }
  72. bool SyncStatusSummary::syncing() const
  73. {
  74. return _isSyncing;
  75. }
  76. void SyncStatusSummary::onFolderListChanged(const OCC::Folder::Map &folderMap)
  77. {
  78. connectToFoldersProgress(folderMap);
  79. }
  80. void SyncStatusSummary::markFolderAsError(const Folder *folder)
  81. {
  82. _foldersWithErrors.insert(folder->alias());
  83. }
  84. void SyncStatusSummary::markFolderAsSuccess(const Folder *folder)
  85. {
  86. _foldersWithErrors.erase(folder->alias());
  87. }
  88. bool SyncStatusSummary::folderErrors() const
  89. {
  90. return _foldersWithErrors.size() != 0;
  91. }
  92. bool SyncStatusSummary::folderError(const Folder *folder) const
  93. {
  94. return _foldersWithErrors.find(folder->alias()) != _foldersWithErrors.end();
  95. }
  96. void SyncStatusSummary::clearFolderErrors()
  97. {
  98. _foldersWithErrors.clear();
  99. }
  100. void SyncStatusSummary::setSyncStateForFolder(const Folder *folder)
  101. {
  102. if (_accountState && !_accountState->isConnected()) {
  103. setSyncing(false);
  104. setSyncStatusString(tr("Offline"));
  105. setSyncStatusDetailString("");
  106. setSyncIcon(Theme::instance()->folderOffline());
  107. return;
  108. }
  109. const auto state = determineSyncStatus(folder->syncResult());
  110. switch (state) {
  111. case SyncResult::Success:
  112. case SyncResult::SyncPrepare:
  113. // Success should only be shown if all folders were fine
  114. if (!folderErrors() || folderError(folder)) {
  115. setSyncing(false);
  116. setSyncStatusString(tr("All synced!"));
  117. setSyncStatusDetailString("");
  118. setSyncIcon(Theme::instance()->syncStatusOk());
  119. markFolderAsSuccess(folder);
  120. }
  121. break;
  122. case SyncResult::Error:
  123. case SyncResult::SetupError:
  124. setSyncing(false);
  125. setSyncStatusString(tr("Some files couldn't be synced!"));
  126. setSyncStatusDetailString(tr("See below for errors"));
  127. setSyncIcon(Theme::instance()->syncStatusError());
  128. markFolderAsError(folder);
  129. break;
  130. case SyncResult::SyncRunning:
  131. case SyncResult::NotYetStarted:
  132. setSyncing(true);
  133. setSyncStatusString(tr("Syncing"));
  134. setSyncStatusDetailString("");
  135. setSyncIcon(Theme::instance()->syncStatusRunning());
  136. break;
  137. case SyncResult::Paused:
  138. case SyncResult::SyncAbortRequested:
  139. setSyncing(false);
  140. setSyncStatusString(tr("Sync paused"));
  141. setSyncStatusDetailString("");
  142. setSyncIcon(Theme::instance()->syncStatusPause());
  143. break;
  144. case SyncResult::Problem:
  145. case SyncResult::Undefined:
  146. setSyncing(false);
  147. setSyncStatusString(tr("Some files could not be synced!"));
  148. setSyncStatusDetailString(tr("See below for warnings"));
  149. setSyncIcon(Theme::instance()->syncStatusWarning());
  150. markFolderAsError(folder);
  151. break;
  152. }
  153. }
  154. void SyncStatusSummary::onFolderSyncStateChanged(const Folder *folder)
  155. {
  156. if (!folder) {
  157. return;
  158. }
  159. if (!_accountState || folder->accountState() != _accountState.data()) {
  160. return;
  161. }
  162. setSyncStateForFolder(folder);
  163. }
  164. constexpr double calculateOverallPercent(
  165. qint64 totalFileCount, qint64 completedFile, qint64 totalSize, qint64 completedSize)
  166. {
  167. int overallPercent = 0;
  168. if (totalFileCount > 0) {
  169. // Add one 'byte' for each file so the percentage is moving when deleting or renaming files
  170. overallPercent = qRound(double(completedSize + completedFile) / double(totalSize + totalFileCount) * 100.0);
  171. }
  172. overallPercent = qBound(0, overallPercent, 100);
  173. return overallPercent / 100.0;
  174. }
  175. void SyncStatusSummary::onFolderProgressInfo(const ProgressInfo &progress)
  176. {
  177. const qint64 completedSize = progress.completedSize();
  178. const qint64 currentFile = progress.currentFile();
  179. const qint64 completedFile = progress.completedFiles();
  180. const qint64 totalSize = qMax(completedSize, progress.totalSize());
  181. const qint64 totalFileCount = qMax(currentFile, progress.totalFiles());
  182. setSyncProgress(calculateOverallPercent(totalFileCount, completedFile, totalSize, completedSize));
  183. if (totalSize > 0) {
  184. const auto completedSizeString = Utility::octetsToString(completedSize);
  185. const auto totalSizeString = Utility::octetsToString(totalSize);
  186. if (progress.trustEta()) {
  187. setSyncStatusDetailString(
  188. tr("%1 of %2 · %3 left")
  189. .arg(completedSizeString, totalSizeString)
  190. .arg(Utility::durationToDescriptiveString1(progress.totalProgress().estimatedEta)));
  191. } else {
  192. setSyncStatusDetailString(tr("%1 of %2").arg(completedSizeString, totalSizeString));
  193. }
  194. }
  195. if (totalFileCount > 0) {
  196. setSyncStatusString(tr("Syncing file %1 of %2").arg(currentFile).arg(totalFileCount));
  197. }
  198. }
  199. void SyncStatusSummary::setSyncing(bool value)
  200. {
  201. if (value == _isSyncing) {
  202. return;
  203. }
  204. _isSyncing = value;
  205. emit syncingChanged();
  206. }
  207. void SyncStatusSummary::setSyncProgress(double value)
  208. {
  209. if (_progress == value) {
  210. return;
  211. }
  212. _progress = value;
  213. emit syncProgressChanged();
  214. }
  215. void SyncStatusSummary::setSyncStatusString(const QString &value)
  216. {
  217. if (_syncStatusString == value) {
  218. return;
  219. }
  220. _syncStatusString = value;
  221. emit syncStatusStringChanged();
  222. }
  223. QString SyncStatusSummary::syncStatusString() const
  224. {
  225. return _syncStatusString;
  226. }
  227. QString SyncStatusSummary::syncStatusDetailString() const
  228. {
  229. return _syncStatusDetailString;
  230. }
  231. void SyncStatusSummary::setSyncIcon(const QUrl &value)
  232. {
  233. if (_syncIcon == value) {
  234. return;
  235. }
  236. _syncIcon = value;
  237. emit syncIconChanged();
  238. }
  239. void SyncStatusSummary::setSyncStatusDetailString(const QString &value)
  240. {
  241. if (_syncStatusDetailString == value) {
  242. return;
  243. }
  244. _syncStatusDetailString = value;
  245. emit syncStatusDetailStringChanged();
  246. }
  247. void SyncStatusSummary::connectToFoldersProgress(const Folder::Map &folderMap)
  248. {
  249. for (const auto &folder : folderMap) {
  250. if (folder->accountState() == _accountState.data()) {
  251. connect(
  252. folder, &Folder::progressInfo, this, &SyncStatusSummary::onFolderProgressInfo, Qt::UniqueConnection);
  253. } else {
  254. disconnect(folder, &Folder::progressInfo, this, &SyncStatusSummary::onFolderProgressInfo);
  255. }
  256. }
  257. }
  258. void SyncStatusSummary::onIsConnectedChanged()
  259. {
  260. setSyncStateToConnectedState();
  261. }
  262. void SyncStatusSummary::setSyncStateToConnectedState()
  263. {
  264. setSyncing(false);
  265. setSyncStatusDetailString("");
  266. if (_accountState && !_accountState->isConnected()) {
  267. setSyncStatusString(tr("Offline"));
  268. setSyncIcon(Theme::instance()->folderOffline());
  269. } else {
  270. setSyncStatusString(tr("All synced!"));
  271. setSyncIcon(Theme::instance()->syncStatusOk());
  272. }
  273. }
  274. void SyncStatusSummary::setAccountState(AccountStatePtr accountState)
  275. {
  276. if (!reloadNeeded(accountState.data())) {
  277. return;
  278. }
  279. if (_accountState) {
  280. disconnect(
  281. _accountState.data(), &AccountState::isConnectedChanged, this, &SyncStatusSummary::onIsConnectedChanged);
  282. }
  283. _accountState = accountState;
  284. connect(_accountState.data(), &AccountState::isConnectedChanged, this, &SyncStatusSummary::onIsConnectedChanged);
  285. }
  286. void SyncStatusSummary::initSyncState()
  287. {
  288. auto syncStateFallbackNeeded = true;
  289. for (const auto &folder : FolderMan::instance()->map()) {
  290. onFolderSyncStateChanged(folder);
  291. syncStateFallbackNeeded = false;
  292. }
  293. if (syncStateFallbackNeeded) {
  294. setSyncStateToConnectedState();
  295. }
  296. }
  297. }