Przeglądaj źródła

add more metadata to sync errors to allow filtering

in order to be able to filter some errors when showing them into the
main dialog activity list, add some more info about the error to know
the origin (like a network issue or a sync issue)

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
Matthieu Gallien 3 lat temu
rodzic
commit
4c883382b6

+ 1 - 1
src/gui/editlocallyjob.cpp

@@ -438,7 +438,7 @@ void EditLocallyJob::showErrorNotification(const QString &message, const QString
     });
 
     if (foundFolder != folderMap.cend()) {
-        emit (*foundFolder)->syncEngine().addErrorToGui(SyncFileItem::SoftError, message, informativeText);
+        emit (*foundFolder)->syncEngine().addErrorToGui(SyncFileItem::SoftError, message, informativeText, OCC::ErrorCategory::GenericError);
     }
 }
 

+ 5 - 5
src/gui/folder.cpp

@@ -890,7 +890,7 @@ void Folder::startSync(const QStringList &pathList)
     _fileLog->start(path());
 
     if (!reloadExcludes()) {
-        slotSyncError(tr("Could not read system exclude file"));
+        slotSyncError(tr("Could not read system exclude file"), ErrorCategory::GenericError);
         QMetaObject::invokeMethod(this, "slotSyncFinished", Qt::QueuedConnection, Q_ARG(bool, false));
         return;
     }
@@ -1005,9 +1005,9 @@ void Folder::slotSyncError(const QString &message, ErrorCategory category)
     }
 }
 
-void Folder::slotAddErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject)
+void Folder::slotAddErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject, ErrorCategory category)
 {
-    emit ProgressDispatcher::instance()->addErrorToGui(alias(), status, errorMessage, subject);
+    emit ProgressDispatcher::instance()->addErrorToGui(alias(), status, errorMessage, subject, category);
 }
 
 void Folder::slotSyncStarted()
@@ -1128,7 +1128,7 @@ void Folder::slotTransmissionProgress(const ProgressInfo &pi)
 }
 
 // a item is completed: count the errors and forward to the ProgressDispatcher
-void Folder::slotItemCompleted(const SyncFileItemPtr &item)
+void Folder::slotItemCompleted(const SyncFileItemPtr &item, ErrorCategory errorCategory)
 {
     if (item->_instruction == CSYNC_INSTRUCTION_NONE || item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {
         // We only care about the updates that deserve to be shown in the UI
@@ -1144,7 +1144,7 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item)
     _syncResult.processCompletedItem(item);
 
     _fileLog->logItem(*item);
-    emit ProgressDispatcher::instance()->itemCompleted(alias(), item);
+    emit ProgressDispatcher::instance()->itemCompleted(alias(), item, errorCategory);
 }
 
 void Folder::slotNewBigFolderDiscovered(const QString &newF, bool isExternal)

+ 3 - 3
src/gui/folder.h

@@ -388,12 +388,12 @@ private slots:
 
     /** Adds a error message that's not tied to a specific item.
      */
-    void slotSyncError(const QString &message, OCC::ErrorCategory category = OCC::ErrorCategory::Normal);
+    void slotSyncError(const QString &message, OCC::ErrorCategory category);
 
-    void slotAddErrorToGui(OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject = {});
+    void slotAddErrorToGui(OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, OCC::ErrorCategory category);
 
     void slotTransmissionProgress(const OCC::ProgressInfo &pi);
-    void slotItemCompleted(const OCC::SyncFileItemPtr &);
+    void slotItemCompleted(const OCC::SyncFileItemPtr &, OCC::ErrorCategory errorCategory);
 
     void slotRunEtagJob();
     void etagRetrieved(const QByteArray &, const QDateTime &tp);

+ 2 - 2
src/gui/tray/activitylistmodel.cpp

@@ -556,9 +556,9 @@ void ActivityListModel::addEntriesToActivityList(const ActivityList &activityLis
     setHasSyncConflicts(conflictsFound);
 }
 
-void ActivityListModel::addErrorToActivityList(const Activity &activity)
+void ActivityListModel::addErrorToActivityList(const Activity &activity, const ErrorType type)
 {
-    qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._message << activity._subject << activity._syncResultStatus << activity._syncFileItemStatus;
+    qCInfo(lcActivity) << "Error successfully added to the notification list: " << type << activity._message << activity._subject << activity._syncResultStatus << activity._syncFileItemStatus;
     addEntriesToActivityList({activity});
     _notificationErrorsLists.prepend(activity);
 }

+ 7 - 1
src/gui/tray/activitylistmodel.h

@@ -82,6 +82,12 @@ public:
     };
     Q_ENUM(DataRole)
 
+    enum class ErrorType {
+        SyncError,
+        NetworkError,
+    };
+    Q_ENUM(ErrorType)
+
     explicit ActivityListModel(QObject *parent = nullptr);
 
     explicit ActivityListModel(AccountState *accountState,
@@ -122,7 +128,7 @@ public slots:
     void slotTriggerDismiss(const int activityIndex);
 
     void addNotificationToActivityList(const OCC::Activity &activity);
-    void addErrorToActivityList(const OCC::Activity &activity);
+    void addErrorToActivityList(const OCC::Activity &activity, const ErrorType type);
     void addIgnoredFileToList(const OCC::Activity &newActivity);
     void addSyncFileItemToActivityList(const OCC::Activity &activity);
     void removeActivityFromActivityList(int row);

+ 36 - 4
src/gui/tray/usermodel.cpp

@@ -627,12 +627,27 @@ void User::slotAddError(const QString &folderAlias, const QString &message, Erro
             activity._links.append(link);
         }
 
+        auto errorType = ActivityListModel::ErrorType::SyncError;
         // add 'other errors' to activity list
-        _activityModel->addErrorToActivityList(activity);
+        switch (category) {
+        case ErrorCategory::GenericError:
+            errorType = ActivityListModel::ErrorType::SyncError;
+            break;
+        case ErrorCategory::InsufficientRemoteStorage:
+            errorType = ActivityListModel::ErrorType::SyncError;
+            break;
+        case ErrorCategory::NetworkError:
+            errorType = ActivityListModel::ErrorType::NetworkError;
+            break;
+        case ErrorCategory::NoError:
+            break;
+        }
+
+        _activityModel->addErrorToActivityList(activity, errorType);
     }
 }
 
-void User::slotAddErrorToGui(const QString &folderAlias, SyncFileItem::Status status, const QString &errorMessage, const QString &subject)
+void User::slotAddErrorToGui(const QString &folderAlias, const SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const ErrorCategory category)
 {
     const auto folderInstance = FolderMan::instance()->folder(folderAlias);
     if (!folderInstance) {
@@ -668,7 +683,24 @@ void User::slotAddErrorToGui(const QString &folderAlias, SyncFileItem::Status st
         activity._id = -static_cast<int>(qHash(activity._subject + activity._message));
 
         // add 'other errors' to activity list
-        _activityModel->addErrorToActivityList(activity);
+        auto errorType = ActivityListModel::ErrorType::SyncError;
+        switch (category)
+        {
+        case ErrorCategory::GenericError:
+            errorType = ActivityListModel::ErrorType::SyncError;
+            break;
+        case ErrorCategory::InsufficientRemoteStorage:
+            errorType = ActivityListModel::ErrorType::SyncError;
+            break;
+        case ErrorCategory::NetworkError:
+            errorType = ActivityListModel::ErrorType::NetworkError;
+            break;
+        case ErrorCategory::NoError:
+            errorType = {};
+            break;
+        }
+
+        _activityModel->addErrorToActivityList(activity, errorType);
 
         showDesktopNotification(activity);
 
@@ -787,7 +819,7 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
 
                 activity._links = {buttonActivityLink};
             }
-            _activityModel->addErrorToActivityList(activity);
+            _activityModel->addErrorToActivityList(activity, ActivityListModel::ErrorType::SyncError);
         }
     }
 }

+ 1 - 1
src/gui/tray/usermodel.h

@@ -117,7 +117,7 @@ public slots:
     void slotItemCompleted(const QString &folder, const OCC::SyncFileItemPtr &item);
     void slotProgressInfo(const QString &folder, const OCC::ProgressInfo &progress);
     void slotAddError(const QString &folderAlias, const QString &message, OCC::ErrorCategory category);
-    void slotAddErrorToGui(const QString &folderAlias, OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject = {});
+    void slotAddErrorToGui(const QString &folderAlias, const OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const ErrorCategory category);
     void slotNotificationRequestFinished(int statusCode);
     void slotNotifyNetworkError(QNetworkReply *reply);
     void slotEndNotificationRequest(int replyCode);

+ 20 - 18
src/libsync/bulkpropagatorjob.cpp

