1 #include "./syncthingdir.h"
2 
3 #include <c++utilities/conversion/stringconversion.h>
4 
5 #include <QCoreApplication>
6 #include <QJsonObject>
7 #include <QStringBuilder>
8 
9 using namespace CppUtilities;
10 
11 namespace Data {
12 
statusString(SyncthingDirStatus status)13 QString statusString(SyncthingDirStatus status)
14 {
15     switch (status) {
16     case SyncthingDirStatus::Unknown:
17         return QCoreApplication::translate("SyncthingDirStatus", "unknown");
18     case SyncthingDirStatus::Idle:
19         return QCoreApplication::translate("SyncthingDirStatus", "idle");
20     case SyncthingDirStatus::Scanning:
21         return QCoreApplication::translate("SyncthingDirStatus", "scanning");
22     case SyncthingDirStatus::WaitingToScan:
23         return QCoreApplication::translate("SyncthingDirStatus", "waiting to scan");
24     case SyncthingDirStatus::WaitingToSync:
25         return QCoreApplication::translate("SyncthingDirStatus", "waiting to sync");
26     case SyncthingDirStatus::PreparingToSync:
27         return QCoreApplication::translate("SyncthingDirStatus", "preparing to sync");
28     case SyncthingDirStatus::Synchronizing:
29         return QCoreApplication::translate("SyncthingDirStatus", "synchronizing");
30     case SyncthingDirStatus::Cleaning:
31         return QCoreApplication::translate("SyncthingDirStatus", "cleaning");
32     case SyncthingDirStatus::WaitingToClean:
33         return QCoreApplication::translate("SyncthingDirStatus", "waiting to clean");
34     case SyncthingDirStatus::OutOfSync:
35         return QCoreApplication::translate("SyncthingDirStatus", "out of sync");
36     }
37     return QString();
38 }
39 
dirTypeString(SyncthingDirType dirType)40 QString dirTypeString(SyncthingDirType dirType)
41 {
42     switch (dirType) {
43     case SyncthingDirType::Unknown:
44         return QCoreApplication::translate("SyncthingDirType", "unknown");
45     case SyncthingDirType::SendReceive:
46         return QCoreApplication::translate("SyncthingDirType", "Send & Receive");
47     case SyncthingDirType::SendOnly:
48         return QCoreApplication::translate("SyncthingDirType", "Send only");
49     case SyncthingDirType::ReceiveOnly:
50         return QCoreApplication::translate("SyncthingDirType", "Receive only");
51     }
52     return QString();
53 }
54 
checkWhetherStatusUpdateRelevant(DateTime time)55 bool SyncthingDir::checkWhetherStatusUpdateRelevant(DateTime time)
56 {
57     // ignore old updates
58     if (lastStatusUpdate > time) {
59         return false;
60     }
61     lastStatusUpdate = time;
62     return true;
63 }
64 
finalizeStatusUpdate(SyncthingDirStatus newStatus,DateTime time)65 bool SyncthingDir::finalizeStatusUpdate(SyncthingDirStatus newStatus, DateTime time)
66 {
67     // handle obsoletion of out-of-sync items: no FolderErrors are accepted older than the last "sync" state are accepted
68     if (newStatus == SyncthingDirStatus::PreparingToSync || newStatus == SyncthingDirStatus::Synchronizing) {
69         // update time of last "sync" state and obsolete currently assigned errors
70         lastSyncStarted = time; // used internally and not displayed, hence keep it GMT
71         itemErrors.clear();
72         pullErrorCount = 0;
73     } else if (lastSyncStarted.isNull() && newStatus != SyncthingDirStatus::OutOfSync) {
74         // prevent adding new errors from "before the first status" if the time of the last "sync" state is unknown
75         lastSyncStarted = time;
76     }
77 
78     // clear global error if not out-of-sync anymore
79     if (newStatus != SyncthingDirStatus::OutOfSync) {
80         globalError.clear();
81     }
82 
83     // consider the directory still as out-of-sync if there are still pull errors
84     // note: Syncthing reports status changes to "idle" despite pull errors. This means we can only rely on reading
85     //       a "FolderSummary" event without pull errors for clearing the out-of-sync status.
86     if (pullErrorCount && (newStatus == SyncthingDirStatus::Unknown || newStatus == SyncthingDirStatus::Idle)) {
87         newStatus = SyncthingDirStatus::OutOfSync;
88     }
89 
90     if (newStatus == status) {
91         return false;
92     }
93 
94     // update last scan time if the previous status was scanning
95     if (status == SyncthingDirStatus::Scanning) {
96         // FIXME: better use \a time and convert it from GMT to local time
97         lastScanTime = DateTime::now();
98     }
99 
100     status = newStatus;
101     return true;
102 }
103 
104 /*!
105  * \brief Assigns the status from the specified status string.
106  * \returns Returns whether the status has actually changed.
107  */
assignStatus(const QString & statusStr,CppUtilities::DateTime time)108 bool SyncthingDir::assignStatus(const QString &statusStr, CppUtilities::DateTime time)
109 {
110     if (!checkWhetherStatusUpdateRelevant(time)) {
111         return false;
112     }
113 
114     // identify statusStr
115     SyncthingDirStatus newStatus;
116     if (statusStr == QLatin1String("idle")) {
117         completionPercentage = 0;
118         newStatus = SyncthingDirStatus::Idle;
119     } else if (statusStr == QLatin1String("scanning")) {
120         newStatus = SyncthingDirStatus::Scanning;
121     } else if (statusStr == QLatin1String("scan-waiting")) {
122         newStatus = SyncthingDirStatus::WaitingToScan;
123     } else if (statusStr == QLatin1String("sync-waiting")) {
124         newStatus = SyncthingDirStatus::WaitingToSync;
125     } else if (statusStr == QLatin1String("sync-preparing")) {
126         // ensure status changed signal is emitted
127         if (!itemErrors.empty()) {
128             status = SyncthingDirStatus::Unknown;
129         }
130         newStatus = SyncthingDirStatus::PreparingToSync;
131     } else if (statusStr == QLatin1String("syncing")) {
132         // ensure status changed signal is emitted
133         if (!itemErrors.empty()) {
134             status = SyncthingDirStatus::Unknown;
135         }
136         newStatus = SyncthingDirStatus::Synchronizing;
137     } else if (statusStr == QLatin1String("cleaning")) {
138         newStatus = SyncthingDirStatus::Cleaning;
139     } else if (statusStr == QLatin1String("clean-waiting")) {
140         newStatus = SyncthingDirStatus::WaitingToClean;
141     } else if (statusStr == QLatin1String("error")) {
142         completionPercentage = 0;
143         newStatus = SyncthingDirStatus::OutOfSync;
144     } else {
145         newStatus = SyncthingDirStatus::Idle;
146     }
147 
148     rawStatus = statusStr;
149 
150     return finalizeStatusUpdate(newStatus, time);
151 }
152 
assignDirType(const QString & dirTypeStr)153 bool SyncthingDir::assignDirType(const QString &dirTypeStr)
154 {
155     if (dirTypeStr == QLatin1String("sendreceive") || dirTypeStr == QLatin1String("readwrite")) {
156         dirType = SyncthingDirType::SendReceive;
157     } else if (dirTypeStr == QLatin1String("sendonly") || dirTypeStr == QLatin1String("readonly")) {
158         dirType = SyncthingDirType::SendOnly;
159     } else if (dirTypeStr == QLatin1String("receiveonly")) {
160         dirType = SyncthingDirType::ReceiveOnly;
161     } else {
162         dirType = SyncthingDirType::Unknown;
163         return false;
164     }
165     return true;
166 }
167 
statusString() const168 QString SyncthingDir::statusString() const
169 {
170     if (paused) {
171         return QCoreApplication::translate("SyncthingDir", "paused");
172     } else if (isUnshared()) {
173         return QCoreApplication::translate("SyncthingDir", "unshared");
174     } else if (status == SyncthingDirStatus::Unknown && !rawStatus.isEmpty()) {
175         return QString(rawStatus);
176     } else {
177         return ::Data::statusString(status);
178     }
179 }
180 
pathWithoutTrailingSlash() const181 QtUtilities::StringView SyncthingDir::pathWithoutTrailingSlash() const
182 {
183     auto dirPath = QtUtilities::makeStringView(path);
184     while (dirPath.endsWith(QChar('/'))) {
185 #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
186         dirPath.chop(1);
187 #else
188         dirPath = dirPath.left(dirPath.size() - 1);
189 #endif
190     }
191     return dirPath;
192 }
193 
areRemotesUpToDate() const194 bool SyncthingDir::areRemotesUpToDate() const
195 {
196     for (const auto &completionForDev : completionByDevice) {
197         if (!completionForDev.second.needed.isNull()) {
198             return false;
199         }
200     }
201     return true;
202 }
203 
SyncthingItemDownloadProgress(const QString & containingDirPath,const QString & relativeItemPath,const QJsonObject & values)204 SyncthingItemDownloadProgress::SyncthingItemDownloadProgress(
205     const QString &containingDirPath, const QString &relativeItemPath, const QJsonObject &values)
206     : relativePath(relativeItemPath)
207     , fileInfo(containingDirPath % QChar('/') % QString(relativeItemPath).replace(QChar('\\'), QChar('/')))
208     , blocksCurrentlyDownloading(values.value(QLatin1String("Pulling")).toInt())
209     , blocksAlreadyDownloaded(values.value(QLatin1String("Pulled")).toInt())
210     , totalNumberOfBlocks(values.value(QLatin1String("Total")).toInt())
211     , downloadPercentage((blocksAlreadyDownloaded > 0 && totalNumberOfBlocks > 0)
212               ? (static_cast<unsigned int>(blocksAlreadyDownloaded) * 100 / static_cast<unsigned int>(totalNumberOfBlocks))
213               : 0)
214     , blocksCopiedFromOrigin(values.value(QLatin1String("CopiedFromOrigin")).toInt())
215     , blocksCopiedFromElsewhere(values.value(QLatin1String("CopiedFromElsewhere")).toInt())
216     , blocksReused(values.value(QLatin1String("Reused")).toInt())
217     , bytesAlreadyHandled(values.value(QLatin1String("BytesDone")).toInt())
218     , totalNumberOfBytes(values.value(QLatin1String("BytesTotal")).toInt())
219     , label(QStringLiteral("%1 / %2 - %3 %")
220                 .arg(QString::fromLatin1(
221                          dataSizeToString(blocksAlreadyDownloaded > 0 ? static_cast<std::uint64_t>(blocksAlreadyDownloaded) * syncthingBlockSize : 0)
222                              .data()),
223                     QString::fromLatin1(
224                         dataSizeToString(totalNumberOfBlocks > 0 ? static_cast<std::uint64_t>(totalNumberOfBlocks) * syncthingBlockSize : 0).data()),
225                     QString::number(downloadPercentage)))
226 {
227 }
228 
operator +=(const SyncthingStatistics & other)229 SyncthingStatistics &SyncthingStatistics::operator+=(const SyncthingStatistics &other)
230 {
231     bytes += other.bytes;
232     deletes += other.deletes;
233     dirs += other.dirs;
234     files += other.files;
235     symlinks += other.symlinks;
236     return *this;
237 }
238 
239 /*!
240  * \brief Computes overall statistics for the specified \a directories.
241  */
SyncthingOverallDirStatistics(const std::vector<SyncthingDir> & directories)242 SyncthingOverallDirStatistics::SyncthingOverallDirStatistics(const std::vector<SyncthingDir> &directories)
243 {
244     for (const auto &dir : directories) {
245         local += dir.localStats;
246         global += dir.globalStats;
247         needed += dir.neededStats;
248     }
249 }
250 
251 } // namespace Data
252