@@ -120,7 +120,7 @@ void BulkPropagatorJob::startUploadFile(SyncFileItemPtr item, UploadFileInfo fil
 
     // Check if the specific file can be accessed
     if (propagator()->hasCaseClashAccessibilityProblem(fileToUpload._file)) {
-        done(item, SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists").arg(QDir::toNativeSeparators(item->_file)));
+        done(item, SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
         return;
     }
 
@@ -160,7 +160,7 @@ void BulkPropagatorJob::doStartUpload(SyncFileItemPtr item,
         const auto renameSuccess = QFile::rename(originalFilePathAbsolute, newFilePathAbsolute);
 
         if (!renameSuccess) {
-            done(item, SyncFileItem::NormalError, "File contains trailing spaces and couldn't be renamed");
+            done(item, SyncFileItem::NormalError, "File contains trailing spaces and couldn't be renamed", ErrorCategory::GenericError);
             return;
         }
 
@@ -172,7 +172,7 @@ void BulkPropagatorJob::doStartUpload(SyncFileItemPtr item,
         item->_modtime = FileSystem::getModTime(newFilePathAbsolute);
         if (item->_modtime <= 0) {
             _pendingChecksumFiles.remove(item->_file);
-            slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)));
+            slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
             checkPropagationIsDone();
             return;
         }
@@ -293,7 +293,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
 
     if (!FileSystem::fileExists(fullFilePath)) {
         _pendingChecksumFiles.remove(item->_file);
-        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath));
+        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath), ErrorCategory::GenericError);
         checkPropagationIsDone();
         return;
     }
@@ -305,7 +305,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
     item->_modtime = FileSystem::getModTime(originalFilePath);
     if (item->_modtime <= 0) {
         _pendingChecksumFiles.remove(item->_file);
-        slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)));
+        slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
         checkPropagationIsDone();
         return;
     }
@@ -317,7 +317,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
                                      << "prevModtime" << prevModtime
                                      << "Curr" << item->_modtime;
 
-        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("Local file changed during syncing. It will be resumed."));
+        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("Local file changed during syncing. It will be resumed."), ErrorCategory::GenericError);
         checkPropagationIsDone();
         return;
     }
@@ -331,7 +331,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
     if (fileIsStillChanging(*item)) {
         propagator()->_anotherSyncNeeded = true;
         _pendingChecksumFiles.remove(item->_file);
-        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("Local file changed during sync."));
+        slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("Local file changed during sync."), ErrorCategory::GenericError);
         checkPropagationIsDone();
         return;
     }
@@ -340,11 +340,12 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
 }
 
 void BulkPropagatorJob::slotOnErrorStartFolderUnlock(SyncFileItemPtr item,
-                                                     SyncFileItem::Status status,
-                                                     const QString &errorString)
+                                                     const SyncFileItem::Status status,
+                                                     const QString &errorString,
+                                                     const ErrorCategory errorCategory)
 {
-    qCInfo(lcBulkPropagatorJob()) << status << errorString;
-    done(item, status, errorString);
+    qCInfo(lcBulkPropagatorJob()) << status << errorString << errorCategory;
+    done(item, status, errorString, errorCategory);
 }
 
 void BulkPropagatorJob::slotPutFinishedOneFile(const BulkUploadItem &singleFile,
@@ -473,10 +474,10 @@ void BulkPropagatorJob::finalizeOneFile(const BulkUploadItem &oneFile)
     // Update the database entry
     const auto result = propagator()->updateMetadata(*oneFile._item);
     if (!result) {
-        done(oneFile._item, SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+        done(oneFile._item, SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), ErrorCategory::GenericError);
         return;
     } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-        done(oneFile._item, SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(oneFile._item->_file));
+        done(oneFile._item, SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(oneFile._item->_file), ErrorCategory::GenericError);
         return;
     }
 
@@ -511,7 +512,7 @@ void BulkPropagatorJob::finalize(const QJsonObject &fullReply)
             finalizeOneFile(singleFile);
         }
 
-        done(singleFile._item, singleFile._item->_status, {});
+        done(singleFile._item, singleFile._item->_status, {}, ErrorCategory::GenericError);
 
         singleFileIt = _filesToUpload.erase(singleFileIt);
     }
@@ -520,8 +521,9 @@ void BulkPropagatorJob::finalize(const QJsonObject &fullReply)
 }
 
 void BulkPropagatorJob::done(SyncFileItemPtr item,
-                             SyncFileItem::Status status,
-                             const QString &errorString)
+                             const SyncFileItem::Status status,
+                             const QString &errorString,
+                             const ErrorCategory category)
 {
     item->_status = status;
     item->_errorString = errorString;
@@ -548,7 +550,7 @@ void BulkPropagatorJob::done(SyncFileItemPtr item,
 
     handleJobDoneErrors(item, status);
 
-    emit propagator()->itemCompleted(item);
+    emit propagator()->itemCompleted(item, category);
 }
 
 QMap<QByteArray, QByteArray> BulkPropagatorJob::headers(SyncFileItemPtr item) const
@@ -606,7 +608,7 @@ void BulkPropagatorJob::abortWithError(SyncFileItemPtr item,
                                        const QString &error)
 {
     abort(AbortType::Synchronous);
-    done(item, status, error);
+    done(item, status, error, ErrorCategory::GenericError);
 }
 
 void BulkPropagatorJob::checkResettingErrors(SyncFileItemPtr item) const

+ 6 - 4
src/libsync/bulkpropagatorjob.h

@@ -81,8 +81,9 @@ private slots:
 
     // invoked on internal error to unlock a folder and faile
     void slotOnErrorStartFolderUnlock(OCC::SyncFileItemPtr item,
-                                      OCC::SyncFileItem::Status status,
-                                      const QString &errorString);
+                                      const OCC::SyncFileItem::Status status,
+                                      const QString &errorString,
+                                      const OCC::ErrorCategory errorCategory);
 
     void slotPutFinished();
 
@@ -107,8 +108,9 @@ private:
                                 const QJsonObject &fullReplyObject);
 
     void done(SyncFileItemPtr item,
-              SyncFileItem::Status status,
-              const QString &errorString);
+              const SyncFileItem::Status status,
+              const QString &errorString,
+              const ErrorCategory category);
 
     /** Bases headers that need to be sent on the PUT, or in the MOVE for chunking-ng */
     [[nodiscard]] QMap<QByteArray, QByteArray> headers(SyncFileItemPtr item) const;

+ 8 - 7
src/libsync/discovery.cpp

@@ -18,6 +18,7 @@
 #include "common/syncjournaldb.h"
 #include "filesystem.h"
 #include "syncfileitem.h"
+#include "progressdispatcher.h"
 #include <QDebug>
 #include <algorithm>
 #include <QEventLoop>
@@ -964,7 +965,7 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
             // Not locally, not on the server. The entry is stale!
             qCInfo(lcDisco) << "Stale DB entry";
             if (!_discoveryData->_statedb->deleteFileRecord(path._original, true)) {
-                emit _discoveryData->fatalError(tr("Error while deleting file record %1 from the database").arg(path._original));
+                emit _discoveryData->fatalError(tr("Error while deleting file record %1 from the database").arg(path._original), ErrorCategory::GenericError);
                 qCWarning(lcDisco) << "Failed to delete a file record from the local DB" << path._original;
             }
             return;
@@ -1214,10 +1215,10 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
             if (isFolderPinStateOnlineOnly && folderPinState.isValid()) {
                 qCInfo(lcDisco) << "*folderPinState:" << *folderPinState;
             }
-            emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading a folder. It's going to get cleared!"), path._local);
+            emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading a folder. It's going to get cleared!"), path._local, ErrorCategory::GenericError);
         } else {
             qCInfo(lcDisco) << "Wiping virtual file without db entry for" << path._local;
-            emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading a file. It's going to get removed!"), path._local);
+            emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading a file. It's going to get removed!"), path._local, ErrorCategory::GenericError);
         }
         item->_instruction = CSYNC_INSTRUCTION_REMOVE;
         item->_direction = SyncFileItem::Down;
@@ -1813,7 +1814,7 @@ int ProcessDirectoryJob::processSubJobs(int nbJobs)
 
 void ProcessDirectoryJob::dbError()
 {
-    emit _discoveryData->fatalError(tr("Error while reading the database"));
+    emit _discoveryData->fatalError(tr("Error while reading the database"), ErrorCategory::GenericError);
 }
 
 void ProcessDirectoryJob::addVirtualFileSuffix(QString &str) const
@@ -1880,7 +1881,7 @@ DiscoverySingleDirectoryJob *ProcessDirectoryJob::startAsyncServerQuery()
             } else {
                 // Fatal for the root job since it has no SyncFileItem, or for the network errors
                 emit _discoveryData->fatalError(tr("Server replied with an error while reading directory \"%1\" : %2")
-                    .arg(_currentFolder._server, results.error().message));
+                    .arg(_currentFolder._server, results.error().message), ErrorCategory::NetworkError);
             }
         }
     });
@@ -1910,7 +1911,7 @@ void ProcessDirectoryJob::startAsyncLocalQuery()
         if (_serverJob)
             _serverJob->abort();
 
-        emit _discoveryData->fatalError(msg);
+        emit _discoveryData->fatalError(msg, ErrorCategory::NetworkError);
     });
 
     connect(localJob, &DiscoverySingleLocalDirectoryJob::finishedNonFatalError, this, [this](const QString &msg) {
@@ -1923,7 +1924,7 @@ void ProcessDirectoryJob::startAsyncLocalQuery()
             emit this->finished();
         } else {
             // Fatal for the root job since it has no SyncFileItem
-            emit _discoveryData->fatalError(msg);
+            emit _discoveryData->fatalError(msg, ErrorCategory::GenericError);
         }
     });
 

+ 3 - 2
src/libsync/discoveryphase.cpp

@@ -15,6 +15,7 @@
 #include "discoveryphase.h"
 #include "discovery.h"
 #include "helpers.h"
+#include "progressdispatcher.h"
 
 #include "account.h"
 #include "clientsideencryptionjobs.h"
@@ -187,8 +188,8 @@ QPair<bool, QByteArray> DiscoveryPhase::findAndCancelDeletedJob(const QString &o
                 qCWarning(lcDiscovery) << "(*it)->_type" << (*it)->_type;
                 qCWarning(lcDiscovery) << "(*it)->_isRestoration " << (*it)->_isRestoration;
                 Q_ASSERT(false);
-                emit addErrorToGui(SyncFileItem::Status::FatalError, tr("Error while canceling deletion of a file"), originalPath);
-                emit fatalError(tr("Error while canceling deletion of %1").arg(originalPath));
+                emit addErrorToGui(SyncFileItem::Status::FatalError, tr("Error while canceling deletion of a file"), originalPath, ErrorCategory::GenericError);
+                emit fatalError(tr("Error while canceling deletion of %1").arg(originalPath), ErrorCategory::GenericError);
             }
             (*it)->_instruction = CSYNC_INSTRUCTION_NONE;
             result = true;

+ 4 - 2
src/libsync/discoveryphase.h

@@ -51,6 +51,8 @@ class Account;
 class SyncJournalDb;
 class ProcessDirectoryJob;
 
+enum class ErrorCategory;
+
 /**
  * Represent all the meta-data about a file in the server
  */
@@ -307,7 +309,7 @@ public:
     QStringList _listExclusiveFiles;
 
 signals:
-    void fatalError(const QString &errorString);
+    void fatalError(const QString &errorString, const OCC::ErrorCategory errorCategory);
     void itemDiscovered(const OCC::SyncFileItemPtr &item);
     void finished();
 
@@ -320,7 +322,7 @@ signals:
       */
     void silentlyExcluded(const QString &folderPath);
 
-    void addErrorToGui(SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
+    void addErrorToGui(const SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const OCC::ErrorCategory category);
 };
 
 /// Implementation of DiscoveryPhase::adjustRenamedPath

+ 59 - 9
src/libsync/owncloudpropagator.cpp

@@ -221,7 +221,7 @@ void blacklistUpdate(SyncJournalDb *journal, SyncFileItem &item)
     }
 }
 
-void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &errorString)
+void PropagateItemJob::done(const SyncFileItem::Status statusArg, const QString &errorString, const ErrorCategory category)
 {
     // Duplicate calls to done() are a logic error
     ENFORCE(_state != Finished);
@@ -283,7 +283,7 @@ void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &error
         qCWarning(lcPropagator) << "Could not complete propagation of" << _item->destination() << "by" << this << "with status" << _item->_status << "and error:" << _item->_errorString;
     else
         qCInfo(lcPropagator) << "Completed propagation of" << _item->destination() << "by" << this << "with status" << _item->_status;
-    emit propagator()->itemCompleted(_item);
+    emit propagator()->itemCompleted(_item, category);
     emit finished(_item->_status);
 
     if (_item->_status == SyncFileItem::FatalError) {
@@ -302,9 +302,9 @@ void PropagateItemJob::slotRestoreJobFinished(SyncFileItem::Status status)
 
     if (status == SyncFileItem::Success || status == SyncFileItem::Conflict
         || status == SyncFileItem::Restoration) {
-        done(SyncFileItem::SoftError, msg);
+        done(SyncFileItem::SoftError, msg, ErrorCategory::GenericError);
     } else {
-        done(status, tr("A file or folder was removed from a read only share, but restoring failed: %1").arg(msg));
+        done(status, tr("A file or folder was removed from a read only share, but restoring failed: %1").arg(msg), ErrorCategory::GenericError);
     }
 }
 
@@ -661,7 +661,7 @@ void OwncloudPropagator::startDirectoryPropagation(const SyncFileItemPtr &item,
             item->_instruction = CSyncEnums::CSYNC_INSTRUCTION_ERROR;
             item->_errorString = tr("Error with the metadata. Getting unexpected metadata format.");
             item->_status = SyncFileItem::NormalError;
-            emit itemCompleted(item);
+            emit itemCompleted(item, OCC::ErrorCategory::GenericError);
         } else {
             directoryPropagationJob->appendJob(new UpdateFileDropMetadataJob(this, item->_file));
             item->_instruction = CSYNC_INSTRUCTION_NONE;
@@ -1087,6 +1087,56 @@ OwncloudPropagator *PropagatorJob::propagator() const
     return qobject_cast<OwncloudPropagator *>(parent());
 }
 
+ErrorCategory PropagatorJob::errorCategoryFromNetworkError(const QNetworkReply::NetworkError error)
+{
+    auto result = ErrorCategory::NoError;
+    switch (error)
+    {
+    case QNetworkReply::NoError:
+        result = ErrorCategory::NoError;
+        break;
+    case QNetworkReply::TemporaryNetworkFailureError:
+    case QNetworkReply::RemoteHostClosedError:
+        result = ErrorCategory::NetworkError;
+        break;
+    case QNetworkReply::ConnectionRefusedError:
+    case QNetworkReply::HostNotFoundError:
+    case QNetworkReply::TimeoutError:
+    case QNetworkReply::OperationCanceledError:
+    case QNetworkReply::SslHandshakeFailedError:
+    case QNetworkReply::NetworkSessionFailedError:
+    case QNetworkReply::BackgroundRequestNotAllowedError:
+    case QNetworkReply::TooManyRedirectsError:
+    case QNetworkReply::InsecureRedirectError:
+    case QNetworkReply::UnknownNetworkError:
+    case QNetworkReply::ProxyConnectionRefusedError:
+    case QNetworkReply::ProxyConnectionClosedError:
+    case QNetworkReply::ProxyNotFoundError:
+    case QNetworkReply::ProxyTimeoutError:
+    case QNetworkReply::ProxyAuthenticationRequiredError:
+    case QNetworkReply::UnknownProxyError:
+    case QNetworkReply::ContentAccessDenied:
+    case QNetworkReply::ContentOperationNotPermittedError:
+    case QNetworkReply::ContentNotFoundError:
+    case QNetworkReply::AuthenticationRequiredError:
+    case QNetworkReply::ContentReSendError:
+    case QNetworkReply::ContentConflictError:
+    case QNetworkReply::ContentGoneError:
+    case QNetworkReply::UnknownContentError:
+    case QNetworkReply::ProtocolUnknownError:
+    case QNetworkReply::ProtocolInvalidOperationError:
+    case QNetworkReply::ProtocolFailure:
+    case QNetworkReply::InternalServerError:
+    case QNetworkReply::OperationNotImplementedError:
+    case QNetworkReply::ServiceUnavailableError:
+    case QNetworkReply::UnknownServerError:
+        result = ErrorCategory::GenericError;
+        break;
+    }
+
+    return result;
+}
+
 // ================================================================================
 
 PropagatorJob::JobParallelism PropagatorCompositeJob::parallelism() const
@@ -1521,7 +1571,7 @@ void CleanupPollsJob::slotPollFinished()
     auto *job = qobject_cast<PollJob *>(sender());
     ASSERT(job);
     if (job->_item->_status == SyncFileItem::FatalError) {
-        emit aborted(job->_item->_errorString);
+        emit aborted(job->_item->_errorString, ErrorCategory::GenericError);
         deleteLater();
         return;
     } else if (job->_item->_status != SyncFileItem::Success) {
@@ -1531,7 +1581,7 @@ void CleanupPollsJob::slotPollFinished()
             qCWarning(lcCleanupPolls) << "database error";
             job->_item->_status = SyncFileItem::FatalError;
             job->_item->_errorString = tr("Error writing metadata to the database");
-            emit aborted(job->_item->_errorString);
+            emit aborted(job->_item->_errorString, ErrorCategory::GenericError);
             deleteLater();
             return;
         }
@@ -1554,7 +1604,7 @@ QString OwncloudPropagator::remotePath() const
 
 void PropagateIgnoreJob::start()
 {
-    SyncFileItem::Status status = _item->_status;
+    auto status = _item->_status;
     if (status == SyncFileItem::NoStatus) {
         if (_item->_instruction == CSYNC_INSTRUCTION_ERROR) {
             status = SyncFileItem::NormalError;
@@ -1568,7 +1618,7 @@ void PropagateIgnoreJob::start()
             _item->_file = conflictRecord.initialBasePath;
         }
     }
-    done(status, _item->_errorString);
+    done(status, _item->_errorString, ErrorCategory::NoError);
 }
 
 }

+ 7 - 3
src/libsync/owncloudpropagator.h

@@ -23,6 +23,7 @@
 #include <QPointer>
 #include <QIODevice>
 #include <QMutex>
+#include <QNetworkReply>
 
 #include "csync.h"
 #include "syncfileitem.h"
@@ -30,6 +31,7 @@
 #include "bandwidthmanager.h"
 #include "accountfwd.h"
 #include "syncoptions.h"
+#include "progressdispatcher.h"
 
 #include <deque>
 
@@ -147,6 +149,8 @@ signals:
 protected:
     [[nodiscard]] OwncloudPropagator *propagator() const;
 
+    static ErrorCategory errorCategoryFromNetworkError(const QNetworkReply::NetworkError error);
+
     /** If this job gets added to a composite job, this will point to the parent.
      *
      * For the PropagateDirectory::_firstJob it will point to
@@ -165,7 +169,7 @@ class PropagateItemJob : public PropagatorJob
 {
     Q_OBJECT
 protected:
-    virtual void done(SyncFileItem::Status status, const QString &errorString = QString());
+    virtual void done(const SyncFileItem::Status status, const QString &errorString, const ErrorCategory category);
 
     /*
      * set a custom restore job message that is used if the restore job succeeded.
@@ -640,7 +644,7 @@ private slots:
 
 signals:
     void newItem(const OCC::SyncFileItemPtr &);
-    void itemCompleted(const OCC::SyncFileItemPtr &);
+    void itemCompleted(const SyncFileItemPtr &item, OCC::ErrorCategory category);
     void progress(const OCC::SyncFileItem &, qint64 bytes);
     void finished(bool success);
 
@@ -721,7 +725,7 @@ public:
     void start();
 signals:
     void finished();
-    void aborted(const QString &error);
+    void aborted(const QString &error, const ErrorCategory errorCategory);
 private slots:
     void slotPollFinished();
 };

+ 5 - 3
src/libsync/progressdispatcher.h

@@ -251,7 +251,9 @@ namespace Progress {
  * in IssuesWidget.
  */
 enum class ErrorCategory {
-    Normal,
+    NoError,
+    GenericError,
+    NetworkError,
     InsufficientRemoteStorage,
 };
 Q_ENUM_NS(OCC::ErrorCategory)
@@ -286,7 +288,7 @@ signals:
     /**
      * @brief: the item was completed by a job
      */
-    void itemCompleted(const QString &folder, const OCC::SyncFileItemPtr &item);
+    void itemCompleted(const QString &folder, const OCC::SyncFileItemPtr &item, const OCC::ErrorCategory category);
 
     /**
      * @brief A new folder-wide sync error was seen.
@@ -300,7 +302,7 @@ signals:
      * @param[out] full error message
      * @param[out] subject (optional)
      */
-    void addErrorToGui(const QString &folder, OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
+    void addErrorToGui(const QString &folder, const OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const OCC::ErrorCategory category);
 
     /**
      * @brief Emitted for a folder when a sync is done, listing all pending conflicts

+ 29 - 29
src/libsync/propagatedownload.cpp

@@ -461,7 +461,7 @@ void PropagateDownloadFile::start()
     SyncJournalFileRecord parentRec;
     if (!propagator()->_journal->getFileRecord(parentPath, &parentRec)) {
         qCWarning(lcPropagateDownload) << "could not get file from local DB" << parentPath;
-        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(parentPath));
+        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(parentPath), ErrorCategory::GenericError);
         return;
     }
 
@@ -478,7 +478,7 @@ void PropagateDownloadFile::start()
         });
         connect(_downloadEncryptedHelper, &PropagateDownloadEncrypted::failed, [this] {
           done(SyncFileItem::NormalError,
-               tr("File %1 cannot be downloaded because encryption information is missing.").arg(QDir::toNativeSeparators(_item->_file)));
+               tr("File %1 cannot be downloaded because encryption information is missing.").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
         });
         _downloadEncryptedHelper->start();
     }
@@ -496,20 +496,20 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
         QString fsPath = propagator()->fullLocalPath(_item->_file);
         if (!FileSystem::verifyFileUnchanged(fsPath, _item->_previousSize, _item->_previousModtime)) {
             propagator()->_anotherSyncNeeded = true;
-            done(SyncFileItem::SoftError, tr("File has changed since discovery"));
+            done(SyncFileItem::SoftError, tr("File has changed since discovery"), ErrorCategory::GenericError);
             return;
         }
 
         qCDebug(lcPropagateDownload) << "dehydrating file" << _item->_file;
         auto r = vfs->dehydratePlaceholder(*_item);
         if (!r) {
-            done(SyncFileItem::NormalError, r.error());
+            done(SyncFileItem::NormalError, r.error(), ErrorCategory::GenericError);
             return;
         }
 
         if (!propagator()->_journal->deleteFileRecord(_item->_originalFile)) {
             qCWarning(lcPropagateDownload) << "could not delete file from local DB" << _item->_originalFile;
-            done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile));
+            done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
             return;
         }
 
@@ -530,12 +530,12 @@ void PropagateDownloadFile::startAfterIsEncryptedIsChecked()
         qCDebug(lcPropagateDownload) << "creating virtual file" << _item->_file;
         // do a klaas' case clash check.
         if (propagator()->localFileNameClash(_item->_file)) {
-            done(SyncFileItem::FileNameClash, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)));
+            done(SyncFileItem::FileNameClash, tr("File %1 can not be downloaded because of a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
             return;
         }
         auto r = vfs->createPlaceholder(*_item);
         if (!r) {
-            done(SyncFileItem::NormalError, r.error());
+            done(SyncFileItem::NormalError, r.error(), ErrorCategory::GenericError);
             return;
         }
         updateMetadata(false);
@@ -675,7 +675,7 @@ void PropagateDownloadFile::startDownload()
         FileSystem::setFileReadOnly(_tmpFile.fileName(), false);
     if (!_tmpFile.open(QIODevice::Append | QIODevice::Unbuffered)) {
         qCWarning(lcPropagateDownload) << "could not open temporary file" << _tmpFile.fileName();
-        done(SyncFileItem::NormalError, _tmpFile.errorString());
+        done(SyncFileItem::NormalError, _tmpFile.errorString(), ErrorCategory::GenericError);
         return;
     }
     // Hide temporary after creation
@@ -689,11 +689,11 @@ void PropagateDownloadFile::startDownload()
             // tab: instead we'll generate a general "disk space low" message and show
             // these detail errors only in the error view.
             done(SyncFileItem::DetailError,
-                tr("The download would reduce free local disk space below the limit"));
+                tr("The download would reduce free local disk space below the limit"), ErrorCategory::GenericError);
             emit propagator()->insufficientLocalStorage();
         } else if (diskSpaceResult == OwncloudPropagator::DiskSpaceCritical) {
             done(SyncFileItem::FatalError,
-                tr("Free space on disk is less than %1").arg(Utility::octetsToString(criticalFreeSpaceLimit())));
+                tr("Free space on disk is less than %1").arg(Utility::octetsToString(criticalFreeSpaceLimit())), ErrorCategory::GenericError);
         }
 
         // Remove the temporary, if empty.
@@ -832,7 +832,7 @@ void PropagateDownloadFile::slotGetFinished()
                 &propagator()->_anotherSyncNeeded, errorBody);
         }
 
-        done(status, errorString);
+        done(status, errorString, errorCategoryFromNetworkError(err));
         return;
     }
 
@@ -880,14 +880,14 @@ void PropagateDownloadFile::slotGetFinished()
         // This happened when trying to resume a file. The Content-Range header was files, Content-Length was == 0
         qCDebug(lcPropagateDownload) << bodySize << _item->_size << _tmpFile.size() << job->resumeStart();
         FileSystem::remove(_tmpFile.fileName());
-        done(SyncFileItem::SoftError, QLatin1String("Broken webserver returning empty content length for non-empty file on resume"));
+        done(SyncFileItem::SoftError, QLatin1String("Broken webserver returning empty content length for non-empty file on resume"), ErrorCategory::GenericError);
         return;
     }
 
     if (bodySize > 0 && bodySize != _tmpFile.size() - job->resumeStart()) {
         qCDebug(lcPropagateDownload) << bodySize << _tmpFile.size() << job->resumeStart();
         propagator()->_anotherSyncNeeded = true;
-        done(SyncFileItem::SoftError, tr("The file could not be downloaded completely."));
+        done(SyncFileItem::SoftError, tr("The file could not be downloaded completely."), ErrorCategory::GenericError);
         return;
     }
 
@@ -895,7 +895,7 @@ void PropagateDownloadFile::slotGetFinished()
         FileSystem::remove(_tmpFile.fileName());
         done(SyncFileItem::NormalError,
             tr("The downloaded file is empty, but the server said it should have been %1.")
-                .arg(Utility::octetsToString(_item->_size)));
+                .arg(Utility::octetsToString(_item->_size)), ErrorCategory::GenericError);
         return;
     }
 
@@ -984,7 +984,7 @@ void PropagateDownloadFile::checksumValidateFailedAbortDownload(const QString &e
 {
     FileSystem::remove(_tmpFile.fileName());
     propagator()->_anotherSyncNeeded = true;
-    done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded."));
+    done(SyncFileItem::SoftError, errMsg, ErrorCategory::GenericError); // tr("The file downloaded with a broken checksum, will be redownloaded."));
 }
 
 void PropagateDownloadFile::deleteExistingFolder()
@@ -1005,7 +1005,7 @@ void PropagateDownloadFile::deleteExistingFolder()
 
     QString error;
     if (!propagator()->createConflict(_item, _associatedComposite, &error)) {
-        done(SyncFileItem::NormalError, error);
+        done(SyncFileItem::NormalError, error, ErrorCategory::GenericError);
     }
 }
 
@@ -1141,7 +1141,7 @@ void PropagateDownloadFile::finalizeDownload()
         if (_downloadEncryptedHelper->decryptFile(_tmpFile)) {
             downloadFinished();
         } else {
-            done(SyncFileItem::NormalError, _downloadEncryptedHelper->errorString());
+            done(SyncFileItem::NormalError, _downloadEncryptedHelper->errorString(), ErrorCategory::GenericError);
         }
     } else {
         downloadFinished();
@@ -1155,7 +1155,7 @@ void PropagateDownloadFile::downloadFinished()
 
     if (_item->_modtime <= 0) {
         FileSystem::remove(_tmpFile.fileName());
-        done(SyncFileItem::NormalError, tr("File %1 has invalid modified time reported by server. Do not save it.").arg(QDir::toNativeSeparators(_item->_file)));
+        done(SyncFileItem::NormalError, tr("File %1 has invalid modified time reported by server. Do not save it.").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
         return;
     }
     Q_ASSERT(_item->_modtime > 0);
@@ -1168,7 +1168,7 @@ void PropagateDownloadFile::downloadFinished()
     _item->_modtime = FileSystem::getModTime(_tmpFile.fileName());
     if (_item->_modtime <= 0) {
         FileSystem::remove(_tmpFile.fileName());
-        done(SyncFileItem::NormalError, tr("File %1 has invalid modified time reported by server. Do not save it.").arg(QDir::toNativeSeparators(_item->_file)));
+        done(SyncFileItem::NormalError, tr("File %1 has invalid modified time reported by server. Do not save it.").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
         return;
     }
     Q_ASSERT(_item->_modtime > 0);
@@ -1193,7 +1193,7 @@ void PropagateDownloadFile::downloadFinished()
         // Make the file a hydrated placeholder if possible
         const auto result = propagator()->syncOptions()._vfs->convertToPlaceholder(_tmpFile.fileName(), *_item, filename);
         if (!result) {
-            done(SyncFileItem::NormalError, result.error());
+            done(SyncFileItem::NormalError, result.error(), ErrorCategory::GenericError);
             return;
         }
     }
@@ -1210,15 +1210,15 @@ void PropagateDownloadFile::downloadFinished()
             qCInfo(lcPropagateDownload) << "downloading case clashed file" << _item->_file;
             const auto caseClashConflictResult = propagator()->createCaseClashConflict(_item, _tmpFile.fileName());
             if (caseClashConflictResult) {
-                done(SyncFileItem::SoftError, *caseClashConflictResult);
+                done(SyncFileItem::SoftError, *caseClashConflictResult, ErrorCategory::GenericError);
             } else {
-                done(SyncFileItem::FileNameClash, tr("File %1 downloaded but it resulted in a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)));
+                done(SyncFileItem::FileNameClash, tr("File %1 downloaded but it resulted in a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
             }
             return;
         } else {
             QString error;
             if (!propagator()->createConflict(_item, _associatedComposite, &error)) {
-                done(SyncFileItem::SoftError, error);
+                done(SyncFileItem::SoftError, error, ErrorCategory::GenericError);
             } else {
                 previousFileExists = false;
             }
@@ -1240,7 +1240,7 @@ void PropagateDownloadFile::downloadFinished()
         const time_t expectedMtime = _item->_previousModtime;
         if (!FileSystem::verifyFileUnchanged(filename, expectedSize, expectedMtime)) {
             propagator()->_anotherSyncNeeded = true;
-            done(SyncFileItem::SoftError, tr("File has changed since discovery"));
+            done(SyncFileItem::SoftError, tr("File has changed since discovery"), ErrorCategory::GenericError);
             return;
         }
     }
@@ -1258,7 +1258,7 @@ void PropagateDownloadFile::downloadFinished()
             propagator()->_anotherSyncNeeded = true;
         }
 
-        done(SyncFileItem::SoftError, error);
+        done(SyncFileItem::SoftError, error, ErrorCategory::GenericError);
         return;
     }
 
@@ -1290,7 +1290,7 @@ void PropagateDownloadFile::downloadFinished()
 
             if (!propagator()->_journal->deleteFileRecord(virtualFile)) {
                 qCWarning(lcPropagateDownload) << "could not delete file from local DB" << virtualFile;
-                done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(virtualFile));
+                done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(virtualFile), ErrorCategory::GenericError);
                 return;
             }
 
@@ -1322,10 +1322,10 @@ void PropagateDownloadFile::updateMetadata(bool isConflict)
     const QString fn = propagator()->fullLocalPath(_item->_file);
     const auto result = propagator()->updateMetadata(*_item);
     if (!result) {
-        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), ErrorCategory::GenericError);
         return;
     } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(_item->_file));
+        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(_item->_file), ErrorCategory::GenericError);
         return;
     }
 
@@ -1337,7 +1337,7 @@ void PropagateDownloadFile::updateMetadata(bool isConflict)
 
     propagator()->_journal->commit("download file start2");
 
-    done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success);
+    done(isConflict ? SyncFileItem::Conflict : SyncFileItem::Success, {}, ErrorCategory::NoError);
 
     // handle the special recall file
     if (!_item->_remotePerm.hasPermission(RemotePermissions::IsShared)

+ 7 - 6
src/libsync/propagateremotedelete.cpp

@@ -45,9 +45,9 @@ void PropagateRemoteDelete::start()
                 if (_deleteEncryptedHelper->networkError() != QNetworkReply::NoError && _deleteEncryptedHelper->networkError() != QNetworkReply::ContentNotFoundError) {
                     status = classifyError(_deleteEncryptedHelper->networkError(), _item->_httpErrorCode, &propagator()->_anotherSyncNeeded);
                 }
-                done(status, _deleteEncryptedHelper->errorString());
+                done(status, _deleteEncryptedHelper->errorString(), ErrorCategory::GenericError);
             } else {
-                done(SyncFileItem::Success);
+                done(SyncFileItem::Success, {}, ErrorCategory::NoError);
             }
         });
         _deleteEncryptedHelper->start();
@@ -94,7 +94,8 @@ void PropagateRemoteDelete::slotDeleteJobFinished()
     if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
         SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
             &propagator()->_anotherSyncNeeded);
-        done(status, _job->errorString());
+
+        done(status, _job->errorString(), errorCategoryFromNetworkError(err));
         return;
     }
 
@@ -109,18 +110,18 @@ void PropagateRemoteDelete::slotDeleteJobFinished()
         done(SyncFileItem::NormalError,
             tr("Wrong HTTP code returned by server. Expected 204, but received \"%1 %2\".")
                 .arg(_item->_httpErrorCode)
-                .arg(_job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
+                .arg(_job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()), ErrorCategory::GenericError);
         return;
     }
 
     if (!propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory())) {
         qCWarning(lcPropagateRemoteDelete) << "could not delete file from local DB" << _item->_originalFile;
-        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile));
+        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
         return;
     }
 
     propagator()->_journal->commit("Remote Remove");
 
-    done(SyncFileItem::Success);
+    done(SyncFileItem::Success, {}, ErrorCategory::NoError);
 }
 }

+ 10 - 10
src/libsync/propagateremotemkdir.cpp

@@ -123,7 +123,7 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
     } else if (err != QNetworkReply::NoError) {
         SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
             &propagator()->_anotherSyncNeeded);
-        done(status, _item->_errorString);
+        done(status, _item->_errorString, errorCategoryFromNetworkError(err));
         return;
     } else if (_item->_httpErrorCode != 201) {
         // Normally we expect "201 Created"
@@ -132,7 +132,7 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
         done(SyncFileItem::NormalError,
             tr("Wrong HTTP code returned by server. Expected 201, but received \"%1 %2\".")
                 .arg(_item->_httpErrorCode)
-                .arg(jobHttpReasonPhraseString));
+                .arg(jobHttpReasonPhraseString), ErrorCategory::GenericError);
         return;
     }
 
@@ -161,10 +161,10 @@ void PropagateRemoteMkdir::finalizeMkColJob(QNetworkReply::NetworkError err, con
             job->start();
         }
     });
-    connect(propfindJob, &PropfindJob::finishedWithError, this, [this]{
-        // ignore the PROPFIND error
+    connect(propfindJob, &PropfindJob::finishedWithError, this, [this] (QNetworkReply *reply) {
+        const auto err = reply ? reply->error() : QNetworkReply::NetworkError::UnknownNetworkError;
         propagator()->_activeJobList.removeOne(this);
-        done(SyncFileItem::NormalError);
+        done(SyncFileItem::NormalError, {}, errorCategoryFromNetworkError(err));
     });
     propfindJob->start();
 }
@@ -176,7 +176,7 @@ void PropagateRemoteMkdir::slotMkdir()
         const auto targetFile = propagator()->fullLocalPath(_item->_renameTarget);
         QString renameError;
         if (!FileSystem::rename(existingFile, targetFile, &renameError)) {
-            done(SyncFileItem::NormalError, renameError);
+            done(SyncFileItem::NormalError, renameError, ErrorCategory::GenericError);
             return;
         }
         emit propagator()->touchedFile(existingFile);
@@ -190,7 +190,7 @@ void PropagateRemoteMkdir::slotMkdir()
     SyncJournalFileRecord parentRec;
     bool ok = propagator()->_journal->getFileRecord(parentPath, &parentRec);
     if (!ok) {
-        done(SyncFileItem::NormalError);
+        done(SyncFileItem::NormalError, {}, ErrorCategory::GenericError);
         return;
     }
 
@@ -257,13 +257,13 @@ void PropagateRemoteMkdir::success()
     // save the file id already so we can detect rename or remove
     const auto result = propagator()->updateMetadata(itemCopy);
     if (!result) {
-        done(SyncFileItem::FatalError, tr("Error writing metadata to the database: %1").arg(result.error()));
+        done(SyncFileItem::FatalError, tr("Error writing metadata to the database: %1").arg(result.error()), ErrorCategory::GenericError);
         return;
     } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-        done(SyncFileItem::FatalError, tr("The file %1 is currently in use").arg(_item->_file));
+        done(SyncFileItem::FatalError, tr("The file %1 is currently in use").arg(_item->_file), ErrorCategory::GenericError);
         return;
     }
 
-    done(SyncFileItem::Success);
+    done(SyncFileItem::Success, {}, ErrorCategory::NoError);
 }
 }

+ 12 - 12
src/libsync/propagateremotemove.cpp

@@ -98,7 +98,7 @@ void PropagateRemoteMove::start()
             SyncJournalFileRecord parentRec;
             bool ok = propagator()->_journal->getFileRecord(parentPath, &parentRec);
             if (!ok) {
-                done(SyncFileItem::NormalError);
+                done(SyncFileItem::NormalError, {}, ErrorCategory::GenericError);
                 return;
             }
 
@@ -167,7 +167,7 @@ void PropagateRemoteMove::start()
             QString error;
             if (!FileSystem::uncheckedRenameReplace(localTargetAlt, localTarget, &error)) {
                 done(SyncFileItem::NormalError, tr("Could not rename %1 to %2, error: %3")
-                     .arg(folderTargetAlt, folderTarget, error));
+                     .arg(folderTargetAlt, folderTarget, error), ErrorCategory::GenericError);
                 return;
             }
             qCInfo(lcPropagateRemoteMove) << "Suffix vfs required local rename of"
@@ -217,7 +217,7 @@ void PropagateRemoteMove::slotMoveJobFinished()
                 << "Could not MOVE file" << filePathOriginal << " to" << filePath
                 << " with error:" << _job->errorString() << " and successfully restored it.";
         }
-        done(status, _job->errorString());
+        done(status, _job->errorString(), ErrorCategory::GenericError);
         return;
     }
 
@@ -228,7 +228,7 @@ void PropagateRemoteMove::slotMoveJobFinished()
         done(SyncFileItem::NormalError,
             tr("Wrong HTTP code returned by server. Expected 201, but received \"%1 %2\".")
                 .arg(_item->_httpErrorCode)
-                .arg(_job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()));
+                .arg(_job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()), ErrorCategory::GenericError);
         return;
     }
 
@@ -245,7 +245,7 @@ void PropagateRemoteMove::finalize()
     SyncJournalFileRecord oldRecord;
     if (!propagator()->_journal->getFileRecord(_item->_originalFile, &oldRecord)) {
         qCWarning(lcPropagateRemoteMove) << "could not get file from local DB" << _item->_originalFile;
-        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(_item->_originalFile));
+        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
         return;
     }
     auto &vfs = propagator()->syncOptions()._vfs;
@@ -257,7 +257,7 @@ void PropagateRemoteMove::finalize()
         // Delete old db data.
         if (!propagator()->_journal->deleteFileRecord(_item->_originalFile)) {
             qCWarning(lcPropagateRemoteMove) << "could not delete file from local DB" << _item->_originalFile;
-            done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile));
+            done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
             return;
         }
         if (!vfs->setPinState(_item->_originalFile, PinState::Inherited)) {
@@ -279,34 +279,34 @@ void PropagateRemoteMove::finalize()
 
     if (!QFileInfo::exists(targetFile)) {
         propagator()->_journal->commit("Remote Rename");
-        done(SyncFileItem::Success);
+        done(SyncFileItem::Success, {}, ErrorCategory::NoError);
         return;
     }
 
     const auto result = propagator()->updateMetadata(newItem);
     if (!result) {
-        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), ErrorCategory::GenericError);
         return;
     } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file));
+        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file), ErrorCategory::GenericError);
         return;
     }
     if (pinState && *pinState != PinState::Inherited
         && !vfs->setPinState(newItem._renameTarget, *pinState)) {
-        done(SyncFileItem::NormalError, tr("Error setting pin state"));
+        done(SyncFileItem::NormalError, tr("Error setting pin state"), ErrorCategory::GenericError);
         return;
     }
 
     if (_item->isDirectory()) {
         propagator()->_renamedDirectories.insert(_item->_file, _item->_renameTarget);
         if (!adjustSelectiveSync(propagator()->_journal, _item->_file, _item->_renameTarget)) {
-            done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
+            done(SyncFileItem::FatalError, tr("Error writing metadata to the database"), ErrorCategory::GenericError);
             return;
         }
     }
 
     propagator()->_journal->commit("Remote Rename");
-    done(SyncFileItem::Success);
+    done(SyncFileItem::Success, {}, ErrorCategory::NoError);
 }
 
 bool PropagateRemoteMove::adjustSelectiveSync(SyncJournalDb *journal, const QString &from_, const QString &to_)

+ 2 - 2
src/libsync/propagateupload.cpp

@@ -629,10 +629,10 @@ void PropagateUploadFileCommon::slotPollFinished()
     finalize();
 }
 
-void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString)
+void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString, const ErrorCategory category)
 {
     _finished = true;
-    PropagateItemJob::done(status, errorString);
+    PropagateItemJob::done(status, errorString, category);
 }
 
 void PropagateUploadFileCommon::checkResettingErrors()

+ 1 - 1
src/libsync/propagateupload.h

@@ -281,7 +281,7 @@ private slots:
     void slotPollFinished();
 
 protected:
-    void done(SyncFileItem::Status status, const QString &errorString = QString()) override;
+    void done(SyncFileItem::Status status, const QString &errorString = QString(), const ErrorCategory category = ErrorCategory::NoError) override;
 
     /**
      * Aborts all running network jobs, except for the ones that mayAbortJob

+ 33 - 28
src/libsync/propagatorjobs.cpp

@@ -102,7 +102,7 @@ void PropagateLocalRemove::start()
     qCInfo(lcPropagateLocalRemove) << "Going to delete:" << filename;
 
     if (propagator()->localFileNameClash(_item->_file)) {
-        done(SyncFileItem::FileNameClash, tr("Could not remove %1 because of a local file name clash").arg(QDir::toNativeSeparators(filename)));
+        done(SyncFileItem::FileNameClash, tr("Could not remove %1 because of a local file name clash").arg(QDir::toNativeSeparators(filename)), ErrorCategory::GenericError);
         return;
     }
 
@@ -110,19 +110,19 @@ void PropagateLocalRemove::start()
     if (_moveToTrash) {
         if ((QDir(filename).exists() || FileSystem::fileExists(filename))
             && !FileSystem::moveToTrash(filename, &removeError)) {
-            done(SyncFileItem::NormalError, removeError);
+            done(SyncFileItem::NormalError, removeError, ErrorCategory::GenericError);
             return;
         }
     } else {
         if (_item->isDirectory()) {
             if (QDir(filename).exists() && !removeRecursively(QString())) {
-                done(SyncFileItem::NormalError, _error);
+                done(SyncFileItem::NormalError, _error, ErrorCategory::GenericError);
                 return;
             }
         } else {
             if (FileSystem::fileExists(filename)
                 && !FileSystem::remove(filename, &removeError)) {
-                done(SyncFileItem::NormalError, removeError);
+                done(SyncFileItem::NormalError, removeError, ErrorCategory::GenericError);
                 return;
             }
         }
@@ -130,11 +130,11 @@ void PropagateLocalRemove::start()
     propagator()->reportProgress(*_item, 0);
     if (!propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->isDirectory())) {
         qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << _item->_originalFile;
-        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile));
+        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
         return;
     }
     propagator()->_journal->commit("Local remove");
-    done(SyncFileItem::Success);
+    done(SyncFileItem::Success, {}, ErrorCategory::NoError);
 }
 
 void PropagateLocalMkdir::start()
@@ -164,13 +164,13 @@ void PropagateLocalMkdir::startLocalMkdir()
             if (!FileSystem::remove(newDirStr, &removeError)) {
                 done(SyncFileItem::NormalError,
                     tr("could not delete file %1, error: %2")
-                        .arg(newDirStr, removeError));
+                        .arg(newDirStr, removeError), ErrorCategory::GenericError);
                 return;
             }
         } else if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT) {
             QString error;
             if (!propagator()->createConflict(_item, _associatedComposite, &error)) {
-                done(SyncFileItem::SoftError, error);
+                done(SyncFileItem::SoftError, error, ErrorCategory::GenericError);
                 return;
             }
         }
@@ -178,13 +178,13 @@ void PropagateLocalMkdir::startLocalMkdir()
 
     if (Utility::fsCasePreserving() && propagator()->localFileNameClash(_item->_file)) {
         qCWarning(lcPropagateLocalMkdir) << "New folder to create locally already exists with different case:" << _item->_file;
-        done(SyncFileItem::FileNameClash, tr("Folder %1 cannot be created because of a local file or folder name clash!").arg(newDirStr));
+        done(SyncFileItem::FileNameClash, tr("Folder %1 cannot be created because of a local file or folder name clash!").arg(newDirStr), ErrorCategory::GenericError);
         return;
     }
     emit propagator()->touchedFile(newDirStr);
     QDir localDir(propagator()->localPath());
     if (!localDir.mkpath(_item->_file)) {
-        done(SyncFileItem::NormalError, tr("Could not create folder %1").arg(newDirStr));
+        done(SyncFileItem::NormalError, tr("Could not create folder %1").arg(newDirStr), ErrorCategory::GenericError);
         return;
     }
 
@@ -197,10 +197,10 @@ void PropagateLocalMkdir::startLocalMkdir()
     newItem._etag = "_invalid_";
     const auto result = propagator()->updateMetadata(newItem);
     if (!result) {
-        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+        done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), ErrorCategory::GenericError);
         return;
     } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file));
+        done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file), ErrorCategory::GenericError);
         return;
     }
     propagator()->_journal->commit("localMkdir");
@@ -208,7 +208,7 @@ void PropagateLocalMkdir::startLocalMkdir()
     auto resultStatus = _item->_instruction == CSYNC_INSTRUCTION_CONFLICT
         ? SyncFileItem::Conflict
         : SyncFileItem::Success;
-    done(resultStatus);
+    done(resultStatus, {}, ErrorCategory::NoError);
 }
 
 PropagateLocalRename::PropagateLocalRename(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
@@ -249,9 +249,9 @@ void PropagateLocalRename::start()
             qCInfo(lcPropagateLocalRename) << "renaming a case clashed file" << _item->_file << _item->_renameTarget;
             const auto caseClashConflictResult = propagator()->createCaseClashConflict(_item, existingFile);
             if (caseClashConflictResult) {
-                done(SyncFileItem::SoftError, *caseClashConflictResult);
+                done(SyncFileItem::SoftError, *caseClashConflictResult, ErrorCategory::GenericError);
             } else {
-                done(SyncFileItem::FileNameClash, tr("File %1 downloaded but it resulted in a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)));
+                done(SyncFileItem::FileNameClash, tr("File %1 downloaded but it resulted in a local file name clash!").arg(QDir::toNativeSeparators(_item->_file)), ErrorCategory::GenericError);
             }
             return;
         }
@@ -259,7 +259,7 @@ void PropagateLocalRename::start()
         emit propagator()->touchedFile(existingFile);
         emit propagator()->touchedFile(targetFile);
         if (QString renameError; !FileSystem::rename(existingFile, targetFile, &renameError)) {
-            done(SyncFileItem::NormalError, renameError);
+            done(SyncFileItem::NormalError, renameError, ErrorCategory::GenericError);
             return;
         }
     }
@@ -267,19 +267,20 @@ void PropagateLocalRename::start()
     SyncJournalFileRecord oldRecord;
     if (!propagator()->_journal->getFileRecord(fileAlreadyMoved ? previousNameInDb : _item->_originalFile, &oldRecord)) {
         qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << _item->_originalFile;
-        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(_item->_originalFile));
+        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(_item->_originalFile), ErrorCategory::GenericError);
         return;
     }
 
     if (fileAlreadyMoved && !deleteOldDbRecord(previousNameInDb)) {
         return;
     } else if (!deleteOldDbRecord(_item->_originalFile)) {
+        qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << _item->_originalFile;
         return;
     }
 
     if (!vfs->setPinState(_item->_renameTarget, pinState)) {
         qCWarning(lcPropagateLocalRename) << "Could not set pin state of" << _item->_renameTarget << "to old value" << pinState;
-        done(SyncFileItem::NormalError, tr("Error setting pin state"));
+        done(SyncFileItem::NormalError, tr("Error setting pin state"), ErrorCategory::GenericError);
         return;
     }
 
@@ -292,10 +293,10 @@ void PropagateLocalRename::start()
         }
         const auto result = propagator()->updateMetadata(newItem);
         if (!result) {
-            done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+            done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), ErrorCategory::GenericError);
             return;
         } else if (*result == Vfs::ConvertToPlaceholderResult::Locked) {
-            done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file));
+            done(SyncFileItem::SoftError, tr("The file %1 is currently in use").arg(newItem._file), ErrorCategory::GenericError);
             return;
         }
     } else {
@@ -312,12 +313,12 @@ void PropagateLocalRename::start()
             SyncJournalFileRecord oldRecord;
             if (!propagator()->_journal->getFileRecord(oldFileName, &oldRecord)) {
                 qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << oldFileName;
-                done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(oldFileNameString));
+                done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(oldFileNameString), OCC::ErrorCategory::GenericError);
                 return;
             }
             if (!propagator()->_journal->deleteFileRecord(oldFileName)) {
                 qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << oldFileName;
-                done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(oldFileNameString));
+                done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(oldFileNameString), OCC::ErrorCategory::GenericError);
                 return;
             }
 
@@ -325,36 +326,40 @@ void PropagateLocalRename::start()
             newItem->_file = newFileNameString;
             const auto result = propagator()->updateMetadata(*newItem);
             if (!result) {
-                done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()));
+                done(SyncFileItem::FatalError, tr("Error updating metadata: %1").arg(result.error()), OCC::ErrorCategory::GenericError);
                 return;
             }
         });
         if (!dbQueryResult) {
-            done(SyncFileItem::FatalError, tr("Failed to propagate directory rename in hierarchy"));
+            done(SyncFileItem::FatalError, tr("Failed to propagate directory rename in hierarchy"), OCC::ErrorCategory::GenericError);
             return;
         }
         propagator()->_renamedDirectories.insert(oldFile, _item->_renameTarget);
         if (!PropagateRemoteMove::adjustSelectiveSync(propagator()->_journal, oldFile, _item->_renameTarget)) {
-            done(SyncFileItem::FatalError, tr("Failed to rename file"));
+            done(SyncFileItem::FatalError, tr("Failed to rename file"), ErrorCategory::GenericError);
             return;
         }
     }
+    if (pinState != PinState::Inherited && !vfs->setPinState(_item->_renameTarget, pinState)) {
+        done(SyncFileItem::NormalError, tr("Error setting pin state"), ErrorCategory::GenericError);
+        return;
+    }
 
     propagator()->_journal->commit("localRename");
 
-    done(SyncFileItem::Success);
+    done(SyncFileItem::Success, {}, ErrorCategory::NoError);
 }
 
 bool PropagateLocalRename::deleteOldDbRecord(const QString &fileName)
 {
     if (SyncJournalFileRecord oldRecord; !propagator()->_journal->getFileRecord(fileName, &oldRecord)) {
         qCWarning(lcPropagateLocalRename) << "could not get file from local DB" << fileName;
-        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(fileName));
+        done(SyncFileItem::NormalError, tr("could not get file %1 from local DB").arg(fileName), OCC::ErrorCategory::GenericError);
         return false;
     }
     if (!propagator()->_journal->deleteFileRecord(fileName)) {
         qCWarning(lcPropagateLocalRename) << "could not delete file from local DB" << fileName;
-        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(fileName));
+        done(SyncFileItem::NormalError, tr("Could not delete file record %1 from local DB").arg(fileName), OCC::ErrorCategory::GenericError);
         return false;
     }
 

+ 18 - 18
src/libsync/syncengine.cpp

@@ -379,7 +379,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                     item->_status = SyncFileItem::Status::NormalError;
                     item->_instruction = CSYNC_INSTRUCTION_ERROR;
                     item->_errorString = tr("Could not update file: %1").arg(result.error());
-                    emit itemCompleted(item);
+                    emit itemCompleted(item, ErrorCategory::GenericError);
                     return;
                 }
             }
@@ -391,14 +391,14 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
                     item->_status = SyncFileItem::Status::NormalError;
                     item->_instruction = CSYNC_INSTRUCTION_ERROR;
                     item->_errorString = tr("Could not update virtual file metadata: %1").arg(r.error());
-                    emit itemCompleted(item);
+                    emit itemCompleted(item, ErrorCategory::GenericError);
                     return;
                 }
             } else {
                 if (!FileSystem::setModTime(filePath, item->_modtime)) {
                     item->_instruction = CSYNC_INSTRUCTION_ERROR;
                     item->_errorString = tr("Could not update file metadata: %1").arg(filePath);
-                    emit itemCompleted(item);
+                    emit itemCompleted(item, ErrorCategory::GenericError);
                     return;
                 }
             }
@@ -412,7 +412,7 @@ void OCC::SyncEngine::slotItemDiscovered(const OCC::SyncFileItemPtr &item)
             }
 
             // This might have changed the shared flag, so we must notify SyncFileStatusTracker for example
-            emit itemCompleted(item);
+            emit itemCompleted(item, ErrorCategory::NoError);
         } else {
             // Update only outdated data from the disk.
 
@@ -516,7 +516,7 @@ void SyncEngine::startSync()
     if (!QDir(_localPath).exists()) {
         _anotherSyncNeeded = DelayedFollowUp;
         // No _tr, it should only occur in non-mirall
-        Q_EMIT syncError(QStringLiteral("Unable to find local sync folder."));
+        Q_EMIT syncError(QStringLiteral("Unable to find local sync folder."), ErrorCategory::GenericError);
         finalize(false);
         return;
     }
@@ -533,7 +533,7 @@ void SyncEngine::startSync()
                 "Placeholders are postfixed with file sizes using Utility::octetsToString()")
                                  .arg(
                                      Utility::octetsToString(freeBytes),
-                                     Utility::octetsToString(minFree)));
+                                     Utility::octetsToString(minFree)), ErrorCategory::GenericError);
             finalize(false);
             return;
         } else {
@@ -562,7 +562,7 @@ void SyncEngine::startSync()
     // This creates the DB if it does not exist yet.
     if (!_journal->open()) {
         qCWarning(lcEngine) << "No way to create a sync journal!";
-        Q_EMIT syncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."));
+        Q_EMIT syncError(tr("Unable to open or create the local sync database. Make sure you have write access in the sync folder."), ErrorCategory::GenericError);
         finalize(false);
         return;
         // database creation error!
@@ -578,7 +578,7 @@ void SyncEngine::startSync()
     _lastLocalDiscoveryStyle = _localDiscoveryStyle;
 
     if (_syncOptions._vfs->mode() == Vfs::WithSuffix && _syncOptions._vfs->fileSuffix().isEmpty()) {
-        Q_EMIT syncError(tr("Using virtual files with suffix, but suffix is not set"));
+        Q_EMIT syncError(tr("Using virtual files with suffix, but suffix is not set"), ErrorCategory::GenericError);
         finalize(false);
         return;
     }
@@ -590,7 +590,7 @@ void SyncEngine::startSync()
         qCInfo(lcEngine) << (usingSelectiveSync ? "Using Selective Sync" : "NOT Using Selective Sync");
     } else {
         qCWarning(lcEngine) << "Could not retrieve selective sync list from DB";
-        Q_EMIT syncError(tr("Unable to read the blacklist from the local database"));
+        Q_EMIT syncError(tr("Unable to read the blacklist from the local database"), ErrorCategory::GenericError);
         finalize(false);
         return;
     }
@@ -631,7 +631,7 @@ void SyncEngine::startSync()
     _discoveryPhase->setSelectiveSyncWhiteList(_journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, &ok));
     if (!ok) {
         qCWarning(lcEngine) << "Unable to read selective sync list, aborting.";
-        Q_EMIT syncError(tr("Unable to read from the sync journal."));
+        Q_EMIT syncError(tr("Unable to read from the sync journal."), ErrorCategory::GenericError);
         finalize(false);
         return;
     }
@@ -654,8 +654,8 @@ void SyncEngine::startSync()
 
     connect(_discoveryPhase.data(), &DiscoveryPhase::itemDiscovered, this, &SyncEngine::slotItemDiscovered);
     connect(_discoveryPhase.data(), &DiscoveryPhase::newBigFolder, this, &SyncEngine::newBigFolder);
-    connect(_discoveryPhase.data(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString) {
-        Q_EMIT syncError(errorString);
+    connect(_discoveryPhase.data(), &DiscoveryPhase::fatalError, this, [this](const QString &errorString, ErrorCategory errorCategory) {
+        Q_EMIT syncError(errorString, errorCategory);
         finalize(false);
     });
     connect(_discoveryPhase.data(), &DiscoveryPhase::finished, this, &SyncEngine::slotDiscoveryFinished);
@@ -760,7 +760,7 @@ void SyncEngine::slotDiscoveryFinished()
     // Sanity check
     if (!_journal->open()) {
         qCWarning(lcEngine) << "Bailing out, DB failure";
-        Q_EMIT syncError(tr("Cannot open the sync journal"));
+        Q_EMIT syncError(tr("Cannot open the sync journal"), ErrorCategory::GenericError);
         finalize(false);
         return;
     } else {
@@ -892,9 +892,9 @@ void SyncEngine::slotDiscoveryFinished()
     finish();
 }
 
-void SyncEngine::slotCleanPollsJobAborted(const QString &error)
+void SyncEngine::slotCleanPollsJobAborted(const QString &error, const ErrorCategory errorCategory)
 {
-    syncError(error);
+    syncError(error, errorCategory);
     finalize(false);
 }
 
@@ -914,12 +914,12 @@ void SyncEngine::setNetworkLimits(int upload, int download)
     }
 }
 
-void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item)
+void SyncEngine::slotItemCompleted(const SyncFileItemPtr &item, const ErrorCategory category)
 {
     _progressInfo->setProgressComplete(*item);
 
     emit transmissionProgress(*_progressInfo);
-    emit itemCompleted(item);
+    emit itemCompleted(item, category);
 }
 
 void SyncEngine::slotPropagationFinished(bool success)
@@ -1211,7 +1211,7 @@ void SyncEngine::slotSummaryError(const QString &message)
         return;
 
     _uniqueErrors.insert(message);
-    emit syncError(message, ErrorCategory::Normal);
+    emit syncError(message, ErrorCategory::GenericError);
 }
 
 void SyncEngine::slotInsufficientLocalStorage()

+ 5 - 5
src/libsync/syncengine.h

@@ -163,16 +163,16 @@ signals:
     void aboutToPropagate(OCC::SyncFileItemVector &);
 
     // after each item completed by a job (successful or not)
-    void itemCompleted(const OCC::SyncFileItemPtr &);
+    void itemCompleted(const OCC::SyncFileItemPtr &item, const OCC::ErrorCategory category);
 
     void transmissionProgress(const OCC::ProgressInfo &progress);
 
     void itemDiscovered(const OCC::SyncFileItemPtr &);
 
     /// We've produced a new sync error of a type.
-    void syncError(const QString &message, OCC::ErrorCategory category = OCC::ErrorCategory::Normal);
+    void syncError(const QString &message, const OCC::ErrorCategory category);
 
-    void addErrorToGui(OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject);
+    void addErrorToGui(const OCC::SyncFileItem::Status status, const QString &errorMessage, const QString &subject, const OCC::ErrorCategory category);
 
     void finished(bool success);
     void started();
@@ -208,11 +208,11 @@ private slots:
      */
     void slotNewItem(const OCC::SyncFileItemPtr &item);
 
-    void slotItemCompleted(const OCC::SyncFileItemPtr &item);
+    void slotItemCompleted(const OCC::SyncFileItemPtr &item, const OCC::ErrorCategory category);
     void slotDiscoveryFinished();
     void slotPropagationFinished(bool success);
     void slotProgress(const OCC::SyncFileItem &item, qint64 curent);
-    void slotCleanPollsJobAborted(const QString &error);
+    void slotCleanPollsJobAborted(const QString &error, const OCC::ErrorCategory category);
 
     /** Records that a file was touched by a job. */
     void slotAddTouchedFile(const QString &fn);

+ 13 - 2
test/testactivitylistmodel.cpp

@@ -456,6 +456,17 @@ public:
         QVERIFY(index.isValid());
     }
 
+    void testActivityAdd(void(OCC::ActivityListModel::*addingMethod)(const OCC::Activity&, OCC::ActivityListModel::ErrorType), OCC::Activity &activity, OCC::ActivityListModel::ErrorType type) {
+        const auto model = testingALM();
+        QCOMPARE(model->rowCount(), 0);
+
+        (model.data()->*addingMethod)(activity, type);
+        QCOMPARE(model->rowCount(), 1);
+
+        const auto index = model->index(0, 0);
+        QVERIFY(index.isValid());
+    }
+
 private slots:
     void initTestCase()
     {
@@ -555,7 +566,7 @@ private slots:
     };
 
     void testAddError() {
-        testActivityAdd(&TestingALM::addErrorToActivityList, testSyncResultErrorActivity);
+        testActivityAdd(&TestingALM::addErrorToActivityList, testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError);
     };
 
     void testAddIgnoredFile() {
@@ -615,7 +626,7 @@ private slots:
         model->addSyncFileItemToActivityList(testSyncFileItemActivity);
         QCOMPARE(model->rowCount(), 51);
 
-        model->addErrorToActivityList(testSyncResultErrorActivity);
+        model->addErrorToActivityList(testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError);
         QCOMPARE(model->rowCount(), 52);
 
         model->addIgnoredFileToList(testFileIgnoredActivity);