From db2f2609079f714c71e5922b564ad361d43e0770 Mon Sep 17 00:00:00 2001 From: Uwe Runtemund Date: Tue, 30 Jan 2024 23:41:43 +0100 Subject: [PATCH 01/37] Initial implementation for tac support on macOS Signed-off-by: Uwe Runtemund --- src/common/preparedsqlquerymanager.h | 2 + src/common/syncjournaldb.cpp | 67 +++- src/common/syncjournaldb.h | 10 + src/common/syncjournalfilerecord.cpp | 3 +- src/common/syncjournalfilerecord.h | 1 + src/csync/csync.h | 2 + src/csync/vio/csync_vio_local_unix.cpp | 47 +++ src/gui/editlocallyjob.cpp | 4 +- src/gui/folder.cpp | 6 +- src/gui/folderwatcher.cpp | 1 + src/gui/folderwatcher_mac.cpp | 7 +- src/libsync/CMakeLists.txt | 2 + src/libsync/discovery.cpp | 23 +- src/libsync/discoveryphase.cpp | 14 +- src/libsync/discoveryphase.h | 5 + src/libsync/filesystem.cpp | 7 +- src/libsync/filesystem.h | 3 +- src/libsync/filetags.cpp | 404 +++++++++++++++++++++++++ src/libsync/filetags.h | 173 +++++++++++ src/libsync/networkjobs.cpp | 2 +- src/libsync/propagatedownload.cpp | 3 + src/libsync/syncengine.cpp | 3 + src/libsync/syncfileitem.cpp | 19 ++ src/libsync/syncfileitem.h | 2 + 24 files changed, 791 insertions(+), 19 deletions(-) create mode 100644 src/libsync/filetags.cpp create mode 100644 src/libsync/filetags.h diff --git a/src/common/preparedsqlquerymanager.h b/src/common/preparedsqlquerymanager.h index a5afa0fe66d93..17ba2c186f69f 100644 --- a/src/common/preparedsqlquerymanager.h +++ b/src/common/preparedsqlquerymanager.h @@ -108,6 +108,8 @@ class OCSYNC_EXPORT PreparedSqlQueryManager GetE2EeLockedFoldersQuery, DeleteE2EeLockedFolderQuery, ListAllTopLevelE2eeFoldersStatusLessThanQuery, + UpdateTagListQuery, + GetTagListQuery, PreparedQueryCount }; diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index 895ad115c4d81..b7aaa8f0559b7 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -49,7 +49,7 @@ Q_LOGGING_CATEGORY(lcDb, "nextcloud.sync.database", QtInfoMsg) #define GET_FILE_RECORD_QUERY \ "SELECT path, inode, modtime, type, md5, fileid, remotePerm, filesize," \ " ignoredChildrenRemote, contentchecksumtype.name || ':' || contentChecksum, e2eMangledName, isE2eEncrypted, " \ - " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe" \ + " lock, lockOwnerDisplayName, lockOwnerId, lockType, lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe, tagList" \ " FROM metadata" \ " LEFT JOIN checksumtype as contentchecksumtype ON metadata.contentChecksumTypeId == contentchecksumtype.id" @@ -77,6 +77,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que rec._isShared = query.intValue(19) > 0; rec._lastShareStateFetchedTimestamp = query.int64Value(20); rec._sharedByMe = query.intValue(21) > 0; + rec._tagList = query.baValue(22); } static QByteArray defaultJournalMode(const QString &dbPath) @@ -832,6 +833,8 @@ bool SyncJournalDb::updateMetadataTableStructure() } commitInternal(QStringLiteral("update database structure: add basePath index")); + addColumn(QStringLiteral("tagList"), QStringLiteral("TEXT")); + return re; } @@ -958,7 +961,8 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & << "lock editor:" << record._lockstate._lockEditorApp << "sharedByMe:" << record._sharedByMe << "isShared:" << record._isShared - << "lastShareStateFetchedTimestamp:" << record._lastShareStateFetchedTimestamp; + << "lastShareStateFetchedTimestamp:" << record._lastShareStateFetchedTimestamp + << "tagList:"< SyncJournalDb::setFileRecord(const SyncJournalFileRecord & const auto query = _queryManager.get(PreparedSqlQueryManager::SetFileRecordQuery, QByteArrayLiteral("INSERT OR REPLACE INTO metadata " "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, " "contentChecksum, contentChecksumTypeId, e2eMangledName, isE2eEncrypted, lock, lockType, lockOwnerDisplayName, lockOwnerId, " - "lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe) " - "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28);"), + "lockOwnerEditor, lockTime, lockTimeout, isShared, lastShareStateFetchedTimestmap, sharedByMe, tagList) " + "VALUES (?1 , ?2, ?3 , ?4 , ?5 , ?6 , ?7, ?8 , ?9 , ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29);"), _db); if (!query) { return query->error(); @@ -1019,6 +1023,7 @@ Result SyncJournalDb::setFileRecord(const SyncJournalFileRecord & query->bindValue(26, record._isShared); query->bindValue(27, record._lastShareStateFetchedTimestamp); query->bindValue(28, record._sharedByMe); + query->bindValue(29, record._tagList); if (!query->exec()) { return query->error(); @@ -1497,6 +1502,60 @@ int SyncJournalDb::getFileRecordCount() return -1; } +bool SyncJournalDb::updateMetadataTagList(const QString &filename,const QByteArray* tagList) +{ + QMutexLocker locker(&_mutex); + + qCInfo(lcDb) << "Updating file tag lists " << filename; + + if (!checkConnect()) { + qCWarning(lcDb) << "Failed to connect database."; + return false; + } + + const qint64 phash = getPHash(filename.toUtf8()); + + const auto query = _queryManager.get(PreparedSqlQueryManager::UpdateTagListQuery, + QByteArrayLiteral("UPDATE metadata" + " SET tagList = ?2" + " WHERE phash = ?1;"), + _db); + if (!query) { + return false; + } + query->bindValue(1, phash); + query->bindValue(2, *tagList); + printf("RMD UPDATE TAG\n"); + return query->exec(); +} + +QByteArray SyncJournalDb::tagList(const QString &file) +{ + QMutexLocker locker(&_mutex); + if (!checkConnect()) + return QByteArray(); + + const qint64 phash = getPHash(file.toUtf8()); + + const auto query = _queryManager.get(PreparedSqlQueryManager::GetTagListQuery, + QByteArrayLiteral("SELECT tagList FROM metadata" + " WHERE phash = ?1;"), + _db); + if (!query) { + return QByteArray(); + } + + query->bindValue(1, phash); + if (!query->exec())return QByteArray(); + + auto next = query->next(); + if (!next.ok || !next.hasData)return QByteArray(); + + QByteArray result = query->baValue(0); + printf("RMD YES %lld %s!\n",phash,result.data()); + return result; +} + bool SyncJournalDb::updateFileRecordChecksum(const QString &filename, const QByteArray &contentChecksum, const QByteArray &contentChecksumType) diff --git a/src/common/syncjournaldb.h b/src/common/syncjournaldb.h index ae4824a41766c..c24cd8d777f5c 100644 --- a/src/common/syncjournaldb.h +++ b/src/common/syncjournaldb.h @@ -246,6 +246,16 @@ class OCSYNC_EXPORT SyncJournalDb : public QObject void setDataFingerprint(const QByteArray &dataFingerprint); QByteArray dataFingerprint(); + /*! + * \brief Update the taglist in the metadata table. + */ + bool updateMetadataTagList(const QString &file, + const QByteArray* tagList); + + /*! + * \brief Returns the taglist of the queried file. + */ + QByteArray tagList(const QString &file); // Conflict record functions diff --git a/src/common/syncjournalfilerecord.cpp b/src/common/syncjournalfilerecord.cpp index 6ca9f43af4397..566312f27dec7 100644 --- a/src/common/syncjournalfilerecord.cpp +++ b/src/common/syncjournalfilerecord.cpp @@ -51,6 +51,7 @@ bool operator==(const SyncJournalFileRecord &lhs, && lhs._fileSize == rhs._fileSize && lhs._remotePerm == rhs._remotePerm && lhs._serverHasIgnoredFiles == rhs._serverHasIgnoredFiles - && lhs._checksumHeader == rhs._checksumHeader; + && lhs._checksumHeader == rhs._checksumHeader + && lhs._tagList == rhs._tagList; } } diff --git a/src/common/syncjournalfilerecord.h b/src/common/syncjournalfilerecord.h index 7270fac137962..cf8f4b8967921 100644 --- a/src/common/syncjournalfilerecord.h +++ b/src/common/syncjournalfilerecord.h @@ -87,6 +87,7 @@ class OCSYNC_EXPORT SyncJournalFileRecord bool _isShared = false; qint64 _lastShareStateFetchedTimestamp = 0; bool _sharedByMe = false; + QByteArray _tagList; }; QDebug& operator<<(QDebug &stream, const SyncJournalFileRecord::EncryptionStatus status); diff --git a/src/csync/csync.h b/src/csync/csync.h index 235f0cd729afb..2680f40319492 100644 --- a/src/csync/csync.h +++ b/src/csync/csync.h @@ -221,6 +221,8 @@ struct OCSYNC_EXPORT csync_file_stat_s { QByteArray directDownloadCookies; QByteArray original_path; // only set if locale conversion fails + QByteArray tagList; // \n-separated List of tags + // In the local tree, this can hold a checksum and its type if it is // computed during discovery for some reason. // In the remote tree, this will have the server checksum, if available. diff --git a/src/csync/vio/csync_vio_local_unix.cpp b/src/csync/vio/csync_vio_local_unix.cpp index c5e22abb3c8b0..b6ccab8fdd4e6 100644 --- a/src/csync/vio/csync_vio_local_unix.cpp +++ b/src/csync/vio/csync_vio_local_unix.cpp @@ -28,6 +28,11 @@ #include +#ifdef __APPLE__//macOS, iOS +#include +#include +#endif + #include "c_private.h" #include "c_lib.h" #include "csync.h" @@ -120,6 +125,48 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *h file_stat->type = ItemTypeSkip; } +#ifdef __APPLE__ +//TODO: GETTAGLISTFROMLOCALFILE + // Create necessary system related objects + CFStringRef cfstr = CFStringCreateWithCString(kCFAllocatorDefault, + fullPath.constData(), + kCFStringEncodingUTF8); + CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + cfstr, + kCFURLPOSIXPathStyle, + dirent->d_type == DT_DIR); + + // Query tags + CFArrayRef labels=NULL; + Boolean result = CFURLCopyResourcePropertyForKey(urlref, + kCFURLTagNamesKey, + &labels, + NULL); + + if(result==true && labels != NULL){ + // Extract the labels to our array + int count = (int) CFArrayGetCount(labels); + + if(count>0){ + QStringList tagarray; + + for(int index=0;index0)tagarray << QString::fromCFString(str); + } + tagarray.sort(Qt::CaseInsensitive); + QString tagList = tagarray.join(QChar(0x000A)); + file_stat->tagList=tagList.toUtf8(); + } + } + + // Clean up + CFRelease(cfstr); + CFRelease(urlref); + if(labels!=NULL)CFRelease(labels); + +#endif + // Override type for virtual files if desired if (vfs) { // Directly modifies file_stat->type. diff --git a/src/gui/editlocallyjob.cpp b/src/gui/editlocallyjob.cpp index 167cf99c33cb9..65bb65f764f32 100644 --- a/src/gui/editlocallyjob.cpp +++ b/src/gui/editlocallyjob.cpp @@ -213,7 +213,9 @@ void EditLocallyJob::fetchRemoteFileParentInfo() QByteArrayLiteral("http://owncloud.org/ns:size"), QByteArrayLiteral("http://owncloud.org/ns:id"), QByteArrayLiteral("http://owncloud.org/ns:permissions"), - QByteArrayLiteral("http://owncloud.org/ns:checksums")}; + QByteArrayLiteral("http://owncloud.org/ns:checksums"), + QByteArrayLiteral("http://owncloud.org/ns:tags"), + QByteArrayLiteral("http://nextcloud.org/ns:system-tags")}; job->setProperties(props); connect(job, &LsColJob::directoryListingIterated, this, &EditLocallyJob::slotDirectoryListingIterated); diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index ace5524cb3d91..983392b03d27a 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -558,7 +558,7 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) qCDebug(lcFolder) << "Changed path is not contained in folder, ignoring:" << path; return; } - + printf("RMD Change 2\n"); auto relativePath = path.midRef(this->path().size()); if (pathIsIgnored(path)) { @@ -610,7 +610,7 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) // an attribute change (pin state) that caused the notification bool spurious = false; if (record.isValid() - && !FileSystem::fileChanged(path, record._fileSize, record._modtime)) { + && !FileSystem::fileChanged(path, record._fileSize, record._modtime,record._tagList)) { spurious = true; if (auto pinState = _vfs->pinState(relativePath.toString())) { @@ -628,7 +628,7 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) } } warnOnNewExcludedItem(record, relativePath); - + printf("RMD Change 3\n"); emit watchedFileChangedExternally(path); // Also schedule this folder for a sync, but only after some delay: diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 07436463a81ab..0bcf518e8506e 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -263,6 +263,7 @@ void FolderWatcher::changeDetected(const QStringList &paths) qCInfo(lcFolderWatcher) << "Detected changes in paths:" << changedPaths; for (const auto &path : changedPaths) { + printf("RMD Change 1\n"); emit pathChanged(path); } } diff --git a/src/gui/folderwatcher_mac.cpp b/src/gui/folderwatcher_mac.cpp index 588b94a27b561..be81315c52618 100644 --- a/src/gui/folderwatcher_mac.cpp +++ b/src/gui/folderwatcher_mac.cpp @@ -71,8 +71,9 @@ static void callback( | kFSEventStreamEventFlagItemInodeMetaMod // for mtime change | kFSEventStreamEventFlagItemRenamed // also coming for moves to trash in finder | kFSEventStreamEventFlagItemModified // for content change - | kFSEventStreamEventFlagItemCloned; // for cloned items (since 10.13) - //We ignore other flags, e.g. for owner change, xattr change, Finder label change etc + | kFSEventStreamEventFlagItemCloned // for cloned items (since 10.13) + | kFSEventStreamEventFlagItemXattrMod; // for tags change (which are stored as xattr) + //We ignore other flags, e.g. for owner change etc. QStringList paths; CFArrayRef eventPaths = (CFArrayRef)eventPathsVoid; @@ -111,7 +112,7 @@ void FolderWatcherPrivate::startWatching() pathsToWatch, kFSEventStreamEventIdSinceNow, 0, // latency - kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagIgnoreSelf); + kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagIgnoreSelf | kFSEventStreamEventFlagItemXattrMod); CFRelease(pathsToWatch); CFRelease(folderCF); diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 6749ff4adc5e2..f01631ea88dbd 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -46,6 +46,8 @@ set(libsync_SRCS encryptedfoldermetadatahandler.cpp filesystem.h filesystem.cpp + filetags.h + filetags.cpp helpers.cpp httplogger.h httplogger.cpp diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index 35bbaed4acbbe..e4ff310163523 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -17,6 +17,7 @@ #include "common/filesystembase.h" #include "common/syncjournaldb.h" #include "filesystem.h" +#include "filetags.h" #include "syncfileitem.h" #include "progressdispatcher.h" #include @@ -476,7 +477,8 @@ void ProcessDirectoryJob::processFile(PathTuple path, << " | e2ee: " << dbEntry.isE2eEncrypted() << "/" << serverEntry.isE2eEncrypted() << " | e2eeMangledName: " << dbEntry.e2eMangledName() << "/" << serverEntry.e2eMangledName << " | file lock: " << localFileIsLocked << "//" << serverFileIsLocked - << " | metadata missing: /" << localEntry.isMetadataMissing << '/'; + << " | metadata missing: /" << localEntry.isMetadataMissing << '/' + << " | tagList: " << dbEntry._tagList << "//" << localEntry.tagList << "//" << serverEntry.tagList; if (localEntry.isValid() && !serverEntry.isValid() @@ -485,6 +487,24 @@ void ProcessDirectoryJob::processFile(PathTuple path, qCWarning(lcDisco) << "File" << path._original << "was modified before the last sync run and is not in the sync journal and server"; } + if(localEntry.isValid() + && dbEntry.isValid()) { + FileTagManager* tm = FileTagManager::GetInstance(); + + // Try tag pull by default (order pull,push is relevant!) + tm->pullTags(_discoveryData->_localDir % path._original, + localEntry, + dbEntry); + + // Only if all three entries are valid, we make a full sync. + if(serverEntry.isValid()) { + tm->pushTags(_discoveryData->_localDir % path._original, + localEntry, + serverEntry, + dbEntry); + } + } + if (_discoveryData->isRenamed(path._original)) { qCDebug(lcDisco) << "Ignoring renamed"; return; // Ignore this. @@ -650,6 +670,7 @@ void ProcessDirectoryJob::processFileAnalyzeRemoteInfo(const SyncFileItemPtr &it item->_lockEditorApp = serverEntry.lockEditorApp; item->_lockTime = serverEntry.lockTime; item->_lockTimeout = serverEntry.lockTimeout; + item->_tagList = serverEntry.tagList; qCDebug(lcDisco()) << "item lock for:" << item->_file << item->_locked diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 7ae2022841803..96acc1c203791 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -16,6 +16,7 @@ #include "common/utility.h" #include "configfile.h" #include "discovery.h" +#include "filetags.h" #include "helpers.h" #include "progressdispatcher.h" @@ -344,6 +345,7 @@ void DiscoverySingleLocalDirectoryJob::run() { i.isVirtualFile = dirent->type == ItemTypeVirtualFile || dirent->type == ItemTypeVirtualFileDownload; i.isMetadataMissing = dirent->is_metadata_missing; i.type = dirent->type; + i.tagList=dirent->tagList; results.push_back(i); } if (errno != 0) { @@ -391,7 +393,9 @@ void DiscoverySingleDirectoryJob::start() << "http://owncloud.org/ns:downloadURL" << "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions" - << "http://owncloud.org/ns:checksums"; + << "http://owncloud.org/ns:checksums" + << "http://owncloud.org/ns:tags" + << "http://nextcloud.org/ns:system-tags"; if (_isRootPath) props << "http://owncloud.org/ns:data-fingerprint"; @@ -538,7 +542,13 @@ static void propertyMapToRemoteInfo(const QMap &map, RemoteInf result.lockTimeout = 0; } } - + if(property == "system-tags" || property == "tags"){ + QByteArray list =FileTagManager::fromPropertiesToTagList(value); + if(list.size()>0){ + if(result.tagList.size()>0)result.tagList.append('\n'); + result.tagList.append(list); + } + } } if (result.isDirectory && map.contains("size")) { diff --git a/src/libsync/discoveryphase.h b/src/libsync/discoveryphase.h index ccad6bb92a361..379702f4cf276 100644 --- a/src/libsync/discoveryphase.h +++ b/src/libsync/discoveryphase.h @@ -86,6 +86,8 @@ struct RemoteInfo QString lockEditorApp; qint64 lockTime = 0; qint64 lockTimeout = 0; + + QByteArray tagList; // DAV property system-tags }; struct LocalInfo @@ -102,6 +104,9 @@ struct LocalInfo bool isVirtualFile = false; bool isSymLink = false; bool isMetadataMissing = false; + + QByteArray tagList; + [[nodiscard]] bool isValid() const { return !name.isNull(); } }; diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index 7c58c7b2bde45..c850deced58c4 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -13,6 +13,7 @@ */ #include "filesystem.h" +#include "filetags.h" #include "common/utility.h" #include @@ -85,10 +86,12 @@ bool FileSystem::setModTime(const QString &filename, time_t modTime) bool FileSystem::fileChanged(const QString &fileName, qint64 previousSize, - time_t previousMtime) + time_t previousMtime, + const QByteArray &taglist) { return getSize(fileName) != previousSize - || getModTime(fileName) != previousMtime; + || getModTime(fileName) != previousMtime + || FileTagManager::GetInstance()->readTagListFromLocalFile(fileName) != taglist; } bool FileSystem::verifyFileUnchanged(const QString &fileName, diff --git a/src/libsync/filesystem.h b/src/libsync/filesystem.h index 602f74bb7f2bc..5a488b165d4bf 100644 --- a/src/libsync/filesystem.h +++ b/src/libsync/filesystem.h @@ -77,7 +77,8 @@ namespace FileSystem { */ bool OWNCLOUDSYNC_EXPORT fileChanged(const QString &fileName, qint64 previousSize, - time_t previousMtime); + time_t previousMtime, + const QByteArray &taglist); /** * @brief Like !fileChanged() but with verbose logging if the file *did* change. diff --git a/src/libsync/filetags.cpp b/src/libsync/filetags.cpp new file mode 100644 index 0000000000000..5428ed717de93 --- /dev/null +++ b/src/libsync/filetags.cpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2024 by Uwe Runtemund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifdef __APPLE__//macOS, iOS +#include +#endif + +#include +#include + +#include "common/syncjournalfilerecord.h" +#include "filetags.h" + +namespace OCC{ + + FileTagManager* gFileTagManger=NULL; + + FileTagManager::FileTagManager(AccountPtr account,SyncJournalDb* journal):QObject(NULL) + { + _account=account; + _journal=journal; + } + + void FileTagManager::Init(AccountPtr account,SyncJournalDb* journal) + { + ASSERT(gFileTagManger==NULL); + gFileTagManger=new FileTagManager(account,journal); + } + + FileTagManager* FileTagManager::GetInstance() + { + ASSERT(gFileTagManger!=NULL); + return gFileTagManger; + } + + + QByteArray FileTagManager::fromPropertiesToTagList(const QString &properties) + { + if(properties==NULL || properties.isEmpty()) return QByteArray(); + + QStringList tags; + QString token; + QXmlStreamReader reader(""%properties%""); + bool insideTag = false; + + while (!reader.atEnd()) + { + QXmlStreamReader::TokenType type = reader.readNext(); + QString name = reader.name().toString(); + + // Start elements with DAV: + if (type == QXmlStreamReader::StartElement && (name=="system-tag" || name=="tag")) + { + insideTag=true; + } + else if(type== QXmlStreamReader::Characters && insideTag) + { + token.append(reader.text()); + } + else if (type == QXmlStreamReader::EndElement && (name == "system-tag" || name=="tag")) + { + if(token.size()>0)tags << token; + token.clear(); + insideTag=false; + } + } + + tags.sort(Qt::CaseInsensitive); + printf("RMD TAG %s\n",tags.join(QChar(';')).toUtf8().data()); + return tags.join(QChar(0x000A)).toUtf8(); + } + + QByteArray FileTagManager::readTagListFromLocalFile(const QString &path) + { + + // Create necessary system related objects + CFStringRef cfstr = path.toCFString(); + CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + cfstr, + kCFURLPOSIXPathStyle, + false); + + // Query tags + CFArrayRef labels=NULL; + Boolean result = CFURLCopyResourcePropertyForKey(urlref, + kCFURLTagNamesKey, + &labels, + NULL); + + QStringList tagarray; + + if(result==true && labels != NULL){ + // Extract the labels to our array + int count = (int) CFArrayGetCount(labels); + + if(count>0){ + + for(int index=0;index0)tagarray << QString::fromCFString(str); + } + } + } + + tagarray.sort(Qt::CaseInsensitive); + + // Clean up + CFRelease(cfstr); + CFRelease(urlref); + if(labels!=NULL)CFRelease(labels); + + return tagarray.join(QChar(0x000A)).toUtf8(); + } + + bool FileTagManager::writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist) + { + printf("RMD SYNC LOCAL FS\n"); + QStringList strlist=QString(taglist).split(QChar(0x000A)); + + //Create new array and append tag + int newsize=strlist.size(); + CFStringRef* strs = new CFStringRef[newsize]; + for(int index=0;indextagList(file)); + } + + void FileTagManager::pushTags(const QString &fullpath, + const LocalInfo &localEntry, + const RemoteInfo &serverEntry, + const SyncJournalFileRecord &dbEntry) + { + if(localEntry.isValid() + && dbEntry.isValid() + && serverEntry.isValid()) + { + + if(localEntry.tagList!= dbEntry._tagList + || localEntry.tagList!=serverEntry.tagList + || dbEntry._tagList!=serverEntry.tagList) + { + printf("RMD TAG PUSH %s\n",fullpath.toUtf8().data()); + + // For syncing tags, the following matrix applies: + // + // LC: Local, DB: SyncDatabase, RM: Remote + // X: Tag is present, -: Tag is not present + //----------------------------------------- + // LC DB RM What happend ACTION + //----------------------------------------- + // 1 - - - no tags NOP + // 2 X X X tag present NOP + // 3 X - X lc/rm added ADD (DB) + // 4 X - - lc added ADD (RM+DB) + // 5 - - X rm added ADD (LC+DB) + // 6 - X - lc/rm deleted DEL (DB) + // 7 - X X lc deleted DEL (RM+DB) + // 8 X X - rm deleted DEL (LC+DB) + //------------------------------------------ + + // Note: The lists from the entries are always alphabetically sorted. + QStringList lcTags; + QStringList dbTags; + QStringList rmTags; + QStringList syncedTagList; + + // Avoid adding empty entries to the list + if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); + if(serverEntry.tagList.size()>0)rmTags=QString(serverEntry.tagList).split(QChar(0x000A)); + if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); + + + // The "new" synced list ist a joined list of all present tags, and then deleting + // Tags according to row 6-8 of above table + syncedTagList << lcTags; + syncedTagList << rmTags; + syncedTagList << dbTags; + syncedTagList.removeDuplicates(); + syncedTagList.sort(Qt::CaseInsensitive); + + for(int index=0;index"); + tags.append(list[index]); + tags.append(""); + } + + QByteArray newTagList = list.join(QChar(0x000A)).toUtf8(); + + QMap properties; + properties.insert(QString("http://owncloud.org/ns:tags").toUtf8(),tags.toUtf8()); + + ProppatchJob* job = new ProppatchJob(_account,dbEntry._path); + job->setProperties(properties); + + QObject::connect(job, &ProppatchJob::success, this, [=](){ + pushTagsStep2(fullpath,newTagList,dbEntry,syncLocal,syncDb); + }); + + QObject::connect(job, &ProppatchJob::finishedWithError, this,[=]() { + // Do nothing, ignore. + printf("RMD PROPPATCH failure\n"); + }); + + job->start(); + } + + void FileTagManager::pushTagsStep2(const QString &fullpath,const QByteArray &newTagList,const SyncJournalFileRecord &dbEntry,bool syncLocal,bool syncDb) + { + printf("RMD PUSH STEP 2 %i,%i\n",syncLocal,syncDb); + bool localSuccess=true; + if(syncLocal) + { + localSuccess=writeTagListToLocalFile(fullpath,newTagList); + printf("RMD LOCAL WRITE %i\n",localSuccess); + } + + if(localSuccess && syncDb) + { + bool success=_journal->updateMetadataTagList(dbEntry._path,&newTagList); + printf("RMD DB UPDATE %i\n",success); + } + }; + + + void FileTagManager::pullTags(const QString &fullpath, + const LocalInfo &localEntry, + const SyncJournalFileRecord &dbEntry) + { + if(localEntry.isValid() + && dbEntry.isValid()) + { + + if(localEntry.tagList!= dbEntry._tagList) + { + printf("RMD TAG PULL %s\n",fullpath.toUtf8().data()); + + // For syncing tags, the following matrix applies: + // + // LC DB What happened Action + // 1 - - no tags NOP + // 2 X X tag present NOP + // 3 - X rm added ADD (LC) + // 4 X - rm added ADD (RM) + // + // If db taglist is empty, we assume an initial sync for that file and push the tags to the + // server, even if we + + // Note: The lists from the entries are always alphabetically sorted. + QStringList lcTags; + QStringList dbTags; + QStringList syncedTagList; + bool initialSync=false; + + // Avoid addint empty entries to the list + if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); + if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); + if(dbEntry._tagList.size()==0)initialSync=true; + + // The "new" synced list ist just a joined list of all present tags, because we do not + // delete here any tags. + syncedTagList << lcTags; + syncedTagList << dbTags; + syncedTagList.removeDuplicates(); + syncedTagList.sort(Qt::CaseInsensitive); + + QByteArray newTagList = syncedTagList.join(QChar(0x000A)).toUtf8(); + + bool syncLocal = newTagList != localEntry.tagList; + bool syncDb = newTagList != dbEntry._tagList; + + printf("RMD pull sync tags %s (%s) %i,%i\n", + fullpath.toUtf8().data(), + syncedTagList.join(QChar((int)' ')).toUtf8().data(), + syncLocal, + syncDb); + + // We sync in that order: + // 1. Local + // 2. Database (only if local is successfull) + + if(initialSync) + { + printf("RMD INITIAL!\n"); + } + + bool localSuccess=true; + if(syncLocal) + { + localSuccess=writeTagListToLocalFile(fullpath,newTagList); + printf("RMD LOCAL WRITE %i\n",localSuccess); + } + + if(localSuccess && syncDb) + { + bool success=_journal->updateMetadataTagList(dbEntry._path,&newTagList); + printf("RMD DB UPDATE %i\n",success); + } + + } + } + } + +} diff --git a/src/libsync/filetags.h b/src/libsync/filetags.h new file mode 100644 index 0000000000000..34b07d0dab213 --- /dev/null +++ b/src/libsync/filetags.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 by Uwe Runtemund + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#ifndef filetags_h +#define filetags_h + +#include "account.h" +#include "discoveryphase.h" +#include "networkjobs.h" +#include "owncloudlib.h" +#include "owncloudpropagator.h" +#include "syncfileitem.h" + +#include +#include + +namespace OCC +{ +/*! + * THE FILE TAG SYNCING PROCESS + * + * Tags are special keywords for files or directories to give them an additional meaning. This + * feature is provided by Nextcloud as well as by many operating systems. So it is a straight + * forward assumption to expect, that also Nextcloud will sync tags with the local file system. + * + * Tags are stored on Nextcloud and can be fetched by a PROPFIND query. Locally the properties + * are stored in the operating file system as well as in the syncronization database. Because the + * tags can only be queried by a PROPFIND and our goal is not to heavily grow the number of slow + * network queries, we go the following way for synchronizing the tags: + * + * In theory for synchronizing correctly, three locations are relevant: + * 1. The local file system (LC) + * 2. The local sync database (DB) + * 3. The remote nextcloud server (RM) + * + * Changes of the tags are done on the RM or LC. Also we add the implementation on already existing + * Nextcloud instances with tags existing propably local or remote, we need a gracefully strategy + * to synchronizes all these tags without loosing any of them. + * + *The following matrix show all possible situations and solutions for syncing the tags: + * + * LC: Local, DB: SyncDatabase, RM: Remote + * X: Tag is present, -: Tag is not present + *----------------------------------------- + * LC DB RM What happend ACTION + *----------------------------------------- + * 1 - - - no tags NOP + * 2 X X X tag present NOP + * 3 X - X lc/rm added ADD (DB) + * 4 X - - lc added ADD (RM+DB) + * 5 - - X rm added ADD (LC+DB) + * 6 - X - lc/rm deleted DEL (DB) + * 7 - X X lc deleted DEL (RM+DB) + * 8 X X - rm deleted DEL (LC+DB) + *------------------------------------------ + * + * IF WE HAVE ALL THREE POINTS, WE SYNC THAT WAY! + * + * In fact, most of the time, we have not all three data points available. Most of the time we have + * LC/DB or RM/DB, so we have to deal a little bit different, to reach the same approach. + * To avoid an massive increasment of the network polls, we use following approach: + * + * - DB is frequently overridden by the client with the RM value, so we assume, the db value is the + * same as remote: + * LC DB What happened Action + * 1 - - no tags NOP + * 2 X X tag present NOP + * 3 - X rm added ADD (LC) + * 4 X - lc added ADD (RM) + * + * So this kind of sync will not delete tags. That is important to prevent local setted tags befor + * adding the tag feature will not be deleted. That also means, that it is not possible to "delete" + * local tags at the moment. + * + * In "language" of git, the first sync step with 3 data points available is like a "PULL & COMMIT & PUSH" + * an the second one is like a "PULL". + * + * TODO: CURRENTLY IT SEEMS THAT PROPPATCH IS NOT IMPLEMENTED LOOK AT: https://github.com/nextcloud/server/blob/master/apps/dav/lib/SystemTag/SystemTagPlugin.php + * TODO: BUT IT IS IMPLEMENTED FOR OC:TAG (NOT SYSTEM:TAG), OC:TAG IS NOT VISIBLE ON THE WEB! WHY? + * TODO: THAT MEAN IN FACT, THAT JUST A "DOWN-SYNC" IS POSSIBLE, SO WE HAVE TO ENSURE, NOT LOOSING + * TODO: LOCAL TAGS. AND PROCEED RESEARCH + * + *curl 'https://cloud.runtemund.de/remote.php/dav/files/USER/Anleitung.md' \ + --user USER:XXX \ + * --request PROPPATCH \ + --data ' + + + Test + + ' + * + */ + class OCSYNC_EXPORT FileTagManager : public QObject + { + Q_OBJECT + public: + + //! Returns an instance of the FileTagManager + static FileTagManager* GetInstance(); + + //! Initialize the global FileTagManager, must be called as early as possible/necessary. + static void Init(AccountPtr account,SyncJournalDb* journal); + +/*! + * \brief Converts the given XML fragment of system-tag entries to a sorted list of tags. + * + * The list is a QByteArray with the content. The tags are delimited by 0x0A ('\\n') + */ +static QByteArray fromPropertiesToTagList(const QString &properties); + +//! Reads the tags from the local file system. +QByteArray readTagListFromLocalFile(const QString &localpath); + +/// Restore the tags in the local file system. +/// \note: is needed after file download, +bool restoreLocalFileTags(const QString &localdir,const QString &file); + +//! Makes a "full" synchronization. Will only do, if all data entries are valid. +void pushTags(const QString &fullpath, + const LocalInfo &localEntry, + const RemoteInfo &serverEntry, + const SyncJournalFileRecord &dbEntry); + +//! Makes a "local" synchronization. Will do a server sync, if initial sync is assumed +void pullTags(const QString &fullpath, + const LocalInfo &localEntry, + const SyncJournalFileRecord &dbEntry); + + private: + + //! Reference to account. Needed for network jobs. + AccountPtr _account; + + //! Reference to sync journal. Needed for database queries. + SyncJournalDb* _journal; + + //! Private constructor. Needs to be private, to avoid multiple instances (we are a singleton). + //! TODO: It seems, that this approach is not good with multiple accounts. + FileTagManager(AccountPtr account,SyncJournalDb* journal); + + //! Helper method for pushTags + void pushTagsStep1(const QString &fullpath, + QStringList list, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb); + + //! Helper method for pushTags + void pushTagsStep2(const QString &fullpath, + const QByteArray &newTagList, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb); + + //! Write the tag list to the local file. + bool writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist); + + }; + +} // namespace OCC + +#endif /* filetags_h */ diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 700afb41741f5..e1bc8e6aa8012 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -891,7 +891,7 @@ void ProppatchJob::start() " \n" + propStr + " \n" "\n"; - + printf("RMD QUERY: %s\n",xml.data()); auto *buf = new QBuffer(this); buf->setData(xml); buf->open(QIODevice::ReadOnly); diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index caa6922dbdb24..d826276a5cb23 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -21,6 +21,7 @@ #include "common/syncjournalfilerecord.h" #include "common/utility.h" #include "filesystem.h" +#include "filetags.h" #include "propagatorjobs.h" #include #include @@ -1364,6 +1365,8 @@ void PropagateDownloadFile::updateMetadata(bool isConflict) if (isLikelyFinishedQuickly() && duration > 5 * 1000) { qCWarning(lcPropagateDownload) << "WARNING: Unexpectedly slow connection, took" << duration << "msec for" << _item->_size - _resumeStart << "bytes for" << _item->_file; } + + FileTagManager::GetInstance()->restoreLocalFileTags(propagator()->localPath(),_item->_file); } void PropagateDownloadFile::slotDownloadProgress(qint64 received, qint64) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 72c043fbbafc3..164d99ef36f45 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -24,6 +24,7 @@ #include "common/syncfilestatus.h" #include "csync_exclude.h" #include "filesystem.h" +#include "filetags.h" #include "deletejob.h" #include "propagatedownload.h" #include "common/asserts.h" @@ -115,6 +116,8 @@ SyncEngine::SyncEngine(AccountPtr account, connect(this, &SyncEngine::finished, [this](bool /* finished */) { _journal->keyValueStoreSet("last_sync", QDateTime::currentSecsSinceEpoch()); }); + + FileTagManager::Init(account,journal); } SyncEngine::~SyncEngine() diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index 4a169d9cbefbb..b77bb252485e1 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -18,6 +18,7 @@ #include "common/utility.h" #include "helpers.h" #include "filesystem.h" +#include "filetags.h" #include #include "csync/vio/csync_vio_local.h" @@ -125,6 +126,7 @@ SyncJournalFileRecord SyncFileItem::toSyncJournalFileRecordWithInode(const QStri rec._lockstate._lockEditorApp = _lockEditorApp; rec._lockstate._lockTime = _lockTime; rec._lockstate._lockTimeout = _lockTimeout; + rec._tagList = _tagList; // Update the inode if possible rec._inode = _inode; @@ -166,6 +168,7 @@ SyncFileItemPtr SyncFileItem::fromSyncJournalFileRecord(const SyncJournalFileRec item->_sharedByMe = rec._sharedByMe; item->_isShared = rec._isShared; item->_lastShareStateFetchedTimestamp = rec._lastShareStateFetchedTimestamp; + item->_tagList = rec._tagList; return item; } @@ -238,6 +241,22 @@ SyncFileItemPtr SyncFileItem::fromProperties(const QString &filePath, const QMap item->_direction = SyncFileItem::None; item->_instruction = CSYNC_INSTRUCTION_NONE; + // Tags + if (properties.contains(QStringLiteral("tags"))) { + QByteArray list =FileTagManager::fromPropertiesToTagList(properties.value("tags")); + if(list.size()>0){ // Avoid empty entries! + if(item->_tagList.size()>0)item->_tagList.append('\n'); + item->_tagList.append(list); + } + } + if (properties.contains(QStringLiteral("system-tags"))) { + QByteArray list =FileTagManager::fromPropertiesToTagList(properties.value("system-tags")); + if(list.size()>0){ // Avoid empty entries! + if(item->_tagList.size()>0)item->_tagList.append('\n'); + item->_tagList.append(list); + } + } + return item; } diff --git a/src/libsync/syncfileitem.h b/src/libsync/syncfileitem.h index 89e68ca99b209..2dd7b6a10159e 100644 --- a/src/libsync/syncfileitem.h +++ b/src/libsync/syncfileitem.h @@ -335,6 +335,8 @@ class OWNCLOUDSYNC_EXPORT SyncFileItem bool _isFileDropDetected = false; bool _isEncryptedMetadataNeedUpdate = false; + + QByteArray _tagList; }; inline bool operator<(const SyncFileItemPtr &item1, const SyncFileItemPtr &item2) From 7ba3fcbc9b402437453f4abfb6b489dee03baa1e Mon Sep 17 00:00:00 2001 From: Uwe Runtemund Date: Wed, 31 Jan 2024 00:37:50 +0100 Subject: [PATCH 02/37] Added xattr support in linux Signed-off-by: Uwe Runtemund --- src/csync/vio/csync_vio_local_unix.cpp | 25 ++++++++++- src/libsync/filetags.cpp | 58 ++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/csync/vio/csync_vio_local_unix.cpp b/src/csync/vio/csync_vio_local_unix.cpp index b6ccab8fdd4e6..2ed52cbb92df0 100644 --- a/src/csync/vio/csync_vio_local_unix.cpp +++ b/src/csync/vio/csync_vio_local_unix.cpp @@ -28,11 +28,15 @@ #include -#ifdef __APPLE__//macOS, iOS +#ifdef __APPLE__ #include #include #endif +#ifdef __linux__ +#include +#endif + #include "c_private.h" #include "c_lib.h" #include "csync.h" @@ -125,8 +129,9 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *h file_stat->type = ItemTypeSkip; } + #ifdef __APPLE__ -//TODO: GETTAGLISTFROMLOCALFILE + // Create necessary system related objects CFStringRef cfstr = CFStringCreateWithCString(kCFAllocatorDefault, fullPath.constData(), @@ -167,6 +172,22 @@ std::unique_ptr csync_vio_local_readdir(csync_vio_handle_t *h #endif +#ifdef __linux__ + const int sz=4096; + char buffer[sz]; + int result = getxattr(fullPath.constData(),"user.xdg.tags",buffer,sz); + + if(result>0) + { + // Data is store usually as comma(,) separated list. + // So we just need to replace ',' by '\n'. + QByteArray tagList = QByteArray(buffer,result); + tagList.replace(',','\n'); + printf("%s\n",buffer); + file_stat->tagList= tagList; + } +#endif + // Override type for virtual files if desired if (vfs) { // Directly modifies file_stat->type. diff --git a/src/libsync/filetags.cpp b/src/libsync/filetags.cpp index 5428ed717de93..2ce13124ba51b 100644 --- a/src/libsync/filetags.cpp +++ b/src/libsync/filetags.cpp @@ -12,10 +12,14 @@ * for more details. */ -#ifdef __APPLE__//macOS, iOS +#ifdef __APPLE__ #include #endif +#ifdef __linux__ +#include +#endif + #include #include @@ -81,9 +85,9 @@ namespace OCC{ return tags.join(QChar(0x000A)).toUtf8(); } - QByteArray FileTagManager::readTagListFromLocalFile(const QString &path) - { - +QByteArray FileTagManager::readTagListFromLocalFile(const QString &path) +{ +#ifdef __APPLE__ // Create necessary system related objects CFStringRef cfstr = path.toCFString(); CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, @@ -120,11 +124,30 @@ namespace OCC{ CFRelease(urlref); if(labels!=NULL)CFRelease(labels); - return tagarray.join(QChar(0x000A)).toUtf8(); - } + return tagarray.join(QChar(0x000A)).toUtf8(); +#endif + +#ifdef __linux__ + const int sz=4096; + char buffer[sz]; + int result = getxattr(path.toUtf8().constData(),"user.xdg.tags",buffer,sz); - bool FileTagManager::writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist) + if(result>0) { + // Data is stored usually as comma(,) separated list. + // So we just need to replace ',' by '\n'. + QByteArray tagList = QByteArray(buffer,result); + tagList.replace(',','\n'); + printf("%s\n",buffer); + return tagList; + } + else return QByteArray(); +#endif +} + +bool FileTagManager::writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist) +{ +#ifdef __APPLE__ printf("RMD SYNC LOCAL FS\n"); QStringList strlist=QString(taglist).split(QChar(0x000A)); @@ -133,7 +156,7 @@ namespace OCC{ CFStringRef* strs = new CFStringRef[newsize]; for(int index=0;index Date: Wed, 31 Jan 2024 02:36:27 +0000 Subject: [PATCH 03/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_ar.ts | 40 +++++++++++++++++----------------- translations/client_de.ts | 40 +++++++++++++++++----------------- translations/client_en_GB.ts | 40 +++++++++++++++++----------------- translations/client_es.ts | 40 +++++++++++++++++----------------- translations/client_fr.ts | 14 ++++++------ translations/client_sr.ts | 42 ++++++++++++++++++------------------ translations/client_sv.ts | 40 +++++++++++++++++----------------- translations/client_zh_HK.ts | 40 +++++++++++++++++----------------- translations/client_zh_TW.ts | 40 +++++++++++++++++----------------- 9 files changed, 168 insertions(+), 168 deletions(-) diff --git a/translations/client_ar.ts b/translations/client_ar.ts index 30af3e38b2901..cd0a7b1a35e52 100644 --- a/translations/client_ar.ts +++ b/translations/client_ar.ts @@ -1054,12 +1054,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + "%1 تعذّر فكّ قفل المجلد المُشفّر %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + قام الخادوم إرجاع رمز HTTP خاطئ. الرمز المتوقع كان: 204، لكن ما تمّ تلقيه كان: "%1 %2". @@ -1472,7 +1472,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + خطأ في إعدادات البيانات الوصفية المشفرة! @@ -1644,29 +1644,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + خطأ في جلب البيانات الوصفية. Error locking folder. - + خطأ في قفل الجلد. Error fetching encrypted folder id. - + خطأ في جلب مُعرِّف المجلد المشفر. Error parsing or decrypting metadata. - + خطأ في تحليل البيانات الوصفية أو في فك تشفيرها. Failed to upload metadata - + تعذّر رفع البيانات الوصفية @@ -4030,7 +4030,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + تعذّر تشفير المجلد %1 @@ -4320,7 +4320,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + خطأ @@ -4353,7 +4353,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + تعذّر إيجاد المجلد المَحلّي لـ %1 @@ -5035,17 +5035,17 @@ Server replied with error: %2 Failed to update folder metadata. - + تعذّر رفع البيانات الوصفية للمجلد. Failed to unlock encrypted folder. - + تعذّر فك قفل المجلد المشفر. Failed to finalize item. - + تعذّر إكمال العنصر. @@ -5061,27 +5061,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + خطأ في تحديث البيانات الوصفية للمجلد %1 Could not fetch publicKey for user %1 - + تعذّر جلب المفتاح العمومي publicKey للمستخدِم %1 Could not find root encrypted folder for folder %1 - + تعذّر إيجاد المجلد الجذر المشفر للمجلد %1 Could not add or remove a folder user %1, for folder %2 - + تعذّرت إضافة أو حذف مستخدم المجلد %1, للمجلد %2 Failed to unlock a folder. - + تعذّر فك قفل مجلد. @@ -5848,7 +5848,7 @@ Server replied with error: %2 Sharing is not available for this folder - + المشاركة غير متاحة لهذا المجلد diff --git a/translations/client_de.ts b/translations/client_de.ts index 87b7d6652cdd3..47ccc710f64bd 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -1058,12 +1058,12 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. "%1 Failed to unlock encrypted folder %2". - + "%1 Der verschlüsselte Ordner %2 konnte nicht entsperrt werden". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Falscher HTTP-Code vom Server zurückgegeben. Erwartet wird 204, jedoch "%1 %2" erhalten. @@ -1476,7 +1476,7 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. Encrypted metadata setup error! - + Verschlüsselte Metadaten Einrichtungsfehler! @@ -1649,29 +1649,29 @@ Dies kann ein Problem mit Ihren OpenSSL-Bibliotheken sein. Error fetching metadata. - + Fehler beim Abrufen der Metadaten. Error locking folder. - + Fehler beim Sperren des Ordners. Error fetching encrypted folder id. - + Fehler beim Abrufen der verschlüsselten Ordner-ID. Error parsing or decrypting metadata. - + Fehler beim Lesen oder Entschlüsseln von Metadaten. Failed to upload metadata - + Fehler beim Hochladen der Metadaten @@ -4049,7 +4049,7 @@ Dies ist ein neuer, experimenteller Modus. Wenn Sie sich entscheiden, ihn zu ver Failed to encrypt a folder %1 - + Ordner konnte nicht verschlüsselt werden %1 @@ -4339,7 +4339,7 @@ Dies ist ein neuer, experimenteller Modus. Wenn Sie sich entscheiden, ihn zu ver Error - + Fehler @@ -4372,7 +4372,7 @@ Dies ist ein neuer, experimenteller Modus. Wenn Sie sich entscheiden, ihn zu ver Could not find local folder for %1 - + Lokaler Ordner für %1 nicht gefunden @@ -5056,17 +5056,17 @@ Server antwortete mit Fehler: %2 Failed to update folder metadata. - + Fehler beim Aktualisieren der Ordner-Metadaten Failed to unlock encrypted folder. - + Verschlüsselter Ordner konnte nicht entsperrt werden. Failed to finalize item. - + Fehler beim Fertigstellen des Elements. @@ -5082,27 +5082,27 @@ Server antwortete mit Fehler: %2 Error updating metadata for a folder %1 - + Fehler beim Aktualisieren der Metadaten für einen Ordner %1 Could not fetch publicKey for user %1 - + PublicKey für den Benutzer %1 konnte nicht abgerufen werden Could not find root encrypted folder for folder %1 - + Verschlüsselter Stammordner für den Ordner %1 nicht gefunden Could not add or remove a folder user %1, for folder %2 - + Ordner konnte nicht hinzugefügt oder entfernt werden. Benutzer %1 für Ordner %2 Failed to unlock a folder. - + Fehler beim Entsperren eines Ordners. @@ -5869,7 +5869,7 @@ Server antwortete mit Fehler: %2 Sharing is not available for this folder - + Teilen ist für diesen Ordner nicht verfügbar diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index ccbe582ac32cb..3dfcf6a582884 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -1059,12 +1059,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + "%1 Failed to unlock encrypted folder %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Wrong HTTP code returned by server. Expected 204, but received "%1 %2". @@ -1477,7 +1477,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + Encrypted metadata setup error! @@ -1650,29 +1650,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + Error fetching metadata. Error locking folder. - + Error locking folder. Error fetching encrypted folder id. - + Error fetching encrypted folder id. Error parsing or decrypting metadata. - + Error parsing or decrypting metadata. Failed to upload metadata - + Failed to upload metadata @@ -4050,7 +4050,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + Failed to encrypt a folder %1 @@ -4340,7 +4340,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + Error @@ -4373,7 +4373,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + Could not find local folder for %1 @@ -5057,17 +5057,17 @@ Server replied with error: %2 Failed to update folder metadata. - + Failed to update folder metadata. Failed to unlock encrypted folder. - + Failed to unlock encrypted folder. Failed to finalize item. - + Failed to finalize item. @@ -5083,27 +5083,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + Error updating metadata for a folder %1 Could not fetch publicKey for user %1 - + Could not fetch publicKey for user %1 Could not find root encrypted folder for folder %1 - + Could not find root encrypted folder for folder %1 Could not add or remove a folder user %1, for folder %2 - + Could not add or remove a folder user %1, for folder %2 Failed to unlock a folder. - + Failed to unlock a folder. @@ -5870,7 +5870,7 @@ Server replied with error: %2 Sharing is not available for this folder - + Sharing is not available for this folder diff --git a/translations/client_es.ts b/translations/client_es.ts index ba90ec5452a64..3fbad46f357d4 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -1060,12 +1060,12 @@ Además, esta acción interrumpirá cualquier sincronización en curso. "%1 Failed to unlock encrypted folder %2". - + "%1 Fallo al desbloquear la carpeta cifrada %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + El código HTTP devuelto por el servidor es erróneo. Se esperaba 204, pero se recibió "%1 %2". @@ -1478,7 +1478,7 @@ Además, esta acción interrumpirá cualquier sincronización en curso. Encrypted metadata setup error! - + ¡Hubo un error al configurar los metadatos cifrados! @@ -1651,29 +1651,29 @@ Esto podría ser un problema con tu librería OpenSSL Error fetching metadata. - + Error al obtener los metadatos. Error locking folder. - + Error al bloquear la carpeta. Error fetching encrypted folder id. - + Error al obtener el id de la carpeta cifrada. Error parsing or decrypting metadata. - + Error al analizar o descifrar los metadatos. Failed to upload metadata - + Fallo al subir los metadatos @@ -4052,7 +4052,7 @@ Esta es un modo nuevo y experimental. Si decides usarlo, por favor, informa de c Failed to encrypt a folder %1 - + Fallo al cifrar una carpeta %1 @@ -4342,7 +4342,7 @@ Esta es un modo nuevo y experimental. Si decides usarlo, por favor, informa de c Error - + Error @@ -4375,7 +4375,7 @@ Esta es un modo nuevo y experimental. Si decides usarlo, por favor, informa de c Could not find local folder for %1 - + No se ha podido encontrar una carpeta local para %1 @@ -5059,17 +5059,17 @@ El servidor respondió con el error: %2 Failed to update folder metadata. - + Fallo al actualizar los metadatos de la carpeta. Failed to unlock encrypted folder. - + Fallo al desbloquear carpeta cifrada. Failed to finalize item. - + Fallo al finalizar ítem. @@ -5085,27 +5085,27 @@ El servidor respondió con el error: %2 Error updating metadata for a folder %1 - + Error al actualizar los metadatos de una carpeta %1 Could not fetch publicKey for user %1 - + No se pudo obtener la llave pública para el usuario %1 Could not find root encrypted folder for folder %1 - + No se ha podido encontrar la carpeta cifrada para la carpeta %1 Could not add or remove a folder user %1, for folder %2 - + No se ha podido añadir o eliminar al usuario de la carpeta %1, para la carpeta %2 Failed to unlock a folder. - + Fallo al desbloquear una carpeta. @@ -5872,7 +5872,7 @@ El servidor respondió con el error: %2 Sharing is not available for this folder - + Compartir no está disponible para esta carpeta diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 4e4d854ad5d65..36a510a4b48be 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -1057,12 +1057,12 @@ Vous prenez vos propres risques. "%1 Failed to unlock encrypted folder %2". - + « %1 Impossible de déverrouiller le dossier chiffré %2 ». Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Le code HTTP retourné par le serveur n'est pas valide. La valeur attendue est 204 mais la valeur retournée est « %1 %2 ». @@ -1648,7 +1648,7 @@ Cela peut être un problème avec vos bibliothèques OpenSSL. Error fetching metadata. - + Erreur à la récupération des méta-données. @@ -4048,7 +4048,7 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Failed to encrypt a folder %1 - + Échec du chiffrement d'un dossier %1 @@ -4338,7 +4338,7 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Error - + Erreur @@ -4371,7 +4371,7 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Could not find local folder for %1 - + Impossible de trouver le dossier local pour %1 @@ -5868,7 +5868,7 @@ Le serveur a répondu avec l'erreur : %2 Sharing is not available for this folder - + Le partage n'est pas disponible pour ce dossier diff --git a/translations/client_sr.ts b/translations/client_sr.ts index a49780160ba05..bb9828b344e8e 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -1059,12 +1059,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + „%1 Није успело откључавање шифрованог фолдера %2”. Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Сервер је вратио погрешан HTTP кôд. Очекивао се 204, а примљен је „%1 %2”. @@ -1477,7 +1477,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + Грешка подешавања шифрованих метаподатака! @@ -1650,29 +1650,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + Грешка приликом добављања метаподатака. Error locking folder. - + Грешка код закључавања фолдера. Error fetching encrypted folder id. - + Грешка приликом добављања id шифрованог фолдера. Error parsing or decrypting metadata. - + Грешка приликом парсирања или дешифровања метаподатака. Failed to upload metadata - + Није успело отпремање метаподатака @@ -3499,7 +3499,7 @@ Note that using any logging command line options will override this setting. Could not create local folder %1 - Не могу да направим локалну фасциклу %1 + Не може да се направи локални фолдер %1 @@ -4050,7 +4050,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + Није успело шифровање фолдера %1 @@ -4340,7 +4340,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + Грешка @@ -4373,7 +4373,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + Не може да се пронађе локални фолдер за %1 @@ -5057,17 +5057,17 @@ Server replied with error: %2 Failed to update folder metadata. - + Није успело отпремање метаподатака фолдера. Failed to unlock encrypted folder. - + Није успело откључавање шифрованог фолдера. Failed to finalize item. - + Није успело довршавање ставке. @@ -5083,27 +5083,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + Грешка приликом ажурирања метаподатака за фолдер %1 Could not fetch publicKey for user %1 - + Није успело добављање јавног кључа за корисника %1 Could not find root encrypted folder for folder %1 - + Није могао да се пронађе корени шифровани фолдер за фолдер %1 Could not add or remove a folder user %1, for folder %2 - + Није могао да се дода корисник фолдера %1, за фолдер %2 Failed to unlock a folder. - + Није успело откључавање фолдера. @@ -5870,7 +5870,7 @@ Server replied with error: %2 Sharing is not available for this folder - + Овај фолдер не може да се дели diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 58f940bf341e0..c4431cbef4e2b 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -1059,12 +1059,12 @@ Den här åtgärden avbryter alla synkroniseringar som körs. "%1 Failed to unlock encrypted folder %2". - + "%1 kunde inte låsa upp den krypterade mappen %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Felaktig HTTP-kod i svaret från servern. 204 förväntades, men "%1 %2" mottogs. @@ -1477,7 +1477,7 @@ Den här åtgärden avbryter alla synkroniseringar som körs. Encrypted metadata setup error! - + Inställningsfel för krypterad metadata! @@ -1650,29 +1650,29 @@ Det kan vara problem med dina OpenSSL-bibliotek. Error fetching metadata. - + Fel vid hämtning av metadata. Error locking folder. - + Fel vid låsning av mapp. Error fetching encrypted folder id. - + Fel vid hämtning av krypterat mapp-ID. Error parsing or decrypting metadata. - + Fel vid tolkning eller dekryptering av metadata. Failed to upload metadata - + Misslyckades med att ladda upp metadata @@ -4050,7 +4050,7 @@ Detta är ett nytt experimentellt läge. Om du bestämmer dig för att använda Failed to encrypt a folder %1 - + Kunde inte kryptera en mapp %1 @@ -4340,7 +4340,7 @@ Detta är ett nytt experimentellt läge. Om du bestämmer dig för att använda Error - + Fel @@ -4373,7 +4373,7 @@ Detta är ett nytt experimentellt läge. Om du bestämmer dig för att använda Could not find local folder for %1 - + Kunde inte hitta lokal mapp för %1 @@ -5057,17 +5057,17 @@ Servern svarade med fel: %2 Failed to update folder metadata. - + Kunde inte uppdatera mappens metadata. Failed to unlock encrypted folder. - + Kunde inte låsa upp krypterad mapp. Failed to finalize item. - + Misslyckades med att slutföra objektet. @@ -5083,27 +5083,27 @@ Servern svarade med fel: %2 Error updating metadata for a folder %1 - + Fel vid uppdatering av metadata för en mapp %1 Could not fetch publicKey for user %1 - + Kunde inte hämta publik nyckel för användare %1 Could not find root encrypted folder for folder %1 - + Kunde inte hitta rot-krypterad mapp för mapp %1 Could not add or remove a folder user %1, for folder %2 - + Kunde inte lägga till eller ta bort en mappanvändare %1, för mappen %2 Failed to unlock a folder. - + Misslyckades att låsa upp en mapp. @@ -5870,7 +5870,7 @@ Servern svarade med fel: %2 Sharing is not available for this folder - + Delning är inte tillgängligt för den här mappen diff --git a/translations/client_zh_HK.ts b/translations/client_zh_HK.ts index 1d1ec65152346..2d9447240b99b 100644 --- a/translations/client_zh_HK.ts +++ b/translations/client_zh_HK.ts @@ -1062,12 +1062,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + "%1 無法解鎖已加密資料夾 %2"。 Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + 從伺服器端回傳錯誤的 HTTP 代碼, 預期是 204, 但是接收到的是 "%1 %2"。 @@ -1480,7 +1480,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + 已加密的元數據設置錯誤! @@ -1652,29 +1652,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + 錯誤擷取元數據。 Error locking folder. - + 鎖定資料夾時發生錯誤。 Error fetching encrypted folder id. - + 錯誤擷取已加密資料夾ID。 Error parsing or decrypting metadata. - + 錯誤剖析或解密元數據。 Failed to upload metadata - + 無法上傳元數據 @@ -4051,7 +4051,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + 加密資料夾失敗 %1 @@ -4341,7 +4341,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + 錯誤 @@ -4374,7 +4374,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + 找不到 %1 的近端資料夾 @@ -5058,17 +5058,17 @@ Server replied with error: %2 Failed to update folder metadata. - + 無法更新資料夾元數據。 Failed to unlock encrypted folder. - + 無法解鎖已加密的資料夾。 Failed to finalize item. - + 無法完成項目。 @@ -5084,27 +5084,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + 錯誤更新資料夾 %1 的元數據 Could not fetch publicKey for user %1 - + 無法擷取用戶 %1 的公共金鑰 Could not find root encrypted folder for folder %1 - + 找不到資料夾 %1 的根已加密資料夾 Could not add or remove a folder user %1, for folder %2 - + 無法添加或移除資料夾用戶 %1,用於資料夾 %2 Failed to unlock a folder. - + 無法解鎖資料夾。 @@ -5871,7 +5871,7 @@ Server replied with error: %2 Sharing is not available for this folder - + 此資料夾無法共享 diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 84d8ea4a125c4..a9a466ca5f842 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -1059,12 +1059,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + 「%1 無法解鎖加密資料夾 %2」。 Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + 伺服器回傳錯誤的 HTTP 狀態碼。預期為 204,但收到的是「%1 %2」。 @@ -1477,7 +1477,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + 已加密的詮釋資料設定錯誤! @@ -1650,29 +1650,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + 擷取詮釋資料時發生錯誤。 Error locking folder. - + 鎖定資料夾時發生錯誤。 Error fetching encrypted folder id. - + 擷取加密資料夾 ID 時發生錯誤。 Error parsing or decrypting metadata. - + 解析或解密詮釋資料時發生錯誤。 Failed to upload metadata - + 上傳詮釋資料時發生錯誤 @@ -4050,7 +4050,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + 加密資料夾失敗 %1 @@ -4340,7 +4340,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + 錯誤 @@ -4373,7 +4373,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + 找不到 %1 的本機資料夾 @@ -5057,17 +5057,17 @@ Server replied with error: %2 Failed to update folder metadata. - + 更新資料夾詮釋資料失敗。 Failed to unlock encrypted folder. - + 解鎖加密資料夾失敗。 Failed to finalize item. - + 無法完成項目。 @@ -5083,27 +5083,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + 更新資料夾 %1 的詮釋資料時發生錯誤 Could not fetch publicKey for user %1 - + 無法擷取使用者 %1 的公開金鑰 Could not find root encrypted folder for folder %1 - + 找不到資料夾 %1 的根已加密資料夾 Could not add or remove a folder user %1, for folder %2 - + 無法新增或移除資料夾使用者 %1,用於資料夾 %2 Failed to unlock a folder. - + 解鎖資料夾失敗。 @@ -5870,7 +5870,7 @@ Server replied with error: %2 Sharing is not available for this folder - + 無法分享此資料夾 From 991c9c5b75a68af78058a426eebef10215fb5999 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Thu, 1 Feb 2024 02:39:36 +0000 Subject: [PATCH 04/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_de.ts | 6 +-- translations/client_ko.ts | 99 ++++++++++++++++++++------------------- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/translations/client_de.ts b/translations/client_de.ts index 47ccc710f64bd..b62f121af9d05 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -1476,7 +1476,7 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. Encrypted metadata setup error! - Verschlüsselte Metadaten Einrichtungsfehler! + Einrichtungsfehler für verschlüsselte Metadaten! @@ -5056,7 +5056,7 @@ Server antwortete mit Fehler: %2 Failed to update folder metadata. - Fehler beim Aktualisieren der Ordner-Metadaten + Fehler beim Aktualisieren der Ordner-Metadaten. @@ -5087,7 +5087,7 @@ Server antwortete mit Fehler: %2 Could not fetch publicKey for user %1 - PublicKey für den Benutzer %1 konnte nicht abgerufen werden + Öffentlicher Schlüssel für den Benutzer %1 konnte nicht abgerufen werden diff --git a/translations/client_ko.ts b/translations/client_ko.ts index d821c8add8d3f..b2dbd7e2efd7b 100644 --- a/translations/client_ko.ts +++ b/translations/client_ko.ts @@ -353,13 +353,15 @@ Should an account import be attempted? %1 accounts were detected from a legacy desktop client. Should the accounts be imported? - + %1개 계정이 예전의 데스크톱 클라이언트에서 발견되었습니다. +이 계정들을 가져올까요? 1 account was detected from a legacy desktop client. Should the account be imported? - + 하나의 계정이 예전의 데스크톱 클라이언트에서 발견되었습니다. +이 계정을 가져올까요? @@ -371,12 +373,12 @@ Should the account be imported? Import - + 가져오기 Skip - + 건너뛰기 @@ -981,35 +983,36 @@ This action will abort any currently running synchronization. %1 accounts number of accounts imported - + %1개 계정 1 account - + 1개 계정 %1 folders number of folders imported - + %1개 폴더 1 folder - + 1개 폴더 Legacy import - + 레거시 불러오기 Imported %1 and %2 from a legacy desktop client. %3 number of accounts and folders imported. list of users. - + %1(과)와 %2(을)를 예전의 데스크톱 클라이언트에서 가져왔습니다. +%3 @@ -1055,12 +1058,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + "%1(이)가 암호화된 폴더 %2의 잠금을 푸는 데 실패했습니다." Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + 서버가 잘못된 HTTP 코드를 반환했습니다. 204가 아닌 "%1 %2"를 수신했습니다. @@ -1473,7 +1476,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + 암호화된 메타데이터 구성 오류! @@ -1646,29 +1649,29 @@ OpenSSL 라이브러리 이슈일 수 있습니다. Error fetching metadata. - + 메타데이터를 가져오는 중 오류가 발생했습니다. Error locking folder. - + 폴더를 잠그는 중 오류가 발생했습니다. Error fetching encrypted folder id. - + 암호화된 폴더 id를 가져오는 중 오류가 발생했습니다. Error parsing or decrypting metadata. - + 메타데이터를 분석 및 해독 중 오류가 발생했습니다. Failed to upload metadata - + 메타데이터를 업로드하지 못했습니다. @@ -1974,7 +1977,7 @@ This means that the synchronization client might not upload local changes immedi Virtual file download failed with code "%1", status "%2" and error message "%3" - + 가상 파일 다운로드에 실패했으며, 코드는 "%1", 상태는 "%2", 오류 메시지는 "%3"입니다. @@ -2572,7 +2575,7 @@ If this was an accident and you decide to keep your files, they will be re-synce Show sync folders in &Explorer's navigation pane - + &탐색기의 탐색 패널에서 동기화 폴더 표시 @@ -2608,7 +2611,7 @@ If this was an accident and you decide to keep your files, they will be re-synce Desktop client x.x.x - + 데스크톱 클라이언트 x.x.x @@ -3608,13 +3611,13 @@ The virtual files mode is mutually exclusive with selective sync. Currently unse Switching to this mode will abort any currently running synchronization. This is a new, experimental mode. If you decide to use it, please report any issues that come up. - "가상 파일" 모드를 활성화 할 경우, 어떠한 파일도 우선 다운로드되지 않을 것입니다. 대신, 작은 "%1"파일이 서버에 존재하는 각 파일마다 생성될 것입니다. 이러한 작은 파일을 실행하거나 각 컨텍스트 메뉴를 이용하여 콘텐츠를 다운로드할 수 있습니다. + "가상 파일" 모드를 활성화 할 경우, 어떠한 파일도 우선 다운로드되지 않을 것입니다. 대신, 작은 "%1"파일이 서버에 존재하는 각 파일마다 생성될 것입니다. 이러한 작은 파일을 실행하거나 컨텍스트 메뉴를 이용하여 콘텐츠를 다운로드할 수 있습니다. 가상 파일 모드는 선택적 동기화와 함께 사용될 수 없습니다. 선택하지 않은 폴더는 online-only 폴더로 바뀌며 선택적 동기화 설정은 초기화됩니다. 이 모드로 변경할 경우 현재 진행중인 모든 동기화는 중단됩니다. -본 기능은 새롭고 실험적인 모드입니다. 사용을 결정했다면, 발생하는 문제들을 보고해주십시오. +본 기능은 새롭고 실험적인 모드입니다. 사용을 결정했다면, 발생하는 문제들을 보고해 주시기 바랍니다. @@ -4048,7 +4051,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + %1 폴더를 암호화하지 못했습니다. @@ -4338,7 +4341,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + 오류 @@ -4371,7 +4374,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + %1의 로컬 폴더를 찾을 수 없습니다. @@ -4401,7 +4404,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss %1 (%2) sharee (shareWithAdditionalInfo) - + %1 (%2) @@ -4892,7 +4895,7 @@ Server replied with error: %2 Preparing sync - + 동기화 준비중 @@ -5055,17 +5058,17 @@ Server replied with error: %2 Failed to update folder metadata. - + 폴더의 메타데이터를 갱신하지 못했습니다. Failed to unlock encrypted folder. - + 폴더의 암호화를 푸는 데 실패했습니다. Failed to finalize item. - + 항목을 마무리하지 못했습니다. @@ -5081,12 +5084,12 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + %1 폴더의 메타데이터를 갱신하는 중 오류가 발생했습니다. Could not fetch publicKey for user %1 - + %1님의 공개 키를 가져오지 못했습니다. @@ -5096,12 +5099,12 @@ Server replied with error: %2 Could not add or remove a folder user %1, for folder %2 - + %1님에게 %2폴더를 제거하거나 만들 수 없습니다. Failed to unlock a folder. - + 폴더의 잠금을 풀지 못했습니다. @@ -5251,37 +5254,37 @@ Server replied with error: %2 Download error - + 다운로드 오류 Error downloading - + 다운로드 중 오류 could not be downloaded - + 다운로드할 수 없음 > More details - + > 더 자세히 More details - + 더 자세히 Error downloading %1 - + %1(을)를 다운로드 중 오류 %1 could not be downloaded. - + %1(은)는 다운로드할 수 없습니다. @@ -5642,7 +5645,7 @@ Server replied with error: %2 %nd delay in days after an activity - + %n일 @@ -5653,7 +5656,7 @@ Server replied with error: %2 %nh delay in hours after an activity - + %n시간 @@ -5670,7 +5673,7 @@ Server replied with error: %2 %nm delay in minutes after an activity - + %n개월 @@ -5868,7 +5871,7 @@ Server replied with error: %2 Sharing is not available for this folder - + 이 폴더에서 공유를 사용할 수 없습니다. @@ -6262,7 +6265,7 @@ Server replied with error: %2 Open local or group folders - + 로컬 및 그룹 폴더 열기 @@ -6287,7 +6290,7 @@ Server replied with error: %2 New activities - + 새 활동 From b213514dd49ef7198e453e5c3453445e82b976d6 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Fri, 2 Feb 2024 16:52:30 +0100 Subject: [PATCH 05/37] wait longer to get the contextual menu entries: may be necessary Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- shell_integration/windows/NCContextMenu/NCClientInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell_integration/windows/NCContextMenu/NCClientInterface.cpp b/shell_integration/windows/NCContextMenu/NCClientInterface.cpp index 6db4a27c9a4a6..380f4e22291b2 100644 --- a/shell_integration/windows/NCContextMenu/NCClientInterface.cpp +++ b/shell_integration/windows/NCContextMenu/NCClientInterface.cpp @@ -48,7 +48,7 @@ NCClientInterface::ContextMenuInfo NCClientInterface::FetchInfo(const std::wstri ContextMenuInfo info; std::wstring response; int sleptCount = 0; - while (sleptCount < 5) { + while (sleptCount < 20) { if (socket.ReadLine(&response)) { if (StringUtil::begins_with(response, wstring(L"REGISTER_PATH:"))) { wstring responsePath = response.substr(14); // length of REGISTER_PATH From df11c0f9246cc2a883e725201866260d24eb9d21 Mon Sep 17 00:00:00 2001 From: rakekniven <2069590+rakekniven@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:33:30 +0100 Subject: [PATCH 06/37] fix(i18n): uppercase ID Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com> Signed-off-by: Uwe Runtemund --- src/libsync/encryptedfoldermetadatahandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsync/encryptedfoldermetadatahandler.cpp b/src/libsync/encryptedfoldermetadatahandler.cpp index 6771b6170eb50..412fe6e0e6623 100644 --- a/src/libsync/encryptedfoldermetadatahandler.cpp +++ b/src/libsync/encryptedfoldermetadatahandler.cpp @@ -144,7 +144,7 @@ void EncryptedFolderMetadataHandler::slotFolderEncryptedIdError(QNetworkReply *r Q_ASSERT(reply); qCDebug(lcFetchAndUploadE2eeFolderMetadataJob) << "Error retrieving the Id of the encrypted folder."; if (!reply) { - emit fetchFinished(-1, tr("Error fetching encrypted folder id.")); + emit fetchFinished(-1, tr("Error fetching encrypted folder ID.")); return; } const auto errorCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); From 886d97b35a0b68ccc8b6fedcab8c45a29b37338c Mon Sep 17 00:00:00 2001 From: rakekniven <2069590+rakekniven@users.noreply.github.com> Date: Sun, 4 Feb 2024 16:35:23 +0100 Subject: [PATCH 07/37] fix(i18n): Changed spelling of "public key" Reported at Transifex Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com> Signed-off-by: Uwe Runtemund --- src/libsync/updatee2eefolderusersmetadatajob.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsync/updatee2eefolderusersmetadatajob.cpp b/src/libsync/updatee2eefolderusersmetadatajob.cpp index e6d951dd968bc..ecbbd5b24f2d0 100644 --- a/src/libsync/updatee2eefolderusersmetadatajob.cpp +++ b/src/libsync/updatee2eefolderusersmetadatajob.cpp @@ -89,7 +89,7 @@ void UpdateE2eeFolderUsersMetadataJob::start(const bool keepLock) void UpdateE2eeFolderUsersMetadataJob::slotStartE2eeMetadataJobs() { if (_operation == Operation::Add && _folderUserCertificate.isNull()) { - emit finished(404, tr("Could not fetch publicKey for user %1").arg(_folderUserId)); + emit finished(404, tr("Could not fetch public key for user %1").arg(_folderUserId)); return; } From dcac5795cf1b6b990287b228721ea124edf1267e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= Date: Mon, 5 Feb 2024 17:08:42 +0100 Subject: [PATCH 08/37] chore: update workflows from templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ Signed-off-by: Uwe Runtemund --- .github/workflows/fixup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fixup.yml b/.github/workflows/fixup.yml index 9548d19f2d5cd..98d49927ab858 100644 --- a/.github/workflows/fixup.yml +++ b/.github/workflows/fixup.yml @@ -24,7 +24,7 @@ jobs: pull-requests: write name: Block fixup and squash commits - runs-on: ubuntu-latest + runs-on: ubuntu-latest-low steps: - name: Run check From bde5af968b9b8751de8d8278119c46dbbda5cec0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:13:01 +0000 Subject: [PATCH 09/37] Build(deps): Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: Uwe Runtemund --- .github/workflows/windows-build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows-build-and-test.yml b/.github/workflows/windows-build-and-test.yml index 663bc9d3f0133..1753a3718c495 100644 --- a/.github/workflows/windows-build-and-test.yml +++ b/.github/workflows/windows-build-and-test.yml @@ -78,7 +78,7 @@ jobs: runTestsAndCreateCoverage - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} directory: ${{ github.workspace }}\cobertura_coverage From 2720921df532028f8ae2ee85aebd9c97f16e11d0 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Wed, 31 Jan 2024 16:38:38 +0100 Subject: [PATCH 10/37] suppress deprecated warnings from openssl is needed to avoid failing builds due to warnings unclear when we will tackle the work of removing the use of deprectaed APIs Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- src/libsync/clientsideencryption.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libsync/clientsideencryption.cpp b/src/libsync/clientsideencryption.cpp index ba34300420a16..c9a655278d0b2 100644 --- a/src/libsync/clientsideencryption.cpp +++ b/src/libsync/clientsideencryption.cpp @@ -1,3 +1,5 @@ +#define OPENSSL_SUPPRESS_DEPRECATED + #include "clientsideencryption.h" #include From 25a7bf1a90734d21781ffe2486c59fc740eef491 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 22 Jan 2024 14:40:47 +0800 Subject: [PATCH 11/37] Run swift-format on swift code Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- ...loudFilesDatabaseManager+Directories.swift | 108 ++-- ...cloudFilesDatabaseManager+LocalFiles.swift | 40 +- .../NextcloudFilesDatabaseManager.swift | 262 ++++++--- .../NextcloudItemMetadataTable+NKFile.swift | 32 +- .../Database/NextcloudItemMetadataTable.swift | 70 +-- .../Extensions/Logger+Extensions.swift | 9 +- .../Extensions/NKError+Extensions.swift | 36 +- .../Extensions/Progress+Extensions.swift | 46 +- .../FileProviderEnumerator+SyncEngine.swift | 293 ++++++---- .../FileProviderEnumerator.swift | 274 +++++++--- ...ileProviderExtension+ClientInterface.swift | 43 +- .../FileProviderExtension+Thumbnailing.swift | 41 +- .../FileProviderExtension.swift | 500 ++++++++++++------ .../FileProviderExt/FileProviderItem.swift | 90 ++-- ...viderMaterialisedEnumerationObserver.swift | 42 +- .../FileProviderSocketLineProcessor.swift | 18 +- .../FileProviderExt/LocalFilesUtils.swift | 25 +- .../FileProviderExt/NextcloudAccount.swift | 9 +- 18 files changed, 1270 insertions(+), 668 deletions(-) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift index 386791ecc65ec..0e29754df9fa3 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift @@ -20,36 +20,51 @@ extension NextcloudFilesDatabaseManager { // We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com" let problematicSeparator = "://" let placeholderSeparator = "__TEMP_REPLACE__" - let serverUrlWithoutPrefix = serverUrl.replacingOccurrences(of: problematicSeparator, with: placeholderSeparator) + let serverUrlWithoutPrefix = serverUrl.replacingOccurrences( + of: problematicSeparator, with: placeholderSeparator) var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/") let directoryItemFileName = String(splitServerUrl.removeLast()) - let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences(of: placeholderSeparator, with: problematicSeparator) + let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences( + of: placeholderSeparator, with: problematicSeparator) - if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, directoryItemServerUrl, directoryItemFileName).first { + if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, + directoryItemServerUrl, directoryItemFileName + ).first { return NextcloudItemMetadataTable(value: metadata) } return nil } - func childItemsForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] { + func childItemsForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) + -> [NextcloudItemMetadataTable] + { let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@", directoryServerUrl) + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "serverUrl BEGINSWITH %@", directoryServerUrl) return sortedItemMetadatas(metadatas) } - func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] { + func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) + -> [NextcloudItemMetadataTable] + { let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND directory == true", directoryServerUrl) + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "serverUrl BEGINSWITH %@ AND directory == true", directoryServerUrl) return sortedItemMetadatas(metadatas) } - func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudItemMetadataTable? { - return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl) + func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) + -> NextcloudItemMetadataTable? + { + directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl) } func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? { - if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first { + if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@ AND directory == true", ocId + ).first { return NextcloudItemMetadataTable(value: metadata) } @@ -57,20 +72,31 @@ extension NextcloudFilesDatabaseManager { } func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account) + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND directory == true", account) return sortedItemMetadatas(metadatas) } - func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, parentDirectoryServerUrl) + func directoryMetadatas(account: String, parentDirectoryServerUrl: String) + -> [NextcloudItemMetadataTable] + { + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, + parentDirectoryServerUrl) return sortedItemMetadatas(metadatas) } // Deletes all metadatas related to the info of the directory provided func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) -> [NextcloudItemMetadataTable]? { let database = ncDatabase() - guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else { - Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion") + guard + let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@ AND directory == true", ocId + ).first + else { + Logger.ncFilesDatabase.error( + "Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion" + ) return nil } @@ -79,20 +105,25 @@ extension NextcloudFilesDatabaseManager { let directoryAccount = directoryMetadata.account let directoryEtag = directoryMetadata.etag - Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" + ) guard deleteItemMetadata(ocId: directoryMetadata.ocId) else { - Logger.ncFilesDatabase.debug("Failure to delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Failure to delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" + ) return nil } var deletedMetadatas: [NextcloudItemMetadataTable] = [directoryMetadataCopy] - let results = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryAccount, directoryUrlPath) + let results = database.objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl BEGINSWITH %@", directoryAccount, directoryUrlPath) for result in results { let successfulItemMetadataDelete = deleteItemMetadata(ocId: result.ocId) - if (successfulItemMetadataDelete) { + if successfulItemMetadataDelete { deletedMetadatas.append(NextcloudItemMetadataTable(value: result)) } @@ -101,24 +132,35 @@ extension NextcloudFilesDatabaseManager { } } - Logger.ncFilesDatabase.debug("Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" + ) return deletedMetadatas } - func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) -> [NextcloudItemMetadataTable]? { - + func renameDirectoryAndPropagateToChildren( + ocId: String, newServerUrl: String, newFileName: String + ) -> [NextcloudItemMetadataTable]? { let database = ncDatabase() - guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else { - Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming") + guard + let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@ AND directory == true", ocId + ).first + else { + Logger.ncFilesDatabase.error( + "Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming" + ) return nil } let oldItemServerUrl = directoryMetadata.serverUrl let oldDirectoryServerUrl = oldItemServerUrl + "/" + directoryMetadata.fileName let newDirectoryServerUrl = newServerUrl + "/" + newFileName - let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldDirectoryServerUrl) + let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, + oldDirectoryServerUrl) renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName) Logger.ncFilesDatabase.debug("Renamed root renaming directory") @@ -127,19 +169,25 @@ extension NextcloudFilesDatabaseManager { try database.write { for childItem in childItemResults { let oldServerUrl = childItem.serverUrl - let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldDirectoryServerUrl, with: newDirectoryServerUrl) + let movedServerUrl = oldServerUrl.replacingOccurrences( + of: oldDirectoryServerUrl, with: newDirectoryServerUrl) childItem.serverUrl = movedServerUrl database.add(childItem, update: .all) - Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)") + Logger.ncFilesDatabase.debug( + "Moved childItem at \(oldServerUrl) to \(movedServerUrl)") } } - } catch let error { - Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)" + ) return nil } - let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, newDirectoryServerUrl) + let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, + newDirectoryServerUrl) return sortedItemMetadatas(updatedChildItemResults) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift index 2241d200c698b..3174bc491b9e8 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift @@ -13,12 +13,14 @@ */ import Foundation -import RealmSwift import OSLog +import RealmSwift extension NextcloudFilesDatabaseManager { func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? { - if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first { + if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter( + "ocId == %@", ocId + ).first { return NextcloudLocalFileMetadataTable(value: metadata) } @@ -41,10 +43,14 @@ extension NextcloudFilesDatabaseManager { newLocalFileMetadata.exifLongitude = "-1" database.add(newLocalFileMetadata, update: .all) - Logger.ncFilesDatabase.debug("Added local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Added local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public)" + ) } - } catch let error { - Logger.ncFilesDatabase.error("Could not add local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not add local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) } } @@ -53,34 +59,42 @@ extension NextcloudFilesDatabaseManager { do { try database.write { - let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId) + let results = database.objects(NextcloudLocalFileMetadataTable.self).filter( + "ocId == %@", ocId) database.delete(results) } - } catch let error { - Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) } } - private func sortedLocalFileMetadatas(_ metadatas: Results) -> [NextcloudLocalFileMetadataTable] { + private func sortedLocalFileMetadatas(_ metadatas: Results) + -> [NextcloudLocalFileMetadataTable] + { let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true) return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) }) } func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] { - let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account) + let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter( + "account == %@", account) return sortedLocalFileMetadatas(results) } func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] { let localFileMetadatas = localFileMetadatas(account: account) - let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId }) + let localFileMetadatasOcIds = Array(localFileMetadatas.map(\.ocId)) var itemMetadatas: [NextcloudItemMetadataTable] = [] for ocId in localFileMetadatasOcIds { guard let itemMetadata = itemMetadataFromOcId(ocId) else { - Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)") - continue; + Logger.ncFilesDatabase.error( + "Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)" + ) + continue } itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata)) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift index 158f28111a4bd..f0344f40b39a5 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift @@ -12,16 +12,14 @@ * for more details. */ -import Foundation -import RealmSwift import FileProvider +import Foundation import NextcloudKit import OSLog +import RealmSwift -class NextcloudFilesDatabaseManager : NSObject { - static let shared = { - return NextcloudFilesDatabaseManager(); - }() +class NextcloudFilesDatabaseManager: NSObject { + static let shared = NextcloudFilesDatabaseManager() let relativeDatabaseFolderPath = "Database/" let databaseFilename = "fileproviderextdatabase.realm" @@ -31,29 +29,36 @@ class NextcloudFilesDatabaseManager : NSObject { let schemaVersion: UInt64 = 100 override init() { - self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename + relativeDatabaseFilePath = relativeDatabaseFolderPath + databaseFilename guard let fileProviderDataDirUrl = pathForFileProviderExtData() else { super.init() return } - self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath) + databasePath = fileProviderDataDirUrl.appendingPathComponent(relativeDatabaseFilePath) // Disable file protection for directory DB // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm - let dbFolder = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFolderPath) + let dbFolder = fileProviderDataDirUrl.appendingPathComponent(relativeDatabaseFolderPath) let dbFolderPath = dbFolder.path do { try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true) - try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: dbFolderPath) - } catch let error { - Logger.ncFilesDatabase.error("Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)") + try FileManager.default.setAttributes( + [ + FileAttributeKey.protectionKey: FileProtectionType + .completeUntilFirstUserAuthentication + ], + ofItemAtPath: dbFolderPath) + } catch { + Logger.ncFilesDatabase.error( + "Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)" + ) } let config = Realm.Configuration( - fileURL: self.databasePath, - schemaVersion: self.schemaVersion, + fileURL: databasePath, + schemaVersion: schemaVersion, objectTypes: [NextcloudItemMetadataTable.self, NextcloudLocalFileMetadataTable.self] ) @@ -63,7 +68,8 @@ class NextcloudFilesDatabaseManager : NSObject { _ = try Realm() Logger.ncFilesDatabase.info("Successfully started Realm db for FileProviderExt") } catch let error as NSError { - Logger.ncFilesDatabase.error("Error opening Realm db: \(error.localizedDescription, privacy: .public)") + Logger.ncFilesDatabase.error( + "Error opening Realm db: \(error.localizedDescription, privacy: .public)") } super.init() @@ -76,81 +82,106 @@ class NextcloudFilesDatabaseManager : NSObject { } func anyItemMetadatasForAccount(_ account: String) -> Bool { - return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty + !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account) + .isEmpty } func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? { // Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db // Let's therefore create a copy - if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first { + if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@", ocId + ).first { return NextcloudItemMetadataTable(value: itemMetadata) } return nil } - func sortedItemMetadatas(_ metadatas: Results) -> [NextcloudItemMetadataTable] { + func sortedItemMetadatas(_ metadatas: Results) + -> [NextcloudItemMetadataTable] + { let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true) return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) }) } func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account) + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@", account) return sortedItemMetadatas(metadatas) } func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl) + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl == %@", account, serverUrl) return sortedItemMetadatas(metadatas) } - func itemMetadatas(account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, status.rawValue) + func itemMetadatas( + account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status + ) + -> [NextcloudItemMetadataTable] + { + let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, + status.rawValue) return sortedItemMetadatas(metadatas) } - func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> NextcloudItemMetadataTable? { + func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) + -> NextcloudItemMetadataTable? + { let ocId = identifier.rawValue return itemMetadataFromOcId(ocId) } - private func processItemMetadatasToDelete(existingMetadatas: Results, - updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] { - + private func processItemMetadatasToDelete( + existingMetadatas: Results, + updatedMetadatas: [NextcloudItemMetadataTable] + ) -> [NextcloudItemMetadataTable] { var deletedMetadatas: [NextcloudItemMetadataTable] = [] for existingMetadata in existingMetadatas { guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }), - let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue } + let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) + else { continue } deletedMetadatas.append(metadataToDelete) - Logger.ncFilesDatabase.debug("Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: .public)" + ) } return deletedMetadatas } - private func processItemMetadatasToUpdate(existingMetadatas: Results, - updatedMetadatas: [NextcloudItemMetadataTable], - updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], directoriesNeedingRename: [NextcloudItemMetadataTable]) { - + private func processItemMetadatasToUpdate( + existingMetadatas: Results, + updatedMetadatas: [NextcloudItemMetadataTable], + updateDirectoryEtags: Bool + ) -> ( + newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], + directoriesNeedingRename: [NextcloudItemMetadataTable] + ) { var returningNewMetadatas: [NextcloudItemMetadataTable] = [] var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = [] var directoriesNeedingRename: [NextcloudItemMetadataTable] = [] for updatedMetadata in updatedMetadatas { - if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) { - - if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue && - !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) { - + if let existingMetadata = existingMetadatas.first(where: { + $0.ocId == updatedMetadata.ocId + }) { + if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue, + !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) + { if updatedMetadata.directory { - - if updatedMetadata.serverUrl != existingMetadata.serverUrl || updatedMetadata.fileName != existingMetadata.fileName { - - directoriesNeedingRename.append(NextcloudItemMetadataTable(value: updatedMetadata)) - updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually + if updatedMetadata.serverUrl != existingMetadata.serverUrl + || updatedMetadata.fileName != existingMetadata.fileName + { + directoriesNeedingRename.append( + NextcloudItemMetadataTable(value: updatedMetadata)) + updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually } else if !updateDirectoryEtags { updatedMetadata.etag = existingMetadata.etag @@ -159,49 +190,68 @@ class NextcloudFilesDatabaseManager : NSObject { returningUpdatedMetadatas.append(updatedMetadata) - - Logger.ncFilesDatabase.debug("Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" + ) } else { - Logger.ncFilesDatabase.debug("Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" + ) } - } else { // This is a new metadata - if !updateDirectoryEtags && updatedMetadata.directory { + } else { // This is a new metadata + if !updateDirectoryEtags, updatedMetadata.directory { updatedMetadata.etag = "" } - + returningNewMetadatas.append(updatedMetadata) - Logger.ncFilesDatabase.debug("Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" + ) } } return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename) } - func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) { + func updateItemMetadatas( + account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], + updateDirectoryEtags: Bool + ) -> ( + newMetadatas: [NextcloudItemMetadataTable]?, + updatedMetadatas: [NextcloudItemMetadataTable]?, + deletedMetadatas: [NextcloudItemMetadataTable]? + ) { let database = ncDatabase() do { - let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue) + let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter( + "account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, + NextcloudItemMetadataTable.Status.normal.rawValue) - let metadatasToDelete = processItemMetadatasToDelete(existingMetadatas: existingMetadatas, - updatedMetadatas: updatedMetadatas) + let metadatasToDelete = processItemMetadatasToDelete( + existingMetadatas: existingMetadatas, + updatedMetadatas: updatedMetadatas) - let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas, - updatedMetadatas: updatedMetadatas, - updateDirectoryEtags: updateDirectoryEtags) + let metadatasToChange = processItemMetadatasToUpdate( + existingMetadatas: existingMetadatas, + updatedMetadatas: updatedMetadatas, + updateDirectoryEtags: updateDirectoryEtags) var metadatasToUpdate = metadatasToChange.updatedMetadatas let metadatasToCreate = metadatasToChange.newMetadatas let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename - let metadatasToAdd = Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) + - Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) }) + let metadatasToAdd = + Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) + + Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) }) for metadata in directoriesNeedingRename { - - if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(ocId: metadata.ocId, newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) { + if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren( + ocId: metadata.ocId, newServerUrl: metadata.serverUrl, + newFileName: metadata.fileName) + { metadatasToUpdate += updatedDirectoryChildren } } @@ -209,40 +259,59 @@ class NextcloudFilesDatabaseManager : NSObject { try database.write { for metadata in metadatasToDelete { // Can't pass copies, we need the originals from the database - database.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId)) + database.delete( + ncDatabase().objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@", metadata.ocId)) } for metadata in metadatasToAdd { database.add(metadata, update: .all) } - } - return (newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, deletedMetadatas: metadatasToDelete) - } catch let error { - Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)") + return ( + newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, + deletedMetadatas: metadatasToDelete + ) + } catch { + Logger.ncFilesDatabase.error( + "Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)" + ) return (nil, nil, nil) } } - func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) { + func setStatusForItemMetadata( + _ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, + completionHandler: @escaping (_ updatedMetadata: NextcloudItemMetadataTable?) -> Void + ) { let database = ncDatabase() do { try database.write { - guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else { - Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)") + guard + let result = database.objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@", metadata.ocId + ).first + else { + Logger.ncFilesDatabase.debug( + "Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)" + ) return } result.status = status.rawValue database.add(result, update: .all) - Logger.ncFilesDatabase.debug("Updated status for item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Updated status for item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" + ) completionHandler(NextcloudItemMetadataTable(value: result)) } - } catch let error { - Logger.ncFilesDatabase.error("Could not update status for item metadata with ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not update status for item metadata with ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) completionHandler(nil) } } @@ -253,10 +322,14 @@ class NextcloudFilesDatabaseManager : NSObject { do { try database.write { database.add(metadata, update: .all) - Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" + ) } - } catch let error { - Logger.ncFilesDatabase.error("Could not add item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not add item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) } } @@ -265,15 +338,18 @@ class NextcloudFilesDatabaseManager : NSObject { do { try database.write { - let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId) + let results = database.objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@", ocId) Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)") database.delete(results) } return true - } catch let error { - Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) return false } } @@ -283,8 +359,14 @@ class NextcloudFilesDatabaseManager : NSObject { do { try database.write { - guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else { - Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: .public)") + guard + let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter( + "ocId == %@", ocId + ).first + else { + Logger.ncFilesDatabase.debug( + "Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: .public)" + ) return } @@ -297,14 +379,20 @@ class NextcloudFilesDatabaseManager : NSObject { database.add(itemMetadata, update: .all) - Logger.ncFilesDatabase.debug("Renamed item \(oldFileName, privacy: .public) to \(newFileName, privacy: .public), moved from serverUrl: \(oldServerUrl, privacy: .public) to serverUrl: \(newServerUrl, privacy: .public)") + Logger.ncFilesDatabase.debug( + "Renamed item \(oldFileName, privacy: .public) to \(newFileName, privacy: .public), moved from serverUrl: \(oldServerUrl, privacy: .public) to serverUrl: \(newServerUrl, privacy: .public)" + ) } - } catch let error { - Logger.ncFilesDatabase.error("Could not rename filename of item metadata with ocID: \(ocId, privacy: .public) to proposed name \(newFileName, privacy: .public) at proposed serverUrl \(newServerUrl, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.ncFilesDatabase.error( + "Could not rename filename of item metadata with ocID: \(ocId, privacy: .public) to proposed name \(newFileName, privacy: .public) at proposed serverUrl \(newServerUrl, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) } } - func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? { + func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) + -> NSFileProviderItemIdentifier? + { let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId if metadata.serverUrl == homeServerFilesUrl { @@ -312,7 +400,9 @@ class NextcloudFilesDatabaseManager : NSObject { } guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else { - Logger.ncFilesDatabase.error("Could not get item parent directory metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.error( + "Could not get item parent directory metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" + ) return nil } @@ -320,7 +410,9 @@ class NextcloudFilesDatabaseManager : NSObject { return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId) } - Logger.ncFilesDatabase.error("Could not get item parent directory item metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)") + Logger.ncFilesDatabase.error( + "Could not get item parent directory item metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" + ) return nil } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift index 29683e7220c68..e64818ebc51df 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift @@ -69,8 +69,10 @@ extension NextcloudItemMetadataTable { } metadata.size = file.size metadata.classFile = file.classFile - //FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown - if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue { + // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown + if metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown", + metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue + { metadata.classFile = NKCommon.TypeClassFile.document.rawValue } if let date = file.uploadDate { @@ -87,26 +89,30 @@ extension NextcloudItemMetadataTable { return metadata } - static func metadatasFromDirectoryReadNKFiles(_ files: [NKFile], - account: String, - completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable, - _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], - _ metadatas: [NextcloudItemMetadataTable]) -> Void) { - + static func metadatasFromDirectoryReadNKFiles( + _ files: [NKFile], + account: String, + completionHandler: @escaping ( + _ directoryMetadata: NextcloudItemMetadataTable, + _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], + _ metadatas: [NextcloudItemMetadataTable] + ) -> Void + ) { var directoryMetadataSet = false var directoryMetadata = NextcloudItemMetadataTable() var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = [] var metadatas: [NextcloudItemMetadataTable] = [] - let conversionQueue = DispatchQueue(label: "nkFileToMetadataConversionQueue", qos: .userInitiated, attributes: .concurrent) - let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) // Serial queue + let conversionQueue = DispatchQueue( + label: "nkFileToMetadataConversionQueue", qos: .userInitiated, attributes: .concurrent) + let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) // Serial queue let dispatchGroup = DispatchGroup() for file in files { - if metadatas.isEmpty && !directoryMetadataSet { + if metadatas.isEmpty, !directoryMetadataSet { let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account) - directoryMetadata = metadata; - directoryMetadataSet = true; + directoryMetadata = metadata + directoryMetadataSet = true } else { conversionQueue.async(group: dispatchGroup) { let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift index b2555c695585f..84ab23e1d7f91 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift @@ -12,10 +12,10 @@ * for more details. */ -import Foundation -import RealmSwift import FileProvider +import Foundation import NextcloudKit +import RealmSwift class NextcloudItemMetadataTable: Object { enum Status: Int { @@ -71,7 +71,7 @@ class NextcloudItemMetadataTable: Object { @Persisted var isExtractFile: Bool = false @Persisted var livePhoto: Bool = false @Persisted var mountType = "" - @Persisted var name = "" // for unifiedSearch is the provider.id + @Persisted var name = "" // for unifiedSearch is the provider.id @Persisted var note = "" @Persisted var ownerId = "" @Persisted var ownerDisplayName = "" @@ -88,13 +88,13 @@ class NextcloudItemMetadataTable: Object { @Persisted var quotaAvailableBytes: Int64 = 0 @Persisted var resourceType = "" @Persisted var richWorkspace: String? - @Persisted var serverUrl = "" // For parent directory!! + @Persisted var serverUrl = "" // For parent directory!! @Persisted var session = "" @Persisted var sessionError = "" @Persisted var sessionSelector = "" @Persisted var sessionTaskIdentifier: Int = 0 @Persisted var sharePermissionsCollaborationServices: Int = 0 - let sharePermissionsCloudMesh = List() // TODO: Find a way to compare these in remote state check + let sharePermissionsCloudMesh = List() // TODO: Find a way to compare these in remote state check let shareType = List() @Persisted var size: Int64 = 0 @Persisted var status: Int = 0 @@ -117,22 +117,25 @@ class NextcloudItemMetadataTable: Object { } var isRenameable: Bool { - return lock + lock } var isPrintable: Bool { if isDocumentViewableOnly { return false } - if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue { + if ["application/pdf", "com.adobe.pdf"].contains(contentType) + || contentType.hasPrefix("text/") + || classFile == NKCommon.TypeClassFile.image.rawValue + { return true } return false } var isDocumentViewableOnly: Bool { - return sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue && - classFile == NKCommon.TypeClassFile.document.rawValue + sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue + && classFile == NKCommon.TypeClassFile.document.rawValue } var isCopyableInPasteboard: Bool { @@ -143,22 +146,21 @@ class NextcloudItemMetadataTable: Object { if directory || isDocumentViewableOnly { return false } - return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue + return contentType == "com.adobe.pdf" || contentType == "application/pdf" + || classFile == NKCommon.TypeClassFile.image.rawValue } var isSettableOnOffline: Bool { - return session.isEmpty && !isDocumentViewableOnly + session.isEmpty && !isDocumentViewableOnly } var canOpenIn: Bool { - return session.isEmpty && !isDocumentViewableOnly && !directory + session.isEmpty && !isDocumentViewableOnly && !directory } var isDownloadUpload: Bool { - return status == Status.inDownload.rawValue || - status == Status.downloading.rawValue || - status == Status.inUpload.rawValue || - status == Status.uploading.rawValue + status == Status.inDownload.rawValue || status == Status.downloading.rawValue + || status == Status.inUpload.rawValue || status == Status.uploading.rawValue } var isDownload: Bool { @@ -171,30 +173,28 @@ class NextcloudItemMetadataTable: Object { override func isEqual(_ object: Any?) -> Bool { if let object = object as? NextcloudItemMetadataTable { - return self.fileId == object.fileId && - self.account == object.account && - self.path == object.path && - self.fileName == object.fileName + return fileId == object.fileId && account == object.account && path == object.path + && fileName == object.fileName } return false } - func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool { - return comparingMetadata.etag == self.etag && - comparingMetadata.fileNameView == self.fileNameView && - comparingMetadata.date == self.date && - comparingMetadata.permissions == self.permissions && - comparingMetadata.hasPreview == self.hasPreview && - comparingMetadata.note == self.note && - comparingMetadata.lock == self.lock && - comparingMetadata.sharePermissionsCollaborationServices == self.sharePermissionsCollaborationServices && - comparingMetadata.favorite == self.favorite + func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) + -> Bool + { + comparingMetadata.etag == etag && comparingMetadata.fileNameView == fileNameView + && comparingMetadata.date == date && comparingMetadata.permissions == permissions + && comparingMetadata.hasPreview == hasPreview && comparingMetadata.note == note + && comparingMetadata.lock == lock + && comparingMetadata.sharePermissionsCollaborationServices + == sharePermissionsCollaborationServices + && comparingMetadata.favorite == favorite } /// Returns false if the user is lokced out of the file. I.e. The file is locked but by someone else func canUnlock(as user: String) -> Bool { - return !lock || (lockOwner == user && lockOwnerType == 0) + !lock || (lockOwner == user && lockOwnerType == 0) } func thumbnailUrl(size: CGSize) -> URL? { @@ -203,10 +203,12 @@ class NextcloudItemMetadataTable: Object { } let urlBase = urlBase.urlEncoded! - let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user // Leave the leading slash - let serverFileRelativeUrl = serverUrl.replacingOccurrences(of: webdavUrl, with: "") + "/" + fileName + let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user // Leave the leading slash + let serverFileRelativeUrl = + serverUrl.replacingOccurrences(of: webdavUrl, with: "") + "/" + fileName - let urlString = "\(urlBase)/index.php/core/preview.png?file=\(serverFileRelativeUrl)&x=\(size.width)&y=\(size.height)&a=1&mode=cover" + let urlString = + "\(urlBase)/index.php/core/preview.png?file=\(serverFileRelativeUrl)&x=\(size.width)&y=\(size.height)&a=1&mode=cover" return URL(string: urlString) } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift index 97119492821fb..f58ff0e908878 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift @@ -17,11 +17,14 @@ import OSLog extension Logger { private static var subsystem = Bundle.main.bundleIdentifier! - static let desktopClientConnection = Logger(subsystem: subsystem, category: "desktopclientconnection") + static let desktopClientConnection = Logger( + subsystem: subsystem, category: "desktopclientconnection") static let enumeration = Logger(subsystem: subsystem, category: "enumeration") - static let fileProviderExtension = Logger(subsystem: subsystem, category: "fileproviderextension") + static let fileProviderExtension = Logger( + subsystem: subsystem, category: "fileproviderextension") static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer") static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations") static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase") - static let materialisedFileHandling = Logger(subsystem: subsystem, category: "materialisedfilehandling") + static let materialisedFileHandling = Logger( + subsystem: subsystem, category: "materialisedfilehandling") } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift index f9ca6681faf41..b3c1be80384b8 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift @@ -12,57 +12,49 @@ * for more details. */ -import Foundation import FileProvider +import Foundation import NextcloudKit extension NKError { static var noChangesErrorCode: Int { - return -200 + -200 } var isCouldntConnectError: Bool { - return errorCode == -9999 || - errorCode == -1001 || - errorCode == -1004 || - errorCode == -1005 || - errorCode == -1009 || - errorCode == -1012 || - errorCode == -1200 || - errorCode == -1202 || - errorCode == 500 || - errorCode == 503 || - errorCode == 200 + errorCode == -9999 || errorCode == -1001 || errorCode == -1004 || errorCode == -1005 + || errorCode == -1009 || errorCode == -1012 || errorCode == -1200 || errorCode == -1202 + || errorCode == 500 || errorCode == 503 || errorCode == 200 } var isUnauthenticatedError: Bool { - return errorCode == -1013 + errorCode == -1013 } var isGoingOverQuotaError: Bool { - return errorCode == 507 + errorCode == 507 } var isNotFoundError: Bool { - return errorCode == 404 + errorCode == 404 } var isNoChangesError: Bool { - return errorCode == NKError.noChangesErrorCode + errorCode == NKError.noChangesErrorCode } var fileProviderError: NSFileProviderError { if isNotFoundError { - return NSFileProviderError(.noSuchItem) + NSFileProviderError(.noSuchItem) } else if isCouldntConnectError { // Provide something the file provider can do something with - return NSFileProviderError(.serverUnreachable) + NSFileProviderError(.serverUnreachable) } else if isUnauthenticatedError { - return NSFileProviderError(.notAuthenticated) + NSFileProviderError(.notAuthenticated) } else if isGoingOverQuotaError { - return NSFileProviderError(.insufficientQuota) + NSFileProviderError(.insufficientQuota) } else { - return NSFileProviderError(.cannotSynchronize) + NSFileProviderError(.cannotSynchronize) } } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift index e15e2e0adf0ff..630a370dd63c9 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift @@ -12,39 +12,39 @@ * for more details. */ -import Foundation import Alamofire +import Foundation extension Progress { func setHandlersFromAfRequest(_ request: Request) { - self.cancellationHandler = { request.cancel() } - self.pausingHandler = { request.suspend() } - self.resumingHandler = { request.resume() } + cancellationHandler = { request.cancel() } + pausingHandler = { request.suspend() } + resumingHandler = { request.resume() } } func copyCurrentStateToProgress(_ otherProgress: Progress, includeHandlers: Bool = false) { if includeHandlers { - otherProgress.cancellationHandler = self.cancellationHandler - otherProgress.pausingHandler = self.pausingHandler - otherProgress.resumingHandler = self.resumingHandler + otherProgress.cancellationHandler = cancellationHandler + otherProgress.pausingHandler = pausingHandler + otherProgress.resumingHandler = resumingHandler } - - otherProgress.totalUnitCount = self.totalUnitCount - otherProgress.completedUnitCount = self.completedUnitCount - otherProgress.estimatedTimeRemaining = self.estimatedTimeRemaining - otherProgress.localizedDescription = self.localizedAdditionalDescription - otherProgress.localizedAdditionalDescription = self.localizedAdditionalDescription - otherProgress.isCancellable = self.isCancellable - otherProgress.isPausable = self.isPausable - otherProgress.fileCompletedCount = self.fileCompletedCount - otherProgress.fileURL = self.fileURL - otherProgress.fileTotalCount = self.fileTotalCount - otherProgress.fileCompletedCount = self.fileCompletedCount - otherProgress.fileOperationKind = self.fileOperationKind - otherProgress.kind = self.kind - otherProgress.throughput = self.throughput - for (key, object) in self.userInfo { + otherProgress.totalUnitCount = totalUnitCount + otherProgress.completedUnitCount = completedUnitCount + otherProgress.estimatedTimeRemaining = estimatedTimeRemaining + otherProgress.localizedDescription = localizedAdditionalDescription + otherProgress.localizedAdditionalDescription = localizedAdditionalDescription + otherProgress.isCancellable = isCancellable + otherProgress.isPausable = isPausable + otherProgress.fileCompletedCount = fileCompletedCount + otherProgress.fileURL = fileURL + otherProgress.fileTotalCount = fileTotalCount + otherProgress.fileCompletedCount = fileCompletedCount + otherProgress.fileOperationKind = fileOperationKind + otherProgress.kind = kind + otherProgress.throughput = throughput + + for (key, object) in userInfo { otherProgress.setUserInfoObject(object, forKey: key) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift index fdd81f83f0932..0fed8a9a71b3d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift @@ -17,27 +17,32 @@ import NextcloudKit import OSLog extension FileProviderEnumerator { - func fullRecursiveScan(ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - scanChangesOnly: Bool, - completionHandler: @escaping(_ metadatas: [NextcloudItemMetadataTable], - _ newMetadatas: [NextcloudItemMetadataTable], - _ updatedMetadatas: [NextcloudItemMetadataTable], - _ deletedMetadatas: [NextcloudItemMetadataTable], - _ error: NKError?) -> Void) { - + func fullRecursiveScan( + ncAccount: NextcloudAccount, + ncKit: NextcloudKit, + scanChangesOnly: Bool, + completionHandler: @escaping ( + _ metadatas: [NextcloudItemMetadataTable], + _ newMetadatas: [NextcloudItemMetadataTable], + _ updatedMetadatas: [NextcloudItemMetadataTable], + _ deletedMetadatas: [NextcloudItemMetadataTable], + _ error: NKError? + ) -> Void + ) { let rootContainerDirectoryMetadata = NextcloudItemMetadataTable() rootContainerDirectoryMetadata.directory = true rootContainerDirectoryMetadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue // Create a serial dispatch queue - let dispatchQueue = DispatchQueue(label: "recursiveChangeEnumerationQueue", qos: .userInitiated) + let dispatchQueue = DispatchQueue( + label: "recursiveChangeEnumerationQueue", qos: .userInitiated) dispatchQueue.async { - let results = self.scanRecursively(rootContainerDirectoryMetadata, - ncAccount: ncAccount, - ncKit: ncKit, - scanChangesOnly: scanChangesOnly) + let results = self.scanRecursively( + rootContainerDirectoryMetadata, + ncAccount: ncAccount, + ncKit: ncKit, + scanChangesOnly: scanChangesOnly) // Run a check to ensure files deleted in one location are not updated in another (e.g. when moved) // The recursive scan provides us with updated/deleted metadatas only on a folder by folder basis; @@ -45,29 +50,38 @@ extension FileProviderEnumerator { var checkedDeletedMetadatas = results.deletedMetadatas for updatedMetadata in results.updatedMetadatas { - guard let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: { $0.ocId == updatedMetadata.ocId } ) else { - continue; + guard + let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: { + $0.ocId == updatedMetadata.ocId + }) + else { + continue } checkedDeletedMetadatas.remove(at: matchingDeletedMetadataIdx) } DispatchQueue.main.async { - completionHandler(results.metadatas, results.newMetadatas, results.updatedMetadatas, checkedDeletedMetadatas, results.error) + completionHandler( + results.metadatas, results.newMetadatas, results.updatedMetadatas, + checkedDeletedMetadatas, results.error) } } } - private func scanRecursively(_ directoryMetadata: NextcloudItemMetadataTable, - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - scanChangesOnly: Bool) -> (metadatas: [NextcloudItemMetadataTable], - newMetadatas: [NextcloudItemMetadataTable], - updatedMetadatas: [NextcloudItemMetadataTable], - deletedMetadatas: [NextcloudItemMetadataTable], - error: NKError?) { - - if self.isInvalidated { + private func scanRecursively( + _ directoryMetadata: NextcloudItemMetadataTable, + ncAccount: NextcloudAccount, + ncKit: NextcloudKit, + scanChangesOnly: Bool + ) -> ( + metadatas: [NextcloudItemMetadataTable], + newMetadatas: [NextcloudItemMetadataTable], + updatedMetadatas: [NextcloudItemMetadataTable], + deletedMetadatas: [NextcloudItemMetadataTable], + error: NKError? + ) { + if isInvalidated { return ([], [], [], [], nil) } @@ -80,43 +94,59 @@ extension FileProviderEnumerator { var allDeletedMetadatas: [NextcloudItemMetadataTable] = [] let dbManager = NextcloudFilesDatabaseManager.shared - let dispatchGroup = DispatchGroup() // TODO: Maybe own thread? + let dispatchGroup = DispatchGroup() // TODO: Maybe own thread? dispatchGroup.enter() var criticalError: NKError? - let itemServerUrl = directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue ? - ncAccount.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName + let itemServerUrl = + directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue + ? ncAccount.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName Logger.enumeration.debug("About to read: \(itemServerUrl, privacy: .public)") - FileProviderEnumerator.readServerUrl(itemServerUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: scanChangesOnly) { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError in + FileProviderEnumerator.readServerUrl( + itemServerUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: scanChangesOnly + ) { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError in if readError != nil { let nkReadError = NKError(error: readError!) // Is the error is that we have found matching etags on this item, then ignore it // if we are doing a full rescan - guard nkReadError.isNoChangesError && scanChangesOnly else { - Logger.enumeration.error("Finishing enumeration of changes at \(itemServerUrl, privacy: .public) with \(readError!.localizedDescription, privacy: .public)") + guard nkReadError.isNoChangesError, scanChangesOnly else { + Logger.enumeration.error( + "Finishing enumeration of changes at \(itemServerUrl, privacy: .public) with \(readError!.localizedDescription, privacy: .public)" + ) if nkReadError.isNotFoundError { - Logger.enumeration.info("404 error means item no longer exists. Deleting metadata and reporting as deletion without error") - - if let deletedMetadatas = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: directoryMetadata.ocId) { + Logger.enumeration.info( + "404 error means item no longer exists. Deleting metadata and reporting as deletion without error" + ) + + if let deletedMetadatas = + dbManager.deleteDirectoryAndSubdirectoriesMetadata( + ocId: directoryMetadata.ocId) + { allDeletedMetadatas += deletedMetadatas } else { - Logger.enumeration.error("An error occurred while trying to delete directory and children not found in recursive scan") + Logger.enumeration.error( + "An error occurred while trying to delete directory and children not found in recursive scan" + ) } - } else if nkReadError.isNoChangesError { // All is well, just no changed etags - Logger.enumeration.info("Error was to say no changed files -- not bad error. No need to check children.") + } else if nkReadError.isNoChangesError { // All is well, just no changed etags + Logger.enumeration.info( + "Error was to say no changed files -- not bad error. No need to check children." + ) - } else if nkReadError.isUnauthenticatedError || nkReadError.isCouldntConnectError { + } else if nkReadError.isUnauthenticatedError + || nkReadError.isCouldntConnectError + { // If it is a critical error then stop, if not then continue - Logger.enumeration.error("Error will affect next enumerated items, so stopping enumeration.") + Logger.enumeration.error( + "Error will affect next enumerated items, so stopping enumeration.") criticalError = nkReadError - } dispatchGroup.leave() @@ -124,30 +154,40 @@ extension FileProviderEnumerator { } } - Logger.enumeration.info("Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.info( + "Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) - if let metadatas = metadatas { + if let metadatas { allMetadatas += metadatas } else { - Logger.enumeration.warning("WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.warning( + "WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) } - if let newMetadatas = newMetadatas { + if let newMetadatas { allNewMetadatas += newMetadatas } else { - Logger.enumeration.warning("WARNING: Nil new metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.warning( + "WARNING: Nil new metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) } - if let updatedMetadatas = updatedMetadatas { + if let updatedMetadatas { allUpdatedMetadatas += updatedMetadatas } else { - Logger.enumeration.warning("WARNING: Nil updated metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.warning( + "WARNING: Nil updated metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) } - if let deletedMetadatas = deletedMetadatas { + if let deletedMetadatas { allDeletedMetadatas += deletedMetadatas } else { - Logger.enumeration.warning("WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.warning( + "WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) } dispatchGroup.leave() @@ -160,13 +200,12 @@ extension FileProviderEnumerator { } var childDirectoriesToScan: [NextcloudItemMetadataTable] = [] - var candidateMetadatas: [NextcloudItemMetadataTable] - - if scanChangesOnly { - candidateMetadatas = allUpdatedMetadatas + allNewMetadatas - } else { - candidateMetadatas = allMetadatas - } + var candidateMetadatas: [NextcloudItemMetadataTable] = + if scanChangesOnly { + allUpdatedMetadatas + allNewMetadatas + } else { + allMetadatas + } for candidateMetadata in candidateMetadatas { if candidateMetadata.directory { @@ -175,14 +214,18 @@ extension FileProviderEnumerator { } if childDirectoriesToScan.isEmpty { - return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil) + return ( + metadatas: allMetadatas, newMetadatas: allNewMetadatas, + updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil + ) } for childDirectory in childDirectoriesToScan { - let childScanResult = scanRecursively(childDirectory, - ncAccount: ncAccount, - ncKit: ncKit, - scanChangesOnly: scanChangesOnly) + let childScanResult = scanRecursively( + childDirectory, + ncAccount: ncAccount, + ncKit: ncKit, + scanChangesOnly: scanChangesOnly) allMetadatas += childScanResult.metadatas allNewMetadatas += childScanResult.newMetadatas @@ -190,31 +233,44 @@ extension FileProviderEnumerator { allDeletedMetadatas += childScanResult.deletedMetadatas } - return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil) + return ( + metadatas: allMetadatas, newMetadatas: allNewMetadatas, + updatedMetadatas: allUpdatedMetadatas, + deletedMetadatas: allDeletedMetadatas, nil + ) } - static func handleDepth1ReadFileOrFolder(serverUrl: String, - ncAccount: NextcloudAccount, - files: [NKFile], - error: NKError, - completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?, - _ newMetadatas: [NextcloudItemMetadataTable]?, - _ updatedMetadatas: [NextcloudItemMetadataTable]?, - _ deletedMetadatas: [NextcloudItemMetadataTable]?, - _ readError: Error?) -> Void) { - + static func handleDepth1ReadFileOrFolder( + serverUrl: String, + ncAccount: NextcloudAccount, + files: [NKFile], + error: NKError, + completionHandler: @escaping ( + _ metadatas: [NextcloudItemMetadataTable]?, + _ newMetadatas: [NextcloudItemMetadataTable]?, + _ updatedMetadatas: [NextcloudItemMetadataTable]?, + _ deletedMetadatas: [NextcloudItemMetadataTable]?, + _ readError: Error? + ) -> Void + ) { guard error == .success else { - Logger.enumeration.error("1 depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)") + Logger.enumeration.error( + "1 depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)" + ) completionHandler(nil, nil, nil, nil, error.error) return } - Logger.enumeration.debug("Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.debug( + "Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) let dbManager = NextcloudFilesDatabaseManager.shared DispatchQueue.global(qos: .userInitiated).async { - NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles(files, account: ncAccount.ncKitAccount) { directoryMetadata, childDirectoriesMetadata, metadatas in + NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles( + files, account: ncAccount.ncKitAccount + ) { directoryMetadata, _, metadatas in // STORE DATA FOR CURRENTLY SCANNED DIRECTORY // We have now scanned this directory's contents, so update with etag in order to not check again if not needed @@ -228,64 +284,88 @@ extension FileProviderEnumerator { // that our local copies are up to date -- instead, leave them as the old. // They will get updated when they are the subject of a readServerUrl call. // (See above) - let changedMetadatas = dbManager.updateItemMetadatas(account: ncAccount.ncKitAccount, serverUrl: serverUrl, updatedMetadatas: metadatas, updateDirectoryEtags: false) + let changedMetadatas = dbManager.updateItemMetadatas( + account: ncAccount.ncKitAccount, serverUrl: serverUrl, + updatedMetadatas: metadatas, + updateDirectoryEtags: false) DispatchQueue.main.async { - completionHandler(metadatas, changedMetadatas.newMetadatas, changedMetadatas.updatedMetadatas, changedMetadatas.deletedMetadatas, nil) + completionHandler( + metadatas, changedMetadatas.newMetadatas, changedMetadatas.updatedMetadatas, + changedMetadatas.deletedMetadatas, nil) } } } } - static func readServerUrl(_ serverUrl: String, - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - stopAtMatchingEtags: Bool = false, - depth: String = "1", - completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?, - _ newMetadatas: [NextcloudItemMetadataTable]?, - _ updatedMetadatas: [NextcloudItemMetadataTable]?, - _ deletedMetadatas: [NextcloudItemMetadataTable]?, - _ readError: Error?) -> Void) { - + static func readServerUrl( + _ serverUrl: String, + ncAccount: NextcloudAccount, + ncKit: NextcloudKit, + stopAtMatchingEtags: Bool = false, + depth: String = "1", + completionHandler: @escaping ( + _ metadatas: [NextcloudItemMetadataTable]?, + _ newMetadatas: [NextcloudItemMetadataTable]?, + _ updatedMetadatas: [NextcloudItemMetadataTable]?, + _ deletedMetadatas: [NextcloudItemMetadataTable]?, + _ readError: Error? + ) -> Void + ) { let dbManager = NextcloudFilesDatabaseManager.shared let ncKitAccount = ncAccount.ncKitAccount - Logger.enumeration.debug("Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public) at depth \(depth, privacy: .public). NCKit info: userId: \(ncKit.nkCommonInstance.user, privacy: .public), password is empty: \(ncKit.nkCommonInstance.password == "" ? "EMPTY PASSWORD" : "NOT EMPTY PASSWORD"), urlBase: \(ncKit.nkCommonInstance.urlBase, privacy: .public), ncVersion: \(ncKit.nkCommonInstance.nextcloudVersion, privacy: .public)") + Logger.enumeration.debug( + "Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public) at depth \(depth, privacy: .public). NCKit info: userId: \(ncKit.nkCommonInstance.user, privacy: .public), password is empty: \(ncKit.nkCommonInstance.password == "" ? "EMPTY PASSWORD" : "NOT EMPTY PASSWORD"), urlBase: \(ncKit.nkCommonInstance.urlBase, privacy: .public), ncVersion: \(ncKit.nkCommonInstance.nextcloudVersion, privacy: .public)" + ) - ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: depth, showHiddenFiles: true) { _, files, _, error in + ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: depth, showHiddenFiles: true) { + _, files, _, error in guard error == .success else { - Logger.enumeration.error("\(depth, privacy: .public) depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)") + Logger.enumeration.error( + "\(depth, privacy: .public) depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)" + ) completionHandler(nil, nil, nil, nil, error.error) return } guard let receivedFile = files.first else { - Logger.enumeration.error("Received no items from readFileOrFolder of \(serverUrl, privacy: .public), not much we can do...") + Logger.enumeration.error( + "Received no items from readFileOrFolder of \(serverUrl, privacy: .public), not much we can do..." + ) completionHandler(nil, nil, nil, nil, error.error) return } guard receivedFile.directory else { - Logger.enumeration.debug("Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)") - let itemMetadata = NextcloudItemMetadataTable.fromNKFile(receivedFile, account: ncKitAccount) - dbManager.addItemMetadata(itemMetadata) // TODO: Return some value when it is an update + Logger.enumeration.debug( + "Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" + ) + let itemMetadata = NextcloudItemMetadataTable.fromNKFile( + receivedFile, account: ncKitAccount) + dbManager.addItemMetadata(itemMetadata) // TODO: Return some value when it is an update completionHandler([itemMetadata], nil, nil, nil, error.error) return } if stopAtMatchingEtags, - let directoryMetadata = dbManager.directoryMetadata(account: ncKitAccount, serverUrl: serverUrl) { - + let directoryMetadata = dbManager.directoryMetadata( + account: ncKitAccount, serverUrl: serverUrl) + { let directoryEtag = directoryMetadata.etag guard directoryEtag == "" || directoryEtag != receivedFile.etag else { - Logger.enumeration.debug("Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error.") + Logger.enumeration.debug( + "Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error." + ) - let description = "Fetched directory etag is same as that stored locally. Not fetching child items." - let nkError = NKError(errorCode: NKError.noChangesErrorCode, errorDescription: description) + let description = + "Fetched directory etag is same as that stored locally. Not fetching child items." + let nkError = NKError( + errorCode: NKError.noChangesErrorCode, errorDescription: description) - let metadatas = dbManager.itemMetadatas(account: ncKitAccount, serverUrl: serverUrl) + let metadatas = dbManager.itemMetadatas( + account: ncKitAccount, serverUrl: serverUrl) completionHandler(metadatas, nil, nil, nil, nkError.error) return @@ -294,7 +374,8 @@ extension FileProviderEnumerator { if depth == "0" { if serverUrl != ncAccount.davFilesUrl { - let metadata = NextcloudItemMetadataTable.fromNKFile(receivedFile, account: ncKitAccount) + let metadata = NextcloudItemMetadataTable.fromNKFile( + receivedFile, account: ncKitAccount) let isNew = dbManager.itemMetadataFromOcId(metadata.ocId) == nil let updatedMetadatas = isNew ? [] : [metadata] let newMetadatas = isNew ? [metadata] : [] @@ -306,7 +387,9 @@ extension FileProviderEnumerator { } } } else { - handleDepth1ReadFileOrFolder(serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error, completionHandler: completionHandler) + handleDepth1ReadFileOrFolder( + serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error, + completionHandler: completionHandler) } } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift index a9dfacc9ff433..4ef7f8e1f2417 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift @@ -17,13 +17,13 @@ import NextcloudKit import OSLog class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { - private let enumeratedItemIdentifier: NSFileProviderItemIdentifier private var enumeratedItemMetadata: NextcloudItemMetadataTable? private var enumeratingSystemIdentifier: Bool { - return FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) + FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) } - private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests + + private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests private static let maxItemsPerFileProviderPage = 100 let ncAccount: NextcloudAccount let ncKit: NextcloudKit @@ -31,59 +31,78 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { var isInvalidated = false private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool { - return identifier == .rootContainer || - identifier == .trashContainer || - identifier == .workingSet + identifier == .rootContainer || identifier == .trashContainer || identifier == .workingSet } - - init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, ncKit: NextcloudKit) { + + init( + enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, + ncKit: NextcloudKit + ) { self.enumeratedItemIdentifier = enumeratedItemIdentifier self.ncAccount = ncAccount self.ncKit = ncKit if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) { - Logger.enumeration.debug("Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)") - self.serverUrl = ncAccount.davFilesUrl + Logger.enumeration.debug( + "Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)" + ) + serverUrl = ncAccount.davFilesUrl } else { - Logger.enumeration.debug("Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)") + Logger.enumeration.debug( + "Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)" + ) let dbManager = NextcloudFilesDatabaseManager.shared - enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(enumeratedItemIdentifier) + enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier( + enumeratedItemIdentifier) if enumeratedItemMetadata != nil { - self.serverUrl = enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName + serverUrl = + enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName } else { - Logger.enumeration.error("Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)") + Logger.enumeration.error( + "Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)" + ) } } - Logger.enumeration.info("Set up enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + Logger.enumeration.info( + "Set up enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) super.init() } func invalidate() { - Logger.enumeration.debug("Enumerator is being invalidated for item with identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)") - self.isInvalidated = true + Logger.enumeration.debug( + "Enumerator is being invalidated for item with identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)" + ) + isInvalidated = true } // MARK: - Protocol methods - func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) { - Logger.enumeration.debug("Received enumerate items request for enumerator with user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + func enumerateItems( + for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage + ) { + Logger.enumeration.debug( + "Received enumerate items request for enumerator with user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) /* - inspect the page to determine whether this is an initial or a follow-up request (TODO) - + If this is an enumerator for a directory, the root container or all directories: - perform a server request to fetch directory contents If this is an enumerator for the working set: - perform a server request to update your local database - fetch the working set from your local database - + - inform the observer about the items returned by the server (possibly multiple times) - inform the observer that you are finished with this page */ if enumeratedItemIdentifier == .trashContainer { - Logger.enumeration.debug("Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + Logger.enumeration.debug( + "Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) // TODO! observer.finishEnumerating(upTo: nil) @@ -98,105 +117,140 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { // navigate a little bit in Finder, file picker, etc guard serverUrl != "" else { - Logger.enumeration.error("Enumerator has empty serverUrl -- can't enumerate that! For identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)") + Logger.enumeration.error( + "Enumerator has empty serverUrl -- can't enumerate that! For identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)" + ) observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem)) return } // TODO: Make better use of pagination and handle paging properly - if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage || - page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage { + if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage + || page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage + { + Logger.enumeration.debug( + "Enumerating initial page for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) - Logger.enumeration.debug("Enumerating initial page for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") - - FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) { metadatas, _, _, _, readError in + FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) { + metadatas, _, _, _, readError in guard readError == nil else { - Logger.enumeration.error("Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.localizedDescription, privacy: .public)") + Logger.enumeration.error( + "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.localizedDescription, privacy: .public)" + ) let nkReadError = NKError(error: readError!) observer.finishEnumeratingWithError(nkReadError.fileProviderError) return } - guard let metadatas = metadatas else { - Logger.enumeration.error("Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas.") + guard let metadatas else { + Logger.enumeration.error( + "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas." + ) observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize)) return } - Logger.enumeration.info("Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas") + Logger.enumeration.info( + "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas" + ) - FileProviderEnumerator.completeEnumerationObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas) + FileProviderEnumerator.completeEnumerationObserver( + observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas) } - return; + return } let numPage = Int(String(data: page.rawValue, encoding: .utf8)!)! - Logger.enumeration.debug("Enumerating page \(numPage, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + Logger.enumeration.debug( + "Enumerating page \(numPage, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) // TODO: Handle paging properly // FileProviderEnumerator.completeObserver(observer, ncKit: ncKit, numPage: numPage, itemMetadatas: nil) observer.finishEnumerating(upTo: nil) } - - func enumerateChanges(for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor) { - Logger.enumeration.debug("Received enumerate changes request for enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + + func enumerateChanges( + for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor + ) { + Logger.enumeration.debug( + "Received enumerate changes request for enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) /* - query the server for updates since the passed-in sync anchor (TODO) - + If this is an enumerator for the working set: - note the changes in your local database - + - inform the observer about item deletions and updates (modifications + insertions) - inform the observer when you have finished enumerating up to a subsequent sync anchor */ if enumeratedItemIdentifier == .workingSet { - Logger.enumeration.debug("Enumerating changes in working set for user: \(self.ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.debug( + "Enumerating changes in working set for user: \(self.ncAccount.ncKitAccount, privacy: .public)" + ) // Unlike when enumerating items we can't progressively enumerate items as we need to wait to resolve which items are truly deleted and which // have just been moved elsewhere. - fullRecursiveScan(ncAccount: self.ncAccount, - ncKit: self.ncKit, - scanChangesOnly: true) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in + fullRecursiveScan( + ncAccount: ncAccount, + ncKit: ncKit, + scanChangesOnly: true + ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in if self.isInvalidated { - Logger.enumeration.info("Enumerator invalidated during working set change scan. For user: \(self.ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.info( + "Enumerator invalidated during working set change scan. For user: \(self.ncAccount.ncKitAccount, privacy: .public)" + ) observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize)) return } guard error == nil else { - Logger.enumeration.info("Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public)") + Logger.enumeration.info( + "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public)" + ) observer.finishEnumeratingWithError(error!.fileProviderError) return } - Logger.enumeration.info("Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public). Enumerating items.") - - FileProviderEnumerator.completeChangesObserver(observer, - anchor: anchor, - ncKit: self.ncKit, - newMetadatas: newMetadatas, - updatedMetadatas: updatedMetadatas, - deletedMetadatas: deletedMetadatas) + Logger.enumeration.info( + "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public). Enumerating items." + ) + + FileProviderEnumerator.completeChangesObserver( + observer, + anchor: anchor, + ncKit: self.ncKit, + newMetadatas: newMetadatas, + updatedMetadatas: updatedMetadatas, + deletedMetadatas: deletedMetadatas) } return } else if enumeratedItemIdentifier == .trashContainer { - Logger.enumeration.debug("Enumerating changes in trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.debug( + "Enumerating changes in trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public)" + ) // TODO! observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) return } - Logger.enumeration.info("Enumerating changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)") + Logger.enumeration.info( + "Enumerating changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" + ) // No matter what happens here we finish enumeration in some way, either from the error // handling below or from the completeChangesObserver // TODO: Move to the sync engine extension - FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: true) { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError in + FileProviderEnumerator.readServerUrl( + serverUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: true + ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError in // If we get a 404 we might add more deleted metadatas var currentDeletedMetadatas: [NextcloudItemMetadataTable] = [] @@ -205,46 +259,66 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { } guard readError == nil else { - Logger.enumeration.error("Finishing enumeration of changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.localizedDescription, privacy: .public)") + Logger.enumeration.error( + "Finishing enumeration of changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.localizedDescription, privacy: .public)" + ) let nkReadError = NKError(error: readError!) let fpError = nkReadError.fileProviderError if nkReadError.isNotFoundError { - Logger.enumeration.info("404 error means item no longer exists. Deleting metadata and reporting \(self.serverUrl, privacy: .public) as deletion without error") + Logger.enumeration.info( + "404 error means item no longer exists. Deleting metadata and reporting \(self.serverUrl, privacy: .public) as deletion without error" + ) guard let itemMetadata = self.enumeratedItemMetadata else { - Logger.enumeration.error("Invalid enumeratedItemMetadata, could not delete metadata nor report deletion") + Logger.enumeration.error( + "Invalid enumeratedItemMetadata, could not delete metadata nor report deletion" + ) observer.finishEnumeratingWithError(fpError) return } let dbManager = NextcloudFilesDatabaseManager.shared if itemMetadata.directory { - if let deletedDirectoryMetadatas = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: itemMetadata.ocId) { + if let deletedDirectoryMetadatas = + dbManager.deleteDirectoryAndSubdirectoriesMetadata( + ocId: itemMetadata.ocId) + { currentDeletedMetadatas += deletedDirectoryMetadatas } else { - Logger.enumeration.error("Something went wrong when recursively deleting directory not found.") + Logger.enumeration.error( + "Something went wrong when recursively deleting directory not found." + ) } } else { dbManager.deleteItemMetadata(ocId: itemMetadata.ocId) } - FileProviderEnumerator.completeChangesObserver(observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: nil, updatedMetadatas: nil, deletedMetadatas: [itemMetadata]) + FileProviderEnumerator.completeChangesObserver( + observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: nil, + updatedMetadatas: nil, + deletedMetadatas: [itemMetadata]) return - } else if nkReadError.isNoChangesError { // All is well, just no changed etags - Logger.enumeration.info("Error was to say no changed files -- not bad error. Finishing change enumeration.") + } else if nkReadError.isNoChangesError { // All is well, just no changed etags + Logger.enumeration.info( + "Error was to say no changed files -- not bad error. Finishing change enumeration." + ) observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) - return; + return } observer.finishEnumeratingWithError(fpError) return } - Logger.enumeration.info("Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)") + Logger.enumeration.info( + "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)" + ) - FileProviderEnumerator.completeChangesObserver(observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: newMetadatas, updatedMetadatas: updatedMetadatas, deletedMetadatas: deletedMetadatas) + FileProviderEnumerator.completeChangesObserver( + observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: newMetadatas, + updatedMetadatas: updatedMetadatas, deletedMetadatas: deletedMetadatas) } } @@ -254,29 +328,43 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { // MARK: - Helper methods - private static func metadatasToFileProviderItems(_ itemMetadatas: [NextcloudItemMetadataTable], ncKit: NextcloudKit, completionHandler: @escaping(_ items: [NSFileProviderItem]) -> Void) { + private static func metadatasToFileProviderItems( + _ itemMetadatas: [NextcloudItemMetadataTable], ncKit: NextcloudKit, + completionHandler: @escaping (_ items: [NSFileProviderItem]) -> Void + ) { var items: [NSFileProviderItem] = [] - let conversionQueue = DispatchQueue(label: "metadataToItemConversionQueue", qos: .userInitiated, attributes: .concurrent) - let appendQueue = DispatchQueue(label: "enumeratorItemAppendQueue", qos: .userInitiated) // Serial queue + let conversionQueue = DispatchQueue( + label: "metadataToItemConversionQueue", qos: .userInitiated, attributes: .concurrent) + let appendQueue = DispatchQueue(label: "enumeratorItemAppendQueue", qos: .userInitiated) // Serial queue let dispatchGroup = DispatchGroup() for itemMetadata in itemMetadatas { conversionQueue.async(group: dispatchGroup) { if itemMetadata.e2eEncrypted { - Logger.enumeration.info("Skipping encrypted metadata in enumeration: \(itemMetadata.ocId, privacy: .public) \(itemMetadata.fileName, privacy: .public)") + Logger.enumeration.info( + "Skipping encrypted metadata in enumeration: \(itemMetadata.ocId, privacy: .public) \(itemMetadata.fileName, privacy: .public)" + ) return } - if let parentItemIdentifier = NextcloudFilesDatabaseManager.shared.parentItemIdentifierFromMetadata(itemMetadata) { - let item = FileProviderItem(metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit) - Logger.enumeration.debug("Will enumerate item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public)") + if let parentItemIdentifier = NextcloudFilesDatabaseManager.shared + .parentItemIdentifierFromMetadata(itemMetadata) + { + let item = FileProviderItem( + metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, + ncKit: ncKit) + Logger.enumeration.debug( + "Will enumerate item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public)" + ) appendQueue.async(group: dispatchGroup) { items.append(item) } } else { - Logger.enumeration.error("Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration") + Logger.enumeration.error( + "Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration" + ) } } } @@ -287,11 +375,13 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { } private static func fileProviderPageforNumPage(_ numPage: Int) -> NSFileProviderPage { - return NSFileProviderPage("\(numPage)".data(using: .utf8)!) + NSFileProviderPage("\(numPage)".data(using: .utf8)!) } - private static func completeEnumerationObserver(_ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, itemMetadatas: [NextcloudItemMetadataTable]) { - + private static func completeEnumerationObserver( + _ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, + itemMetadatas: [NextcloudItemMetadataTable] + ) { metadatasToFileProviderItems(itemMetadatas, ncKit: ncKit) { items in observer.didEnumerate(items) Logger.enumeration.info("Did enumerate \(items.count) items") @@ -310,10 +400,17 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { } } - private static func completeChangesObserver(_ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, ncKit: NextcloudKit, newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) { - + private static func completeChangesObserver( + _ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, + ncKit: NextcloudKit, + newMetadatas: [NextcloudItemMetadataTable]?, + updatedMetadatas: [NextcloudItemMetadataTable]?, + deletedMetadatas: [NextcloudItemMetadataTable]? + ) { guard newMetadatas != nil || updatedMetadatas != nil || deletedMetadatas != nil else { - Logger.enumeration.error("Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error.") + Logger.enumeration.error( + "Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error." + ) observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem)) return } @@ -322,19 +419,20 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { var allUpdatedMetadatas: [NextcloudItemMetadataTable] = [] var allDeletedMetadatas: [NextcloudItemMetadataTable] = [] - if let newMetadatas = newMetadatas { + if let newMetadatas { allUpdatedMetadatas += newMetadatas } - if let updatedMetadatas = updatedMetadatas { + if let updatedMetadatas { allUpdatedMetadatas += updatedMetadatas } - if let deletedMetadatas = deletedMetadatas { + if let deletedMetadatas { allDeletedMetadatas = deletedMetadatas } - let allFpItemDeletionsIdentifiers = Array(allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) }) + let allFpItemDeletionsIdentifiers = Array( + allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) }) if !allFpItemDeletionsIdentifiers.isEmpty { observer.didDeleteItems(withIdentifiers: allFpItemDeletionsIdentifiers) } @@ -345,7 +443,9 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { observer.didUpdate(updatedItems) } - Logger.enumeration.info("Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas.") + Logger.enumeration.info( + "Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas." + ) observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index dd3bef8d87380..26c42aca87be0 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -12,11 +12,11 @@ * for more details. */ -import Foundation import FileProvider -import OSLog +import Foundation import NCDesktopClientSocketKit import NextcloudKit +import OSLog extension FileProviderExtension { func sendFileProviderDomainIdentifier() { @@ -28,7 +28,9 @@ extension FileProviderExtension { private func signalEnumeratorAfterAccountSetup() { guard let fpManager = NSFileProviderManager(for: domain) else { - Logger.fileProviderExtension.error("Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup") + Logger.fileProviderExtension.error( + "Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup" + ) return } @@ -36,36 +38,47 @@ extension FileProviderExtension { fpManager.signalErrorResolved(NSFileProviderError(.notAuthenticated)) { error in if error != nil { - Logger.fileProviderExtension.error("Error resolving not authenticated, received error: \(error!.localizedDescription)") + Logger.fileProviderExtension.error( + "Error resolving not authenticated, received error: \(error!.localizedDescription)" + ) } } - Logger.fileProviderExtension.debug("Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)") + Logger.fileProviderExtension.debug( + "Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)" + ) fpManager.signalEnumerator(for: .workingSet) { error in if error != nil { - Logger.fileProviderExtension.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)") + Logger.fileProviderExtension.error( + "Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)" + ) } } } func setupDomainAccount(user: String, serverUrl: String, password: String) { ncAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password) - ncKit.setup(user: ncAccount!.username, - userId: ncAccount!.username, - password: ncAccount!.password, - urlBase: ncAccount!.serverUrl, - userAgent: "Nextcloud-macOS/FileProviderExt", - nextcloudVersion: 25, - delegate: nil) // TODO: add delegate methods for self + ncKit.setup( + user: ncAccount!.username, + userId: ncAccount!.username, + password: ncAccount!.password, + urlBase: ncAccount!.serverUrl, + userAgent: "Nextcloud-macOS/FileProviderExt", + nextcloudVersion: 25, + delegate: nil) // TODO: add delegate methods for self - Logger.fileProviderExtension.info("Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)") + Logger.fileProviderExtension.info( + "Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)" + ) signalEnumeratorAfterAccountSetup() } func removeAccountConfig() { - Logger.fileProviderExtension.info("Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)") + Logger.fileProviderExtension.info( + "Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)" + ) ncAccount = nil } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift index 7abc46090cb16..daa4a24da40c4 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift @@ -12,19 +12,22 @@ * for more details. */ -import Foundation import FileProvider +import Foundation import NextcloudKit import OSLog extension FileProviderExtension: NSFileProviderThumbnailing { - func fetchThumbnails(for itemIdentifiers: [NSFileProviderItemIdentifier], - requestedSize size: CGSize, - perThumbnailCompletionHandler: @escaping (NSFileProviderItemIdentifier, - Data?, - Error?) -> Void, - completionHandler: @escaping (Error?) -> Void) -> Progress { - + func fetchThumbnails( + for itemIdentifiers: [NSFileProviderItemIdentifier], + requestedSize size: CGSize, + perThumbnailCompletionHandler: @escaping ( + NSFileProviderItemIdentifier, + Data?, + Error? + ) -> Void, + completionHandler: @escaping (Error?) -> Void + ) -> Progress { let progress = Progress(totalUnitCount: Int64(itemIdentifiers.count)) var progressCounter: Int64 = 0 @@ -37,21 +40,29 @@ extension FileProviderExtension: NSFileProviderThumbnailing { } for itemIdentifier in itemIdentifiers { - Logger.fileProviderExtension.debug("Fetching thumbnail for item with identifier:\(itemIdentifier.rawValue, privacy: .public)") - guard let metadata = NextcloudFilesDatabaseManager.shared.itemMetadataFromFileProviderItemIdentifier(itemIdentifier), - let thumbnailUrl = metadata.thumbnailUrl(size: size) else { + Logger.fileProviderExtension.debug( + "Fetching thumbnail for item with identifier:\(itemIdentifier.rawValue, privacy: .public)" + ) + guard + let metadata = NextcloudFilesDatabaseManager.shared + .itemMetadataFromFileProviderItemIdentifier(itemIdentifier), + let thumbnailUrl = metadata.thumbnailUrl(size: size) + else { Logger.fileProviderExtension.debug("Did not fetch thumbnail URL") finishCurrent() continue } - Logger.fileProviderExtension.debug("Fetching thumbnail for file:\(metadata.fileName) at:\(thumbnailUrl.absoluteString, privacy: .public)") + Logger.fileProviderExtension.debug( + "Fetching thumbnail for file:\(metadata.fileName) at:\(thumbnailUrl.absoluteString, privacy: .public)" + ) - self.ncKit.getPreview(url: thumbnailUrl) { _, data, error in - if error == .success && data != nil { + ncKit.getPreview(url: thumbnailUrl) { _, data, error in + if error == .success, data != nil { perThumbnailCompletionHandler(itemIdentifier, data, nil) } else { - perThumbnailCompletionHandler(itemIdentifier, nil, NSFileProviderError(.serverUnreachable)) + perThumbnailCompletionHandler( + itemIdentifier, nil, NSFileProviderError(.serverUnreachable)) } finishCurrent() } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index e4912dbaac2db..31c09f227b447 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -13,9 +13,9 @@ */ import FileProvider -import OSLog import NCDesktopClientSocketKit import NextcloudKit +import OSLog class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate { let domain: NSFileProviderDomain @@ -25,15 +25,19 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm return nckb }() - let appGroupIdentifier: String? = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String + let appGroupIdentifier: String? = + Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String var ncAccount: NextcloudAccount? lazy var socketClient: LocalSocketClient? = { guard let containerUrl = pathForAppGroupContainer() else { - Logger.fileProviderExtension.critical("Could not start file provider socket client properly as could not get container url") - return nil; + Logger.fileProviderExtension.critical( + "Could not start file provider socket client properly as could not get container url" + ) + return nil } - let socketPath = containerUrl.appendingPathComponent(".fileprovidersocket", conformingTo: .archive) + let socketPath = containerUrl.appendingPathComponent( + ".fileprovidersocket", conformingTo: .archive) let lineProcessor = FileProviderSocketLineProcessor(delegate: self) return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor) @@ -50,7 +54,9 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData configuration.sharedContainerIdentifier = appGroupIdentifier - let session = URLSession(configuration: configuration, delegate: ncKitBackground, delegateQueue: OperationQueue.main) + let session = URLSession( + configuration: configuration, delegate: ncKitBackground, + delegateQueue: OperationQueue.main) return session }() @@ -59,23 +65,31 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm // The containing application must create a domain using `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the application extension process, call `FileProviderExtension.init(domain:)` to instantiate the extension for that domain, and call methods on the instance. super.init() - self.socketClient?.start() + socketClient?.start() } - + func invalidate() { // TODO: cleanup any resources - Logger.fileProviderExtension.debug("Extension for domain \(self.domain.displayName, privacy: .public) is being torn down") + Logger.fileProviderExtension.debug( + "Extension for domain \(self.domain.displayName, privacy: .public) is being torn down") } // MARK: NSFileProviderReplicatedExtension protocol methods - - func item(for identifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) -> Progress { + + func item( + for identifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest, + completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void + ) -> Progress { // resolve the given identifier to a record in the model - Logger.fileProviderExtension.debug("Received item request for item with identifier: \(identifier.rawValue, privacy: .public)") + Logger.fileProviderExtension.debug( + "Received item request for item with identifier: \(identifier.rawValue, privacy: .public)" + ) if identifier == .rootContainer { - guard let ncAccount = ncAccount else { - Logger.fileProviderExtension.error("Not providing item: \(identifier.rawValue, privacy: .public) as account not set up yet") + guard let ncAccount else { + Logger.fileProviderExtension.error( + "Not providing item: \(identifier.rawValue, privacy: .public) as account not set up yet" + ) completionHandler(nil, NSFileProviderError(.notAuthenticated)) return Progress() } @@ -90,35 +104,52 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm metadata.serverUrl = ncAccount.serverUrl metadata.classFile = NKCommon.TypeClassFile.directory.rawValue - completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: NSFileProviderItemIdentifier.rootContainer, ncKit: ncKit), nil) + completionHandler( + FileProviderItem( + metadata: metadata, + parentItemIdentifier: NSFileProviderItemIdentifier.rootContainer, + ncKit: ncKit), nil) return Progress() } let dbManager = NextcloudFilesDatabaseManager.shared - + guard let metadata = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier), - let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(metadata) else { + let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(metadata) + else { completionHandler(nil, NSFileProviderError(.noSuchItem)) return Progress() } - completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil) + completionHandler( + FileProviderItem( + metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil) return Progress() } - - func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier, version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress { - Logger.fileProviderExtension.debug("Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)") + func fetchContents( + for itemIdentifier: NSFileProviderItemIdentifier, + version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, + completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void + ) -> Progress { + Logger.fileProviderExtension.debug( + "Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)" + ) guard requestedVersion == nil else { // TODO: Add proper support for file versioning - Logger.fileProviderExtension.error("Can't return contents for specific version as this is not supported.") - completionHandler(nil, nil, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])) + Logger.fileProviderExtension.error( + "Can't return contents for specific version as this is not supported.") + completionHandler( + nil, nil, + NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:])) return Progress() } guard ncAccount != nil else { - Logger.fileProviderExtension.error("Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet") + Logger.fileProviderExtension.error( + "Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet" + ) completionHandler(nil, nil, NSFileProviderError(.notAuthenticated)) return Progress() } @@ -126,46 +157,65 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm let dbManager = NextcloudFilesDatabaseManager.shared let ocId = itemIdentifier.rawValue guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)") + Logger.fileProviderExtension.error( + "Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)" + ) completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) return Progress() } guard !metadata.isDocumentViewableOnly else { - Logger.fileProviderExtension.error("Could not get contents of item as is readonly: \(itemIdentifier.rawValue, privacy: .public) \(metadata.fileName, privacy: .public)") + Logger.fileProviderExtension.error( + "Could not get contents of item as is readonly: \(itemIdentifier.rawValue, privacy: .public) \(metadata.fileName, privacy: .public)" + ) completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize)) return Progress() } let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName - Logger.fileProviderExtension.debug("Fetching file with name \(metadata.fileName, privacy: .public) at URL: \(serverUrlFileName, privacy: .public)") + Logger.fileProviderExtension.debug( + "Fetching file with name \(metadata.fileName, privacy: .public) at URL: \(serverUrlFileName, privacy: .public)" + ) let progress = Progress() // TODO: Handle folders nicely do { - let fileNameLocalPath = try localPathForNCFile(ocId: metadata.ocId, fileNameView: metadata.fileNameView, domain: self.domain) + let fileNameLocalPath = try localPathForNCFile( + ocId: metadata.ocId, fileNameView: metadata.fileNameView, domain: domain) - dbManager.setStatusForItemMetadata(metadata, status: NextcloudItemMetadataTable.Status.downloading) { updatedMetadata in + dbManager.setStatusForItemMetadata( + metadata, status: NextcloudItemMetadataTable.Status.downloading + ) { updatedMetadata in - guard let updatedMetadata = updatedMetadata else { - Logger.fileProviderExtension.error("Could not acquire updated metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public), unable to update item status to downloading") + guard let updatedMetadata else { + Logger.fileProviderExtension.error( + "Could not acquire updated metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public), unable to update item status to downloading" + ) completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) return } - self.ncKit.download(serverUrlFileName: serverUrlFileName, - fileNameLocalPath: fileNameLocalPath.path, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: itemIdentifier, completionHandler: { _ in }) - }, progressHandler: { downloadProgress in - downloadProgress.copyCurrentStateToProgress(progress) - }) { _, etag, date, _, _, _, error in + self.ncKit.download( + serverUrlFileName: serverUrlFileName, + fileNameLocalPath: fileNameLocalPath.path, + requestHandler: { request in + progress.setHandlersFromAfRequest(request) + }, + taskHandler: { task in + NSFileProviderManager(for: self.domain)?.register( + task, forItemWithIdentifier: itemIdentifier, completionHandler: { _ in } + ) + }, + progressHandler: { downloadProgress in + downloadProgress.copyCurrentStateToProgress(progress) + } + ) { _, etag, date, _, _, _, error in if error == .success { - Logger.fileTransfer.debug("Acquired contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and filename: \(updatedMetadata.fileName, privacy: .public)") + Logger.fileTransfer.debug( + "Acquired contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and filename: \(updatedMetadata.fileName, privacy: .public)" + ) updatedMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue updatedMetadata.sessionError = "" @@ -175,18 +225,26 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm dbManager.addLocalFileMetadataFromItemMetadata(updatedMetadata) dbManager.addItemMetadata(updatedMetadata) - guard let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(updatedMetadata) else { + guard + let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata( + updatedMetadata) + else { completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) return } - let fpItem = FileProviderItem(metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit) + let fpItem = FileProviderItem( + metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, + ncKit: self.ncKit) completionHandler(fileNameLocalPath, fpItem, nil) } else { - Logger.fileTransfer.error("Could not acquire contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and fileName: \(updatedMetadata.fileName, privacy: .public)") + Logger.fileTransfer.error( + "Could not acquire contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and fileName: \(updatedMetadata.fileName, privacy: .public)" + ) - updatedMetadata.status = NextcloudItemMetadataTable.Status.downloadError.rawValue + updatedMetadata.status = + NextcloudItemMetadataTable.Status.downloadError.rawValue updatedMetadata.sessionError = error.errorDescription dbManager.addItemMetadata(updatedMetadata) @@ -195,40 +253,60 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm } } } - } catch let error { - Logger.fileProviderExtension.error("Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)") + } catch { + Logger.fileProviderExtension.error( + "Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + ) completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize)) } return progress } - - func createItem(basedOn itemTemplate: NSFileProviderItem, fields: NSFileProviderItemFields, contents url: URL?, options: NSFileProviderCreateItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress { + + func createItem( + basedOn itemTemplate: NSFileProviderItem, fields _: NSFileProviderItemFields, + contents url: URL?, options: NSFileProviderCreateItemOptions = [], + request: NSFileProviderRequest, + completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) + -> + Void + ) -> Progress { // TODO: a new item was created on disk, process the item's creation - Logger.fileProviderExtension.debug("Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)") + Logger.fileProviderExtension.debug( + "Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)" + ) guard itemTemplate.contentType != .symbolicLink else { Logger.fileProviderExtension.error("Cannot create item, symbolic links not supported.") - completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:])) + completionHandler( + itemTemplate, NSFileProviderItemFields(), false, + NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:])) return Progress() } - guard let ncAccount = ncAccount else { - Logger.fileProviderExtension.error("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as account not set up yet") - completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.notAuthenticated)) + guard let ncAccount else { + Logger.fileProviderExtension.error( + "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as account not set up yet" + ) + completionHandler( + itemTemplate, NSFileProviderItemFields(), false, + NSFileProviderError(.notAuthenticated)) return Progress() } let dbManager = NextcloudFilesDatabaseManager.shared let parentItemIdentifier = itemTemplate.parentItemIdentifier - let itemTemplateIsFolder = itemTemplate.contentType == .folder || - itemTemplate.contentType == .directory + let itemTemplateIsFolder = + itemTemplate.contentType == .folder || itemTemplate.contentType == .directory if options.contains(.mayAlreadyExist) { // TODO: This needs to be properly handled with a check in the db - Logger.fileProviderExtension.info("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as it may already exist") - completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) + Logger.fileProviderExtension.info( + "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as it may already exist" + ) + completionHandler( + itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) return Progress() } @@ -237,9 +315,16 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm if parentItemIdentifier == .rootContainer { parentItemServerUrl = ncAccount.davFilesUrl } else { - guard let parentItemMetadata = dbManager.directoryMetadata(ocId: parentItemIdentifier.rawValue) else { - Logger.fileProviderExtension.error("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)") - completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) + guard + let parentItemMetadata = dbManager.directoryMetadata( + ocId: parentItemIdentifier.rawValue) + else { + Logger.fileProviderExtension.error( + "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)" + ) + completionHandler( + itemTemplate, NSFileProviderItemFields(), false, + NSFileProviderError(.noSuchItem)) return Progress() } @@ -249,29 +334,43 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm let fileNameLocalPath = url?.path ?? "" let newServerUrlFileName = parentItemServerUrl + "/" + itemTemplate.filename - Logger.fileProviderExtension.debug("About to upload item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) of type: \(itemTemplate.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(itemTemplate.filename) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)") + Logger.fileProviderExtension.debug( + "About to upload item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) of type: \(itemTemplate.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(itemTemplate.filename) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)" + ) if itemTemplateIsFolder { - self.ncKit.createFolder(serverUrlFileName: newServerUrlFileName) { account, ocId, _, error in + ncKit.createFolder(serverUrlFileName: newServerUrlFileName) { account, _, _, error in guard error == .success else { - Logger.fileTransfer.error("Could not create new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + Logger.fileTransfer.error( + "Could not create new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) completionHandler(itemTemplate, [], false, error.fileProviderError) return } // Read contents after creation - self.ncKit.readFileOrFolder(serverUrlFileName: newServerUrlFileName, depth: "0", showHiddenFiles: true) { account, files, _, error in + self.ncKit.readFileOrFolder( + serverUrlFileName: newServerUrlFileName, depth: "0", showHiddenFiles: true + ) { account, files, _, error in guard error == .success else { - Logger.fileTransfer.error("Could not read new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + Logger.fileTransfer.error( + "Could not read new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) return } DispatchQueue.global().async { - NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles(files, account: account) { directoryMetadata, childDirectoriesMetadata, metadatas in + NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles( + files, account: account + ) { + directoryMetadata, _, _ in dbManager.addItemMetadata(directoryMetadata) - let fpItem = FileProviderItem(metadata: directoryMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit) + let fpItem = FileProviderItem( + metadata: directoryMetadata, + parentItemIdentifier: parentItemIdentifier, + ncKit: self.ncKit) completionHandler(fpItem, [], true, nil) } @@ -284,25 +383,37 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm let progress = Progress() - self.ncKit.upload(serverUrlFileName: newServerUrlFileName, - fileNameLocalPath: fileNameLocalPath, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: itemTemplate.itemIdentifier, completionHandler: { _ in }) - }, progressHandler: { uploadProgress in - uploadProgress.copyCurrentStateToProgress(progress) - }) { account, ocId, etag, date, size, _, _, error in - guard error == .success, let ocId = ocId else { - Logger.fileTransfer.error("Could not upload item with filename: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + ncKit.upload( + serverUrlFileName: newServerUrlFileName, + fileNameLocalPath: fileNameLocalPath, + requestHandler: { request in + progress.setHandlersFromAfRequest(request) + }, + taskHandler: { task in + NSFileProviderManager(for: self.domain)?.register( + task, forItemWithIdentifier: itemTemplate.itemIdentifier, + completionHandler: { _ in }) + }, + progressHandler: { uploadProgress in + uploadProgress.copyCurrentStateToProgress(progress) + } + ) { account, ocId, etag, date, size, _, _, error in + guard error == .success, let ocId else { + Logger.fileTransfer.error( + "Could not upload item with filename: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) completionHandler(itemTemplate, [], false, error.fileProviderError) return } - Logger.fileTransfer.info("Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)") + Logger.fileTransfer.info( + "Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)" + ) if size != itemTemplate.documentSize as? Int64 { - Logger.fileTransfer.warning("Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(itemTemplate.documentSize??.int64Value ?? 0))") + Logger.fileTransfer.warning( + "Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(itemTemplate.documentSize??.int64Value ?? 0))" + ) } let newMetadata = NextcloudItemMetadataTable() @@ -324,34 +435,48 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm dbManager.addLocalFileMetadataFromItemMetadata(newMetadata) dbManager.addItemMetadata(newMetadata) - let fpItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit) + let fpItem = FileProviderItem( + metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit + ) completionHandler(fpItem, [], false, nil) } return progress } - - func modifyItem(_ item: NSFileProviderItem, baseVersion version: NSFileProviderItemVersion, changedFields: NSFileProviderItemFields, contents newContents: URL?, options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress { + + func modifyItem( + _ item: NSFileProviderItem, baseVersion _: NSFileProviderItemVersion, + changedFields: NSFileProviderItemFields, contents newContents: URL?, + options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest, + completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) + -> + Void + ) -> Progress { // An item was modified on disk, process the item's modification // TODO: Handle finder things like tags, other possible item changed fields - Logger.fileProviderExtension.debug("Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)") + Logger.fileProviderExtension.debug( + "Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)" + ) - guard let ncAccount = ncAccount else { - Logger.fileProviderExtension.error("Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public) as account not set up yet") + guard let ncAccount else { + Logger.fileProviderExtension.error( + "Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public) as account not set up yet" + ) completionHandler(item, [], false, NSFileProviderError(.notAuthenticated)) return Progress() } let dbManager = NextcloudFilesDatabaseManager.shared let parentItemIdentifier = item.parentItemIdentifier - let itemTemplateIsFolder = item.contentType == .folder || - item.contentType == .directory + let itemTemplateIsFolder = item.contentType == .folder || item.contentType == .directory if options.contains(.mayAlreadyExist) { // TODO: This needs to be properly handled with a check in the db - Logger.fileProviderExtension.warning("Modification for item: \(item.itemIdentifier.rawValue, privacy: .public) may already exist") + Logger.fileProviderExtension.warning( + "Modification for item: \(item.itemIdentifier.rawValue, privacy: .public) may already exist" + ) } var parentItemServerUrl: String @@ -359,8 +484,13 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm if parentItemIdentifier == .rootContainer { parentItemServerUrl = ncAccount.davFilesUrl } else { - guard let parentItemMetadata = dbManager.directoryMetadata(ocId: parentItemIdentifier.rawValue) else { - Logger.fileProviderExtension.error("Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)") + guard + let parentItemMetadata = dbManager.directoryMetadata( + ocId: parentItemIdentifier.rawValue) + else { + Logger.fileProviderExtension.error( + "Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)" + ) completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) return Progress() } @@ -371,7 +501,9 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm let fileNameLocalPath = newContents?.path ?? "" let newServerUrlFileName = parentItemServerUrl + "/" + item.filename - Logger.fileProviderExtension.debug("About to upload modified item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) of type: \(item.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(item.filename, privacy: .public) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)") + Logger.fileProviderExtension.debug( + "About to upload modified item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) of type: \(item.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(item.filename, privacy: .public) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)" + ) var modifiedItem = item @@ -384,10 +516,14 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm if changedFields.contains(.filename) || changedFields.contains(.parentItemIdentifier) { dispatchQueue.async { let ocId = item.itemIdentifier.rawValue - Logger.fileProviderExtension.debug("Changed fields for item \(ocId, privacy: .public) with filename \(item.filename, privacy: .public) includes filename or parentitemidentifier...") + Logger.fileProviderExtension.debug( + "Changed fields for item \(ocId, privacy: .public) with filename \(item.filename, privacy: .public) includes filename or parentitemidentifier..." + ) guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(item.itemIdentifier.rawValue, privacy: .public)") + Logger.fileProviderExtension.error( + "Could not acquire metadata of item with identifier: \(item.itemIdentifier.rawValue, privacy: .public)" + ) completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) return } @@ -395,14 +531,18 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm var renameError: NSFileProviderError? let oldServerUrlFileName = metadata.serverUrl + "/" + metadata.fileName - let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done + let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done moveFileOrFolderDispatchGroup.enter() - self.ncKit.moveFileOrFolder(serverUrlFileNameSource: oldServerUrlFileName, - serverUrlFileNameDestination: newServerUrlFileName, - overwrite: false) { account, error in + self.ncKit.moveFileOrFolder( + serverUrlFileNameSource: oldServerUrlFileName, + serverUrlFileNameDestination: newServerUrlFileName, + overwrite: false + ) { _, error in guard error == .success else { - Logger.fileTransfer.error("Could not move file or folder: \(oldServerUrlFileName, privacy: .public) to \(newServerUrlFileName, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + Logger.fileTransfer.error( + "Could not move file or folder: \(oldServerUrlFileName, privacy: .public) to \(newServerUrlFileName, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) renameError = error.fileProviderError moveFileOrFolderDispatchGroup.leave() return @@ -411,37 +551,49 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm // Remember that a folder metadata's serverUrl is its direct server URL, while for // an item metadata the server URL is the parent folder's URL if itemTemplateIsFolder { - _ = dbManager.renameDirectoryAndPropagateToChildren(ocId: ocId, newServerUrl: newServerUrlFileName, newFileName: item.filename) + _ = dbManager.renameDirectoryAndPropagateToChildren( + ocId: ocId, newServerUrl: newServerUrlFileName, + newFileName: item.filename) self.signalEnumerator { error in if error != nil { - Logger.fileTransfer.error("Error notifying change in moved directory: \(error)") + Logger.fileTransfer.error( + "Error notifying change in moved directory: \(error)") } } } else { - dbManager.renameItemMetadata(ocId: ocId, newServerUrl: parentItemServerUrl, newFileName: item.filename) + dbManager.renameItemMetadata( + ocId: ocId, newServerUrl: parentItemServerUrl, + newFileName: item.filename) } guard let newMetadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileTransfer.error("Could not acquire metadata of item with identifier: \(ocId, privacy: .public), cannot correctly inform of modification") + Logger.fileTransfer.error( + "Could not acquire metadata of item with identifier: \(ocId, privacy: .public), cannot correctly inform of modification" + ) renameError = NSFileProviderError(.noSuchItem) moveFileOrFolderDispatchGroup.leave() return } - modifiedItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit) + modifiedItem = FileProviderItem( + metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, + ncKit: self.ncKit) moveFileOrFolderDispatchGroup.leave() } moveFileOrFolderDispatchGroup.wait() guard renameError == nil else { - Logger.fileTransfer.error("Stopping rename of item with ocId \(ocId, privacy: .public) due to error: \(renameError!.localizedDescription, privacy: .public)") + Logger.fileTransfer.error( + "Stopping rename of item with ocId \(ocId, privacy: .public) due to error: \(renameError!.localizedDescription, privacy: .public)" + ) completionHandler(modifiedItem, [], false, renameError) return } guard !itemTemplateIsFolder else { - Logger.fileTransfer.debug("Only handling renaming for folders. ocId: \(ocId, privacy: .public)") + Logger.fileTransfer.debug( + "Only handling renaming for folders. ocId: \(ocId, privacy: .public)") completionHandler(modifiedItem, [], false, nil) return } @@ -454,7 +606,9 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm } guard !itemTemplateIsFolder else { - Logger.fileTransfer.debug("System requested modification for folder with ocID \(item.itemIdentifier.rawValue, privacy: .public) (\(newServerUrlFileName, privacy: .public)) of something other than folder name.") + Logger.fileTransfer.debug( + "System requested modification for folder with ocID \(item.itemIdentifier.rawValue, privacy: .public) (\(newServerUrlFileName, privacy: .public)) of something other than folder name." + ) completionHandler(modifiedItem, [], false, nil) return Progress() } @@ -463,41 +617,62 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm if changedFields.contains(.contents) { dispatchQueue.async { - Logger.fileProviderExtension.debug("Item modification for \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public) includes contents") + Logger.fileProviderExtension.debug( + "Item modification for \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public) includes contents" + ) guard newContents != nil else { - Logger.fileProviderExtension.warning("WARNING. Could not upload modified contents as was provided nil contents url. ocId: \(item.itemIdentifier.rawValue, privacy: .public)") + Logger.fileProviderExtension.warning( + "WARNING. Could not upload modified contents as was provided nil contents url. ocId: \(item.itemIdentifier.rawValue, privacy: .public)" + ) completionHandler(modifiedItem, [], false, NSFileProviderError(.noSuchItem)) return } let ocId = item.itemIdentifier.rawValue guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(ocId, privacy: .public)") - completionHandler(item, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) + Logger.fileProviderExtension.error( + "Could not acquire metadata of item with identifier: \(ocId, privacy: .public)" + ) + completionHandler( + item, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) return } - dbManager.setStatusForItemMetadata(metadata, status: NextcloudItemMetadataTable.Status.uploading) { updatedMetadata in + dbManager.setStatusForItemMetadata( + metadata, status: NextcloudItemMetadataTable.Status.uploading + ) { updatedMetadata in if updatedMetadata == nil { - Logger.fileProviderExtension.warning("Could not acquire updated metadata of item with identifier: \(ocId, privacy: .public), unable to update item status to uploading") + Logger.fileProviderExtension.warning( + "Could not acquire updated metadata of item with identifier: \(ocId, privacy: .public), unable to update item status to uploading" + ) } - self.ncKit.upload(serverUrlFileName: newServerUrlFileName, - fileNameLocalPath: fileNameLocalPath, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: item.itemIdentifier, completionHandler: { _ in }) - }, progressHandler: { uploadProgress in - uploadProgress.copyCurrentStateToProgress(progress) - }) { account, ocId, etag, date, size, _, _, error in - if error == .success, let ocId = ocId { - Logger.fileProviderExtension.info("Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(item.filename, privacy: .public)") + self.ncKit.upload( + serverUrlFileName: newServerUrlFileName, + fileNameLocalPath: fileNameLocalPath, + requestHandler: { request in + progress.setHandlersFromAfRequest(request) + }, + taskHandler: { task in + NSFileProviderManager(for: self.domain)?.register( + task, forItemWithIdentifier: item.itemIdentifier, + completionHandler: { _ in }) + }, + progressHandler: { uploadProgress in + uploadProgress.copyCurrentStateToProgress(progress) + } + ) { account, ocId, etag, date, size, _, _, error in + if error == .success, let ocId { + Logger.fileProviderExtension.info( + "Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(item.filename, privacy: .public)" + ) if size != item.documentSize as? Int64 { - Logger.fileTransfer.warning("Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(item.documentSize??.int64Value ?? 0))") + Logger.fileTransfer.warning( + "Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(item.documentSize??.int64Value ?? 0))" + ) } let newMetadata = NextcloudItemMetadataTable() @@ -519,10 +694,15 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm dbManager.addLocalFileMetadataFromItemMetadata(newMetadata) dbManager.addItemMetadata(newMetadata) - modifiedItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit) + modifiedItem = FileProviderItem( + metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, + ncKit: self.ncKit + ) completionHandler(modifiedItem, [], false, nil) } else { - Logger.fileTransfer.error("Could not upload item \(item.itemIdentifier.rawValue, privacy: .public) with filename: \(item.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + Logger.fileTransfer.error( + "Could not upload item \(item.itemIdentifier.rawValue, privacy: .public) with filename: \(item.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) metadata.status = NextcloudItemMetadataTable.Status.uploadError.rawValue metadata.sessionError = error.errorDescription @@ -536,19 +716,28 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm } } } else { - Logger.fileProviderExtension.debug("Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete") + Logger.fileProviderExtension.debug( + "Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete" + ) completionHandler(modifiedItem, [], false, nil) } return progress } - - func deleteItem(identifier: NSFileProviderItemIdentifier, baseVersion version: NSFileProviderItemVersion, options: NSFileProviderDeleteItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (Error?) -> Void) -> Progress { - Logger.fileProviderExtension.debug("Received delete item request for item with identifier: \(identifier.rawValue, privacy: .public)") + func deleteItem( + identifier: NSFileProviderItemIdentifier, baseVersion _: NSFileProviderItemVersion, + options _: NSFileProviderDeleteItemOptions = [], request _: NSFileProviderRequest, + completionHandler: @escaping (Error?) -> Void + ) -> Progress { + Logger.fileProviderExtension.debug( + "Received delete item request for item with identifier: \(identifier.rawValue, privacy: .public)" + ) guard ncAccount != nil else { - Logger.fileProviderExtension.error("Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet") + Logger.fileProviderExtension.error( + "Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet" + ) completionHandler(NSFileProviderError(.notAuthenticated)) return Progress() } @@ -566,14 +755,18 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm return Progress() } - self.ncKit.deleteFileOrFolder(serverUrlFileName: serverFileNameUrl) { account, error in + ncKit.deleteFileOrFolder(serverUrlFileName: serverFileNameUrl) { _, error in guard error == .success else { - Logger.fileTransfer.error("Could not delete item with ocId \(identifier.rawValue, privacy: .public) at \(serverFileNameUrl, privacy: .public), received error: \(error.errorDescription, privacy: .public)") + Logger.fileTransfer.error( + "Could not delete item with ocId \(identifier.rawValue, privacy: .public) at \(serverFileNameUrl, privacy: .public), received error: \(error.errorDescription, privacy: .public)" + ) completionHandler(error.fileProviderError) return } - Logger.fileTransfer.info("Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)") + Logger.fileTransfer.info( + "Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)" + ) if itemMetadata.directory { _ = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: ocId) @@ -589,32 +782,41 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm return Progress() } - - func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest) throws -> NSFileProviderEnumerator { - guard let ncAccount = ncAccount else { - Logger.fileProviderExtension.error("Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up") + func enumerator( + for containerItemIdentifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest + ) throws -> NSFileProviderEnumerator { + guard let ncAccount else { + Logger.fileProviderExtension.error( + "Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up" + ) throw NSFileProviderError(.notAuthenticated) } - return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit) + return FileProviderEnumerator( + enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit) } func materializedItemsDidChange(completionHandler: @escaping () -> Void) { - guard let ncAccount = self.ncAccount else { - Logger.fileProviderExtension.error("Not purging stale local file metadatas, account not set up") + guard let ncAccount else { + Logger.fileProviderExtension.error( + "Not purging stale local file metadatas, account not set up") completionHandler() return } guard let fpManager = NSFileProviderManager(for: domain) else { - Logger.fileProviderExtension.error("Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)") + Logger.fileProviderExtension.error( + "Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)" + ) completionHandler() return } let materialisedEnumerator = fpManager.enumeratorForMaterializedItems() - let materialisedObserver = FileProviderMaterialisedEnumerationObserver(ncKitAccount: ncAccount.ncKitAccount) { _ in + let materialisedObserver = FileProviderMaterialisedEnumerationObserver( + ncKitAccount: ncAccount.ncKitAccount + ) { _ in completionHandler() } let startingPage = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data) @@ -622,9 +824,11 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm materialisedEnumerator.enumerateItems(for: materialisedObserver, startingAt: startingPage) } - func signalEnumerator(completionHandler: @escaping(_ error: Error?) -> Void) { - guard let fpManager = NSFileProviderManager(for: self.domain) else { - Logger.fileProviderExtension.error("Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts.") + func signalEnumerator(completionHandler: @escaping (_ error: Error?) -> Void) { + guard let fpManager = NSFileProviderManager(for: domain) else { + Logger.fileProviderExtension.error( + "Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts." + ) return } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift index 9a3c3e7224fa9..b571c38d4c631 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift @@ -13,11 +13,10 @@ */ import FileProvider -import UniformTypeIdentifiers import NextcloudKit +import UniformTypeIdentifiers class FileProviderItem: NSObject, NSFileProviderItem { - enum FileProviderItemTransferError: Error { case downloadError case uploadError @@ -26,71 +25,78 @@ class FileProviderItem: NSObject, NSFileProviderItem { let metadata: NextcloudItemMetadataTable let parentItemIdentifier: NSFileProviderItemIdentifier let ncKit: NextcloudKit - + var itemIdentifier: NSFileProviderItemIdentifier { - return NSFileProviderItemIdentifier(metadata.ocId) + NSFileProviderItemIdentifier(metadata.ocId) } - + var capabilities: NSFileProviderItemCapabilities { guard !metadata.directory else { - return [ .allowsAddingSubItems, - .allowsContentEnumerating, - .allowsReading, - .allowsDeleting, - .allowsRenaming ] + return [ + .allowsAddingSubItems, + .allowsContentEnumerating, + .allowsReading, + .allowsDeleting, + .allowsRenaming, + ] } guard !metadata.lock else { - return [ .allowsReading ] + return [.allowsReading] } - return [ .allowsWriting, - .allowsReading, - .allowsDeleting, - .allowsRenaming, - .allowsReparenting ] + return [ + .allowsWriting, + .allowsReading, + .allowsDeleting, + .allowsRenaming, + .allowsReparenting, + ] } - + var itemVersion: NSFileProviderItemVersion { - NSFileProviderItemVersion(contentVersion: metadata.etag.data(using: .utf8)!, - metadataVersion: metadata.etag.data(using: .utf8)!) + NSFileProviderItemVersion( + contentVersion: metadata.etag.data(using: .utf8)!, + metadataVersion: metadata.etag.data(using: .utf8)!) } - + var filename: String { - return metadata.fileNameView + metadata.fileNameView } - + var contentType: UTType { - if self.itemIdentifier == .rootContainer || metadata.directory { + if itemIdentifier == .rootContainer || metadata.directory { return .folder } - let internalType = ncKit.nkCommonInstance.getInternalType(fileName: metadata.fileNameView, - mimeType: "", - directory: metadata.directory) + let internalType = ncKit.nkCommonInstance.getInternalType( + fileName: metadata.fileNameView, + mimeType: "", + directory: metadata.directory) return UTType(filenameExtension: internalType.ext) ?? .content } var documentSize: NSNumber? { - return NSNumber(value: metadata.size) + NSNumber(value: metadata.size) } var creationDate: Date? { - return metadata.creationDate as Date + metadata.creationDate as Date } var lastUsedDate: Date? { - return metadata.date as Date + metadata.date as Date } var contentModificationDate: Date? { - return metadata.date as Date + metadata.date as Date } var isDownloaded: Bool { - return metadata.directory || NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil + metadata.directory + || NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil } var isDownloading: Bool { - return metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue + metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue } var downloadingError: Error? { @@ -101,30 +107,36 @@ class FileProviderItem: NSObject, NSFileProviderItem { } var isUploaded: Bool { - return NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil + NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil } var isUploading: Bool { - return metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue + metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue } var uploadingError: Error? { if metadata.status == NextcloudItemMetadataTable.Status.uploadError.rawValue { - return FileProviderItemTransferError.uploadError + FileProviderItemTransferError.uploadError } else { - return nil + nil } } var childItemCount: NSNumber? { if metadata.directory { - return NSNumber(integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory(metadata).count) + NSNumber( + integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory( + metadata + ).count) } else { - return nil + nil } } - required init(metadata: NextcloudItemMetadataTable, parentItemIdentifier: NSFileProviderItemIdentifier, ncKit: NextcloudKit) { + required init( + metadata: NextcloudItemMetadataTable, parentItemIdentifier: NSFileProviderItemIdentifier, + ncKit: NextcloudKit + ) { self.metadata = metadata self.parentItemIdentifier = parentItemIdentifier self.ncKit = ncKit diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift index 168de6680c731..e68c3fd8becd9 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift @@ -12,44 +12,53 @@ * for more details. */ -import Foundation import FileProvider +import Foundation import OSLog -class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnumerationObserver { +class FileProviderMaterialisedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver { let ncKitAccount: String let completionHandler: (_ deletedOcIds: Set) -> Void - var allEnumeratedItemIds: Set = Set() + var allEnumeratedItemIds: Set = .init() - required init(ncKitAccount: String, completionHandler: @escaping(_ deletedOcIds: Set) -> Void) { + required init( + ncKitAccount: String, completionHandler: @escaping (_ deletedOcIds: Set) -> Void + ) { self.ncKitAccount = ncKitAccount self.completionHandler = completionHandler super.init() } func didEnumerate(_ updatedItems: [NSFileProviderItemProtocol]) { - let updatedItemsIds = Array(updatedItems.map { $0.itemIdentifier.rawValue }) + let updatedItemsIds = Array(updatedItems.map(\.itemIdentifier.rawValue)) for updatedItemsId in updatedItemsIds { allEnumeratedItemIds.insert(updatedItemsId) } } - func finishEnumerating(upTo nextPage: NSFileProviderPage?) { + func finishEnumerating(upTo _: NSFileProviderPage?) { Logger.materialisedFileHandling.debug("Handling enumerated materialised items.") - FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds, - account: self.ncKitAccount, - completionHandler: self.completionHandler) + FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems( + allEnumeratedItemIds, + account: ncKitAccount, + completionHandler: completionHandler) } func finishEnumeratingWithError(_ error: Error) { - Logger.materialisedFileHandling.error("Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far") - FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds, - account: self.ncKitAccount, - completionHandler: self.completionHandler) + Logger.materialisedFileHandling.error( + "Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far" + ) + FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems( + allEnumeratedItemIds, + account: ncKitAccount, + completionHandler: completionHandler) } - static func handleEnumeratedItems(_ itemIds: Set, account: String, completionHandler: @escaping(_ deletedOcIds: Set) -> Void) { + static func handleEnumeratedItems( + _ itemIds: Set, account: String, + completionHandler: @escaping (_ deletedOcIds: Set) -> Void + ) { let dbManager = NextcloudFilesDatabaseManager.shared let databaseLocalFileMetadatas = dbManager.localFileMetadatas(account: account) var noLongerMaterialisedIds = Set() @@ -60,12 +69,13 @@ class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnum guard itemIds.contains(localFileOcId) else { noLongerMaterialisedIds.insert(localFileOcId) - continue; + continue } } DispatchQueue.main.async { - Logger.materialisedFileHandling.info("Cleaning up local file metadatas for unmaterialised items") + Logger.materialisedFileHandling.info( + "Cleaning up local file metadatas for unmaterialised items") for itemId in noLongerMaterialisedIds { dbManager.deleteLocalFileMetadata(ocId: itemId) } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift index cf6ff8c771eb4..3a9f54a10abf4 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderSocketLineProcessor.swift @@ -24,25 +24,27 @@ class FileProviderSocketLineProcessor: NSObject, LineProcessor { } func process(_ line: String) { - if (line.contains("~")) { // We use this as the separator specifically in ACCOUNT_DETAILS - Logger.desktopClientConnection.debug("Processing file provider line with potentially sensitive user data") + if line.contains("~") { // We use this as the separator specifically in ACCOUNT_DETAILS + Logger.desktopClientConnection.debug( + "Processing file provider line with potentially sensitive user data") } else { - Logger.desktopClientConnection.debug("Processing file provider line: \(line, privacy: .public)") + Logger.desktopClientConnection.debug( + "Processing file provider line: \(line, privacy: .public)") } let splitLine = line.split(separator: ":", maxSplits: 1) guard let commandSubsequence = splitLine.first else { Logger.desktopClientConnection.error("Input line did not have a first element") - return; + return } - let command = String(commandSubsequence); + let command = String(commandSubsequence) Logger.desktopClientConnection.debug("Received command: \(command, privacy: .public)") - if (command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER") { + if command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER" { delegate.sendFileProviderDomainIdentifier() - } else if (command == "ACCOUNT_NOT_AUTHENTICATED") { + } else if command == "ACCOUNT_NOT_AUTHENTICATED" { delegate.removeAccountConfig() - } else if (command == "ACCOUNT_DETAILS") { + } else if command == "ACCOUNT_DETAILS" { guard let accountDetailsSubsequence = splitLine.last else { return } let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 2) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift index 1e68cf890ea26..d1465276a6d07 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift @@ -12,17 +12,22 @@ * for more details. */ -import Foundation import FileProvider +import Foundation import OSLog func pathForAppGroupContainer() -> URL? { - guard let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String else { - Logger.localFileOps.critical("Could not get container url as missing SocketApiPrefix info in app Info.plist") + guard + let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") + as? String + else { + Logger.localFileOps.critical( + "Could not get container url as missing SocketApiPrefix info in app Info.plist") return nil } - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) + return FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: appGroupIdentifier) } func pathForFileProviderExtData() -> URL? { @@ -32,7 +37,9 @@ func pathForFileProviderExtData() -> URL? { func pathForFileProviderTempFilesForDomain(_ domain: NSFileProviderDomain) throws -> URL? { guard let fpManager = NSFileProviderManager(for: domain) else { - Logger.localFileOps.error("Unable to get file provider manager for domain: \(domain.displayName, privacy: .public)") + Logger.localFileOps.error( + "Unable to get file provider manager for domain: \(domain.displayName, privacy: .public)" + ) throw NSFileProviderError(.providerNotFound) } @@ -40,9 +47,13 @@ func pathForFileProviderTempFilesForDomain(_ domain: NSFileProviderDomain) throw return fileProviderDataUrl.appendingPathComponent("TemporaryNextcloudFiles/") } -func localPathForNCFile(ocId: String, fileNameView: String, domain: NSFileProviderDomain) throws -> URL { +func localPathForNCFile(ocId _: String, fileNameView: String, domain: NSFileProviderDomain) throws + -> URL +{ guard let fileProviderFilesPathUrl = try pathForFileProviderTempFilesForDomain(domain) else { - Logger.localFileOps.error("Unable to get path for file provider temp files for domain: \(domain.displayName, privacy: .public)") + Logger.localFileOps.error( + "Unable to get path for file provider temp files for domain: \(domain.displayName, privacy: .public)" + ) throw URLError(.badURL) } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift index 0dc628d7d42c8..9e5b64656608d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift @@ -12,21 +12,20 @@ * for more details. */ -import Foundation import FileProvider +import Foundation class NextcloudAccount: NSObject { static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/" let username, password, ncKitAccount, serverUrl, davFilesUrl: String init(user: String, serverUrl: String, password: String) { - self.username = user + username = user self.password = password - self.ncKitAccount = user + " " + serverUrl + ncKitAccount = user + " " + serverUrl self.serverUrl = serverUrl - self.davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user + davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user super.init() } } - From 88d83e3682defc54f6d59516a7202abf4877fcb9 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 23 Jan 2024 23:42:52 +0800 Subject: [PATCH 12/37] Add .swift-format.json Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- .swift-format.json | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .swift-format.json diff --git a/.swift-format.json b/.swift-format.json new file mode 100644 index 0000000000000..722b5de35ad26 --- /dev/null +++ b/.swift-format.json @@ -0,0 +1,69 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 100, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 8, + "version" : 1 +} From 096f56096bc978efc0a1c4b4b487f3da726e1c90 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 5 Feb 2024 17:21:32 +0800 Subject: [PATCH 13/37] Clean up file provider extension property declarations and init Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- .../FileProviderExtension.swift | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 31c09f227b447..2f6039a71d816 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -20,26 +20,17 @@ import OSLog class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate { let domain: NSFileProviderDomain let ncKit = NextcloudKit() - lazy var ncKitBackground: NKBackground = { - let nckb = NKBackground(nkCommonInstance: ncKit.nkCommonInstance) - return nckb - }() - - let appGroupIdentifier: String? = - Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String + let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String var ncAccount: NextcloudAccount? + lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance) lazy var socketClient: LocalSocketClient? = { guard let containerUrl = pathForAppGroupContainer() else { - Logger.fileProviderExtension.critical( - "Could not start file provider socket client properly as could not get container url" - ) + Logger.fileProviderExtension.critical("Won't start client, no container url") return nil } - let socketPath = containerUrl.appendingPathComponent( ".fileprovidersocket", conformingTo: .archive) let lineProcessor = FileProviderSocketLineProcessor(delegate: self) - return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor) }() @@ -61,9 +52,11 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm }() required init(domain: NSFileProviderDomain) { + // The containing application must create a domain using + // `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the + // application extension process, call `FileProviderExtension.init(domain:)` to instantiate + // the extension for that domain, and call methods on the instance. self.domain = domain - // The containing application must create a domain using `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the application extension process, call `FileProviderExtension.init(domain:)` to instantiate the extension for that domain, and call methods on the instance. - super.init() socketClient?.start() } From f45195825d60d7ce5ebfb62607fc6fbe891ea445 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Mon, 5 Feb 2024 17:31:55 +0800 Subject: [PATCH 14/37] Cllean up function and method calls in File Provider Extension Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- ...loudFilesDatabaseManager+Directories.swift | 6 ++++-- .../NextcloudFilesDatabaseManager.swift | 21 +++++++++++++------ .../NextcloudItemMetadataTable+NKFile.swift | 7 +++++-- .../Database/NextcloudItemMetadataTable.swift | 16 ++++++++------ .../FileProviderEnumerator.swift | 14 +++++++++---- .../FileProviderExt/FileProviderItem.swift | 3 ++- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift index 0e29754df9fa3..62ad0445cf13d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift @@ -28,8 +28,10 @@ extension NextcloudFilesDatabaseManager { of: placeholderSeparator, with: problematicSeparator) if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, - directoryItemServerUrl, directoryItemFileName + "account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", + account, + directoryItemServerUrl, + directoryItemFileName ).first { return NextcloudItemMetadataTable(value: metadata) } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift index f0344f40b39a5..77d980a78832a 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift @@ -123,7 +123,9 @@ class NextcloudFilesDatabaseManager: NSObject { -> [NextcloudItemMetadataTable] { let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, + "account == %@ AND serverUrl == %@ AND status == %@", + account, + serverUrl, status.rawValue) return sortedItemMetadatas(metadatas) } @@ -216,7 +218,9 @@ class NextcloudFilesDatabaseManager: NSObject { } func updateItemMetadatas( - account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], + account: String, + serverUrl: String, + updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool ) -> ( newMetadatas: [NextcloudItemMetadataTable]?, @@ -227,7 +231,9 @@ class NextcloudFilesDatabaseManager: NSObject { do { let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, + "account == %@ AND serverUrl == %@ AND status == %@", + account, + serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue) let metadatasToDelete = processItemMetadatasToDelete( @@ -249,7 +255,8 @@ class NextcloudFilesDatabaseManager: NSObject { for metadata in directoriesNeedingRename { if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren( - ocId: metadata.ocId, newServerUrl: metadata.serverUrl, + ocId: metadata.ocId, + newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) { metadatasToUpdate += updatedDirectoryChildren @@ -270,7 +277,8 @@ class NextcloudFilesDatabaseManager: NSObject { } return ( - newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, + newMetadatas: metadatasToCreate, + updatedMetadatas: metadatasToUpdate, deletedMetadatas: metadatasToDelete ) } catch { @@ -282,7 +290,8 @@ class NextcloudFilesDatabaseManager: NSObject { } func setStatusForItemMetadata( - _ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, + _ metadata: NextcloudItemMetadataTable, + status: NextcloudItemMetadataTable.Status, completionHandler: @escaping (_ updatedMetadata: NextcloudItemMetadataTable?) -> Void ) { let database = ncDatabase() diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift index e64818ebc51df..c54744948af41 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift @@ -104,8 +104,11 @@ extension NextcloudItemMetadataTable { var metadatas: [NextcloudItemMetadataTable] = [] let conversionQueue = DispatchQueue( - label: "nkFileToMetadataConversionQueue", qos: .userInitiated, attributes: .concurrent) - let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) // Serial queue + label: "nkFileToMetadataConversionQueue", + qos: .userInitiated, + attributes: .concurrent) + // appendQueue is a serial queue, not concurrent + let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) let dispatchGroup = DispatchGroup() for file in files { diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift index 84ab23e1d7f91..1ba072128e676 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift @@ -94,7 +94,8 @@ class NextcloudItemMetadataTable: Object { @Persisted var sessionSelector = "" @Persisted var sessionTaskIdentifier: Int = 0 @Persisted var sharePermissionsCollaborationServices: Int = 0 - let sharePermissionsCloudMesh = List() // TODO: Find a way to compare these in remote state check + // TODO: Find a way to compare these two below in remote state check + let sharePermissionsCloudMesh = List() let shareType = List() @Persisted var size: Int64 = 0 @Persisted var status: Int = 0 @@ -183,9 +184,12 @@ class NextcloudItemMetadataTable: Object { func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool { - comparingMetadata.etag == etag && comparingMetadata.fileNameView == fileNameView - && comparingMetadata.date == date && comparingMetadata.permissions == permissions - && comparingMetadata.hasPreview == hasPreview && comparingMetadata.note == note + comparingMetadata.etag == etag + && comparingMetadata.fileNameView == fileNameView + && comparingMetadata.date == date + && comparingMetadata.permissions == permissions + && comparingMetadata.hasPreview == hasPreview + && comparingMetadata.note == note && comparingMetadata.lock == lock && comparingMetadata.sharePermissionsCollaborationServices == sharePermissionsCollaborationServices @@ -203,13 +207,13 @@ class NextcloudItemMetadataTable: Object { } let urlBase = urlBase.urlEncoded! - let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user // Leave the leading slash + // Leave the leading slash in webdavUrl + let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user let serverFileRelativeUrl = serverUrl.replacingOccurrences(of: webdavUrl, with: "") + "/" + fileName let urlString = "\(urlBase)/index.php/core/preview.png?file=\(serverFileRelativeUrl)&x=\(size.width)&y=\(size.height)&a=1&mode=cover" - return URL(string: urlString) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift index 4ef7f8e1f2417..dc6e306213167 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift @@ -23,7 +23,8 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) } - private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests + // TODO: actually use this in NCKit and server requests + private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) private static let maxItemsPerFileProviderPage = 100 let ncAccount: NextcloudAccount let ncKit: NextcloudKit @@ -35,7 +36,8 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { } init( - enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, + enumeratedItemIdentifier: NSFileProviderItemIdentifier, + ncAccount: NextcloudAccount, ncKit: NextcloudKit ) { self.enumeratedItemIdentifier = enumeratedItemIdentifier @@ -317,8 +319,12 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { ) FileProviderEnumerator.completeChangesObserver( - observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: newMetadatas, - updatedMetadatas: updatedMetadatas, deletedMetadatas: deletedMetadatas) + observer, + anchor: anchor, + ncKit: self.ncKit, + newMetadatas: newMetadatas, + updatedMetadatas: updatedMetadatas, + deletedMetadatas: deletedMetadatas) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift index b571c38d4c631..9dc4dbbb5f35b 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift @@ -134,7 +134,8 @@ class FileProviderItem: NSObject, NSFileProviderItem { } required init( - metadata: NextcloudItemMetadataTable, parentItemIdentifier: NSFileProviderItemIdentifier, + metadata: NextcloudItemMetadataTable, + parentItemIdentifier: NSFileProviderItemIdentifier, ncKit: NextcloudKit ) { self.metadata = metadata From c115c3987e68f457799386c2070ce63f16294cde Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Wed, 7 Feb 2024 02:36:31 +0000 Subject: [PATCH 15/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_ar.ts | 8 ++++---- translations/client_bg.ts | 4 ++-- translations/client_br.ts | 4 ++-- translations/client_ca.ts | 4 ++-- translations/client_cs.ts | 4 ++-- translations/client_da.ts | 4 ++-- translations/client_de.ts | 8 ++++---- translations/client_el.ts | 4 ++-- translations/client_en_GB.ts | 8 ++++---- translations/client_eo.ts | 4 ++-- translations/client_es.ts | 8 ++++---- translations/client_es_CL.ts | 4 ++-- translations/client_es_CO.ts | 4 ++-- translations/client_es_CR.ts | 4 ++-- translations/client_es_DO.ts | 4 ++-- translations/client_es_EC.ts | 4 ++-- translations/client_es_GT.ts | 4 ++-- translations/client_es_HN.ts | 4 ++-- translations/client_es_MX.ts | 4 ++-- translations/client_es_SV.ts | 4 ++-- translations/client_et.ts | 4 ++-- translations/client_eu.ts | 4 ++-- translations/client_fa.ts | 4 ++-- translations/client_fi.ts | 4 ++-- translations/client_fr.ts | 4 ++-- translations/client_gl.ts | 4 ++-- translations/client_he.ts | 4 ++-- translations/client_hr.ts | 4 ++-- translations/client_hu.ts | 4 ++-- translations/client_is.ts | 4 ++-- translations/client_it.ts | 4 ++-- translations/client_ja.ts | 4 ++-- translations/client_ko.ts | 8 ++++---- translations/client_lt_LT.ts | 4 ++-- translations/client_lv.ts | 4 ++-- translations/client_mk.ts | 4 ++-- translations/client_nb_NO.ts | 4 ++-- translations/client_nl.ts | 4 ++-- translations/client_oc.ts | 4 ++-- translations/client_pl.ts | 4 ++-- translations/client_pt.ts | 4 ++-- translations/client_pt_BR.ts | 4 ++-- translations/client_ro.ts | 4 ++-- translations/client_ru.ts | 4 ++-- translations/client_sc.ts | 4 ++-- translations/client_sk.ts | 4 ++-- translations/client_sl.ts | 4 ++-- translations/client_sr.ts | 8 ++++---- translations/client_sv.ts | 8 ++++---- translations/client_th.ts | 4 ++-- translations/client_tr.ts | 4 ++-- translations/client_uk.ts | 4 ++-- translations/client_zh_CN.ts | 4 ++-- translations/client_zh_HK.ts | 8 ++++---- translations/client_zh_TW.ts | 8 ++++---- 55 files changed, 128 insertions(+), 128 deletions(-) diff --git a/translations/client_ar.ts b/translations/client_ar.ts index cd0a7b1a35e52..6082171cf9906 100644 --- a/translations/client_ar.ts +++ b/translations/client_ar.ts @@ -1655,8 +1655,8 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. - خطأ في جلب مُعرِّف المجلد المشفر. + Error fetching encrypted folder ID. + @@ -5065,8 +5065,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - تعذّر جلب المفتاح العمومي publicKey للمستخدِم %1 + Could not fetch public key for user %1 + diff --git a/translations/client_bg.ts b/translations/client_bg.ts index 45e1e0bbf5abe..ac1d327ef3dd8 100644 --- a/translations/client_bg.ts +++ b/translations/client_bg.ts @@ -1656,7 +1656,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5081,7 +5081,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_br.ts b/translations/client_br.ts index 04e0bbe38992f..e7cba20486153 100644 --- a/translations/client_br.ts +++ b/translations/client_br.ts @@ -1648,7 +1648,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5055,7 +5055,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_ca.ts b/translations/client_ca.ts index 413180c39a541..c47e04e5a96cc 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -1655,7 +1655,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5056,7 +5056,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 69f8c5291d89e..316fa4142215c 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -1660,7 +1660,7 @@ Toto může být způsobeno problémem s OpenSSL knihovnami. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5085,7 +5085,7 @@ Server odpověděl chybou: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_da.ts b/translations/client_da.ts index 9b642d509b260..70b35b26a6174 100644 --- a/translations/client_da.ts +++ b/translations/client_da.ts @@ -1653,7 +1653,7 @@ Dette kan være et problem med dine OpenSSL biblioteker. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5060,7 +5060,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_de.ts b/translations/client_de.ts index b62f121af9d05..9d1e3e13b1bc2 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -1660,8 +1660,8 @@ Dies kann ein Problem mit Ihren OpenSSL-Bibliotheken sein. - Error fetching encrypted folder id. - Fehler beim Abrufen der verschlüsselten Ordner-ID. + Error fetching encrypted folder ID. + @@ -5086,8 +5086,8 @@ Server antwortete mit Fehler: %2 - Could not fetch publicKey for user %1 - Öffentlicher Schlüssel für den Benutzer %1 konnte nicht abgerufen werden + Could not fetch public key for user %1 + diff --git a/translations/client_el.ts b/translations/client_el.ts index ea31b52bbb599..dad13d738b6e7 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -1651,7 +1651,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5063,7 +5063,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index 3dfcf6a582884..4b5d77f4e3a56 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -1661,8 +1661,8 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. + @@ -5087,8 +5087,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 + diff --git a/translations/client_eo.ts b/translations/client_eo.ts index f933e05990beb..59c9f42fc7567 100644 --- a/translations/client_eo.ts +++ b/translations/client_eo.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5053,7 +5053,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es.ts b/translations/client_es.ts index 3fbad46f357d4..145478d4e111e 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -1662,8 +1662,8 @@ Esto podría ser un problema con tu librería OpenSSL - Error fetching encrypted folder id. - Error al obtener el id de la carpeta cifrada. + Error fetching encrypted folder ID. + @@ -5089,8 +5089,8 @@ El servidor respondió con el error: %2 - Could not fetch publicKey for user %1 - No se pudo obtener la llave pública para el usuario %1 + Could not fetch public key for user %1 + diff --git a/translations/client_es_CL.ts b/translations/client_es_CL.ts index b5917d6ef6d2c..62e57cd1e449c 100644 --- a/translations/client_es_CL.ts +++ b/translations/client_es_CL.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_CO.ts b/translations/client_es_CO.ts index 58f996e041b52..d746d9302d81a 100644 --- a/translations/client_es_CO.ts +++ b/translations/client_es_CO.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_CR.ts b/translations/client_es_CR.ts index 9d07233baacb4..5e6876acbc893 100644 --- a/translations/client_es_CR.ts +++ b/translations/client_es_CR.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_DO.ts b/translations/client_es_DO.ts index e7f93cf928343..f3914b140b785 100644 --- a/translations/client_es_DO.ts +++ b/translations/client_es_DO.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_EC.ts b/translations/client_es_EC.ts index fdfe9ecf39cae..f429fbd7b55e6 100644 --- a/translations/client_es_EC.ts +++ b/translations/client_es_EC.ts @@ -1656,7 +1656,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5081,7 +5081,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_GT.ts b/translations/client_es_GT.ts index 5c7cf792b04d3..a626b79b19da1 100644 --- a/translations/client_es_GT.ts +++ b/translations/client_es_GT.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_HN.ts b/translations/client_es_HN.ts index 55893b33d8d59..44368059a4d96 100644 --- a/translations/client_es_HN.ts +++ b/translations/client_es_HN.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_MX.ts b/translations/client_es_MX.ts index df4c82aeffc74..50703d278a279 100644 --- a/translations/client_es_MX.ts +++ b/translations/client_es_MX.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_es_SV.ts b/translations/client_es_SV.ts index cf0a6ecc0962f..581a98346a0c8 100644 --- a/translations/client_es_SV.ts +++ b/translations/client_es_SV.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5046,7 +5046,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_et.ts b/translations/client_et.ts index eb0b1684d57a6..42b4e47950f68 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5044,7 +5044,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_eu.ts b/translations/client_eu.ts index 6420101ca506a..e4c57b50e548f 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -1661,7 +1661,7 @@ Baliteke OpenSSL liburutegiekin arazoa egotea. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5087,7 +5087,7 @@ Zerbitzariak errorearekin erantzun du: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_fa.ts b/translations/client_fa.ts index 28498a37d116d..97b90856713fa 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -1657,7 +1657,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5082,7 +5082,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 8cbb88e1abbaa..660fa5a9bf24e 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -1654,7 +1654,7 @@ OpenSSL-kirjastosi kanssa saattaa olla ongelma. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5057,7 +5057,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 36a510a4b48be..f9829e0b8ae11 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -1659,7 +1659,7 @@ Cela peut être un problème avec vos bibliothèques OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5085,7 +5085,7 @@ Le serveur a répondu avec l'erreur : %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_gl.ts b/translations/client_gl.ts index 911c67a91994a..033cbdfd2ecf0 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -1661,7 +1661,7 @@ Isto pode ser un problema coas súas bibliotecas OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5087,7 +5087,7 @@ O servidor respondeu co erro: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_he.ts b/translations/client_he.ts index 96488d70240d9..e3af198fac2ad 100644 --- a/translations/client_he.ts +++ b/translations/client_he.ts @@ -1650,7 +1650,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5051,7 +5051,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_hr.ts b/translations/client_hr.ts index 1af053954e9a8..3c25692c8fd77 100644 --- a/translations/client_hr.ts +++ b/translations/client_hr.ts @@ -1655,7 +1655,7 @@ Možda se radi o pogrešci u radu OpenSSL biblioteka. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5078,7 +5078,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 1a3b935a36230..47c56c0e9fc9b 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -1657,7 +1657,7 @@ Ezt a problémát valószínűleg az OpenSSL programkönyvtárakban kell keresni - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5083,7 +5083,7 @@ A kiszolgáló hibával válaszolt: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_is.ts b/translations/client_is.ts index 74d8e37ab8019..700d4819aba9e 100644 --- a/translations/client_is.ts +++ b/translations/client_is.ts @@ -1657,7 +1657,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5059,7 +5059,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_it.ts b/translations/client_it.ts index 3172037e9845e..5425e3a6d03bf 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -1659,7 +1659,7 @@ Questo può essere un problema delle le tue librerie OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5074,7 +5074,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_ja.ts b/translations/client_ja.ts index 2e08f0feced4c..add938ae8dbac 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -1655,7 +1655,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5077,7 +5077,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_ko.ts b/translations/client_ko.ts index b2dbd7e2efd7b..74db615467b86 100644 --- a/translations/client_ko.ts +++ b/translations/client_ko.ts @@ -1660,8 +1660,8 @@ OpenSSL 라이브러리 이슈일 수 있습니다. - Error fetching encrypted folder id. - 암호화된 폴더 id를 가져오는 중 오류가 발생했습니다. + Error fetching encrypted folder ID. + @@ -5088,8 +5088,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - %1님의 공개 키를 가져오지 못했습니다. + Could not fetch public key for user %1 + diff --git a/translations/client_lt_LT.ts b/translations/client_lt_LT.ts index 297bc94bbdc51..55025b58d284b 100644 --- a/translations/client_lt_LT.ts +++ b/translations/client_lt_LT.ts @@ -1650,7 +1650,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5056,7 +5056,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_lv.ts b/translations/client_lv.ts index 561312c80e913..100e3c7f15cd8 100644 --- a/translations/client_lv.ts +++ b/translations/client_lv.ts @@ -1661,7 +1661,7 @@ Varētu būt problēma ar jūsu OpenSSL bibliotēkām. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5064,7 +5064,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_mk.ts b/translations/client_mk.ts index 759d7ad3a5385..c3eb103163c28 100644 --- a/translations/client_mk.ts +++ b/translations/client_mk.ts @@ -1649,7 +1649,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5053,7 +5053,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index dde234e0c3af9..7989403a08f6b 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -1658,7 +1658,7 @@ Dette kan være et problem med OpenSSL-bibliotekene dine. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5083,7 +5083,7 @@ Server svarte med feil: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_nl.ts b/translations/client_nl.ts index 4ed1e3c2e4d0d..ddaf8eff525dc 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -1654,7 +1654,7 @@ Dit kan een probleem zijn met je OpenSSL-bibliotheken. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5077,7 +5077,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_oc.ts b/translations/client_oc.ts index 22757cccb3763..bfde815213250 100644 --- a/translations/client_oc.ts +++ b/translations/client_oc.ts @@ -1647,7 +1647,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5044,7 +5044,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 9bebc811cc19f..9357d0597b6aa 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -1661,7 +1661,7 @@ Może to być problem z bibliotekami OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5087,7 +5087,7 @@ Serwer odpowiedział błędem: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 76a2329018317..6dc3d3072c792 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -1648,7 +1648,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5049,7 +5049,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index b1d8911428a53..613337b338da9 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -1657,7 +1657,7 @@ Isso pode ser um problema com suas bibliotecas OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5081,7 +5081,7 @@ Servidor respondeu com erro: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_ro.ts b/translations/client_ro.ts index dd8c138b61289..e6a7f08520a3f 100644 --- a/translations/client_ro.ts +++ b/translations/client_ro.ts @@ -1655,7 +1655,7 @@ Aceasta poate fi o problemă cu librariile OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5059,7 +5059,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 7daa48f4be218..51bc3698fb289 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -1659,7 +1659,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5083,7 +5083,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_sc.ts b/translations/client_sc.ts index 9fcedaa2bc339..b33f80e2cb4a1 100644 --- a/translations/client_sc.ts +++ b/translations/client_sc.ts @@ -1655,7 +1655,7 @@ Custu podet èssere un'errore de is librerias tuas OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5078,7 +5078,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_sk.ts b/translations/client_sk.ts index 2121f7a2e10d5..ec05eda92f56e 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -1655,7 +1655,7 @@ Môže to byť problém s knižnicami OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5081,7 +5081,7 @@ Server odpovedal chybou: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_sl.ts b/translations/client_sl.ts index 2e7f6c3bdcc0c..386feac3aae0a 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -1655,7 +1655,7 @@ Morda je napaka v knjužnicah OpenSSL. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5078,7 +5078,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_sr.ts b/translations/client_sr.ts index bb9828b344e8e..52a44dd1a73e8 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -1661,8 +1661,8 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. - Грешка приликом добављања id шифрованог фолдера. + Error fetching encrypted folder ID. + @@ -5087,8 +5087,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - Није успело добављање јавног кључа за корисника %1 + Could not fetch public key for user %1 + diff --git a/translations/client_sv.ts b/translations/client_sv.ts index c4431cbef4e2b..6b5c88ca5e07d 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -1661,8 +1661,8 @@ Det kan vara problem med dina OpenSSL-bibliotek. - Error fetching encrypted folder id. - Fel vid hämtning av krypterat mapp-ID. + Error fetching encrypted folder ID. + @@ -5087,8 +5087,8 @@ Servern svarade med fel: %2 - Could not fetch publicKey for user %1 - Kunde inte hämta publik nyckel för användare %1 + Could not fetch public key for user %1 + diff --git a/translations/client_th.ts b/translations/client_th.ts index 82cd53d04d6da..dbebd7d4fd69b 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -1653,7 +1653,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5053,7 +5053,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_tr.ts b/translations/client_tr.ts index 2bd75137ca7dc..c36e199b8efef 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -1661,7 +1661,7 @@ Bu durum OpenSLL kitaplıkları ile ilgili bir sorun olabilir. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5086,7 +5086,7 @@ Sunucunun verdiği hata yanıtı: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_uk.ts b/translations/client_uk.ts index 6730b014a6402..d96ef4fdd32f6 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -1661,7 +1661,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5087,7 +5087,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 151623c9ed288..bee4a5a5255c9 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -1655,7 +1655,7 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. + Error fetching encrypted folder ID. @@ -5072,7 +5072,7 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 + Could not fetch public key for user %1 diff --git a/translations/client_zh_HK.ts b/translations/client_zh_HK.ts index 2d9447240b99b..ec1b827610549 100644 --- a/translations/client_zh_HK.ts +++ b/translations/client_zh_HK.ts @@ -1663,8 +1663,8 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. - 錯誤擷取已加密資料夾ID。 + Error fetching encrypted folder ID. + @@ -5088,8 +5088,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - 無法擷取用戶 %1 的公共金鑰 + Could not fetch public key for user %1 + diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index a9a466ca5f842..83dff7c16e631 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -1661,8 +1661,8 @@ This can be an issue with your OpenSSL libraries. - Error fetching encrypted folder id. - 擷取加密資料夾 ID 時發生錯誤。 + Error fetching encrypted folder ID. + @@ -5087,8 +5087,8 @@ Server replied with error: %2 - Could not fetch publicKey for user %1 - 無法擷取使用者 %1 的公開金鑰 + Could not fetch public key for user %1 + From a81e81843a3629d73c64814760d3606b3c9d7314 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Thu, 1 Feb 2024 17:49:36 +0100 Subject: [PATCH 16/37] Modernize networkReplyErrorString by using const auto. Signed-off-by: Camila Ayres Signed-off-by: Uwe Runtemund --- src/libsync/abstractnetworkjob.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 5623c81259385..64ad59bade736 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -457,16 +457,22 @@ QString errorMessage(const QString &baseError, const QByteArray &body) QString networkReplyErrorString(const QNetworkReply &reply) { - QString base = reply.errorString(); - int httpStatus = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - QString httpReason = reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); + const auto base = reply.errorString(); + const auto httpStatus = reply.attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + const auto httpReason = reply.attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString(); // Only adjust HTTP error messages of the expected format. if (httpReason.isEmpty() || httpStatus == 0 || !base.contains(httpReason)) { return base; } - return AbstractNetworkJob::tr(R"(Server replied "%1 %2" to "%3 %4")").arg(QString::number(httpStatus), httpReason, HttpLogger::requestVerb(reply), reply.request().url().toDisplayString()); + const auto displayString = reply.request().url().toDisplayString(); + const auto requestVerb = HttpLogger::requestVerb(reply); + + return AbstractNetworkJob::tr(R"(Server replied "%1 %2" to "%3 %4")").arg(QString::number(httpStatus), + httpReason, + requestVerb, + displayString); } void AbstractNetworkJob::retry() From b947b8a2057955d5edf1facd449e7d99e1ad8714 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Fri, 2 Feb 2024 11:31:21 +0100 Subject: [PATCH 17/37] Modernize extractErrorMessage and AbstractNetworkJob::errorStringParsingBody. - Add const auto. - Remove extra error message information to make it more user friendly. Signed-off-by: Camila Ayres Signed-off-by: Uwe Runtemund --- src/libsync/abstractnetworkjob.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index 64ad59bade736..73640273b2480 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -323,20 +323,20 @@ QString AbstractNetworkJob::errorString() const QString AbstractNetworkJob::errorStringParsingBody(QByteArray *body) { - QString base = errorString(); + const auto base = errorString(); if (base.isEmpty() || !reply()) { return QString(); } - QByteArray replyBody = reply()->readAll(); + const auto replyBody = reply()->readAll(); if (body) { *body = replyBody; } - QString extra = extractErrorMessage(replyBody); + const auto extra = extractErrorMessage(replyBody); // Don't append the XML error message to a OC-ErrorString message. if (!extra.isEmpty() && !reply()->hasRawHeader("OC-ErrorString")) { - return QString::fromLatin1("%1 (%2)").arg(base, extra); + return extra; } return base; @@ -416,7 +416,7 @@ QString extractErrorMessage(const QByteArray &errorResponse) while (!reader.atEnd() && !reader.hasError()) { reader.readNextStartElement(); if (reader.name() == QLatin1String("message")) { - QString message = reader.readElementText(); + const auto message = reader.readElementText(); if (!message.isEmpty()) { return message; } From f09cef77af2263c343d65f945942c18a40ff1114 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Fri, 2 Feb 2024 11:45:47 +0100 Subject: [PATCH 18/37] Update 'no connection' error text to a more user friendly message. Signed-off-by: Camila Ayres Signed-off-by: Uwe Runtemund --- src/gui/accountsettings.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index c7d85648c945b..8387c25c0bdca 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -1105,10 +1105,10 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er _ui->connectLabel->setStyleSheet({}); } else { errors.prepend(message); - auto msg = errors.join(QLatin1String("\n")); - qCDebug(lcAccountSettings) << msg; - Theme::replaceLinkColorString(msg, QColor("#c1c8e6")); - _ui->connectLabel->setText(msg); + auto userFriendlyMsg = errors.join(QLatin1String("
")); + qCDebug(lcAccountSettings) << userFriendlyMsg; + Theme::replaceLinkColorString(userFriendlyMsg, QColor("#c1c8e6")); + _ui->connectLabel->setText(userFriendlyMsg); _ui->connectLabel->setToolTip({}); _ui->connectLabel->setStyleSheet(errStyle); } @@ -1276,9 +1276,9 @@ void AccountSettings::slotAccountStateChanged() break; } case AccountState::NetworkError: - showConnectionLabel(tr("No connection to %1 at %2.") - .arg(Utility::escape(Theme::instance()->appNameGUI()), server), - _accountState->connectionErrors()); + showConnectionLabel(tr("Unable to connect to %1.") + .arg(Utility::escape(Theme::instance()->appNameGUI())), + _accountState->connectionErrors()); break; case AccountState::ConfigurationError: showConnectionLabel(tr("Server configuration error: %1 at %2.") From 05a5c2b2740c753b07229789d3a412c47a197397 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Fri, 2 Feb 2024 12:46:31 +0100 Subject: [PATCH 19/37] Show systray error message when there is a network error. Signed-off-by: Camila Ayres Signed-off-by: Uwe Runtemund --- src/gui/connectionvalidator.cpp | 9 +++++++++ src/gui/connectionvalidator.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index f9b149787ed01..f59d28b3383a6 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -27,6 +27,7 @@ #include "networkjobs.h" #include "clientproxy.h" #include +#include "systray.h" namespace OCC { @@ -328,7 +329,15 @@ void ConnectionValidator::reportConnected() { void ConnectionValidator::reportResult(Status status) { emit connectionResult(status, _errors); + showSystrayErrorMessage(); deleteLater(); } +void ConnectionValidator::showSystrayErrorMessage() +{ + Systray::instance()->showMessage(tr("Network Error"), + _errors.join("
"), + QSystemTrayIcon::Warning); +} + } // namespace OCC diff --git a/src/gui/connectionvalidator.h b/src/gui/connectionvalidator.h index bd955d4128e67..22f8ce686e1b1 100644 --- a/src/gui/connectionvalidator.h +++ b/src/gui/connectionvalidator.h @@ -146,6 +146,8 @@ protected slots: AccountStatePtr _accountState; AccountPtr _account; bool _isCheckingServerAndAuth = false; + + void showSystrayErrorMessage(); }; } From d9cd1a491ad872126b0cfff8d98817e4abe3f6e8 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Thu, 8 Feb 2024 02:37:23 +0000 Subject: [PATCH 20/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_ar.ts | 14 +++- translations/client_bg.ts | 10 +++ translations/client_br.ts | 10 +++ translations/client_ca.ts | 10 +++ translations/client_cs.ts | 10 +++ translations/client_da.ts | 10 +++ translations/client_de.ts | 14 +++- translations/client_el.ts | 10 +++ translations/client_en_GB.ts | 10 +++ translations/client_eo.ts | 10 +++ translations/client_es.ts | 10 +++ translations/client_es_CL.ts | 10 +++ translations/client_es_CO.ts | 10 +++ translations/client_es_CR.ts | 10 +++ translations/client_es_DO.ts | 10 +++ translations/client_es_EC.ts | 10 +++ translations/client_es_GT.ts | 10 +++ translations/client_es_HN.ts | 10 +++ translations/client_es_MX.ts | 10 +++ translations/client_es_SV.ts | 10 +++ translations/client_et.ts | 10 +++ translations/client_eu.ts | 10 +++ translations/client_fa.ts | 10 +++ translations/client_fi.ts | 10 +++ translations/client_fr.ts | 10 +++ translations/client_gl.ts | 10 +++ translations/client_he.ts | 10 +++ translations/client_hr.ts | 10 +++ translations/client_hu.ts | 10 +++ translations/client_is.ts | 10 +++ translations/client_it.ts | 10 +++ translations/client_ja.ts | 10 +++ translations/client_ko.ts | 10 +++ translations/client_lt_LT.ts | 10 +++ translations/client_lv.ts | 10 +++ translations/client_mk.ts | 10 +++ translations/client_nb_NO.ts | 131 +++++++++++++++++++---------------- translations/client_nl.ts | 10 +++ translations/client_oc.ts | 10 +++ translations/client_pl.ts | 10 +++ translations/client_pt.ts | 10 +++ translations/client_pt_BR.ts | 10 +++ translations/client_ro.ts | 10 +++ translations/client_ru.ts | 10 +++ translations/client_sc.ts | 10 +++ translations/client_sk.ts | 10 +++ translations/client_sl.ts | 10 +++ translations/client_sr.ts | 14 +++- translations/client_sv.ts | 14 +++- translations/client_th.ts | 10 +++ translations/client_tr.ts | 10 +++ translations/client_uk.ts | 10 +++ translations/client_zh_CN.ts | 10 +++ translations/client_zh_HK.ts | 10 +++ translations/client_zh_TW.ts | 10 +++ 55 files changed, 620 insertions(+), 67 deletions(-) diff --git a/translations/client_ar.ts b/translations/client_ar.ts index 6082171cf9906..bab6214821d97 100644 --- a/translations/client_ar.ts +++ b/translations/client_ar.ts @@ -753,6 +753,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. لا يوجد اتصال مع %1 فى %2.
+ + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1446,6 +1451,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. يرجى التحديث إلى الإصدار الأخير من الخادوم ثم إعادة تشغيل العميل. + + + Network Error + +
OCC::DiscoveryPhase @@ -1656,7 +1666,7 @@ This can be an issue with your OpenSSL libraries. Error fetching encrypted folder ID. - + خطأ أثناء جلب مُعرِّف المجلد المُشفّر. @@ -5066,7 +5076,7 @@ Server replied with error: %2 Could not fetch public key for user %1 - + تعذّر جلب المفتاح العمومي للمستخدِم %1 diff --git a/translations/client_bg.ts b/translations/client_bg.ts index ac1d327ef3dd8..c002ff80d76ec 100644 --- a/translations/client_bg.ts +++ b/translations/client_bg.ts @@ -754,6 +754,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Не може да се осъществи връзка като %1 с %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1446,6 +1451,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Моля, обновете до по-нов сървър и рестартирайте клиента + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_br.ts b/translations/client_br.ts index e7cba20486153..91f52d0230aa2 100644 --- a/translations/client_br.ts +++ b/translations/client_br.ts @@ -749,6 +749,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Kestagadenn ebet da %1 da %2 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1439,6 +1444,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Adnevesit ar servijouar divezhañ ha adloc'hit ar c'hliant. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_ca.ts b/translations/client_ca.ts index c47e04e5a96cc..0eae68be8bf33 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -754,6 +754,11 @@ Aquesta acció anul·larà qualsevol sincronització en execució. No connection to %1 at %2. No hi ha connexió a %1 a %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1446,6 +1451,11 @@ Aquesta acció anul·larà qualsevol sincronització en execució. Please update to the latest server and restart the client. Actualitzeu el servidor a la versió més recent i reinicieu el client. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 316fa4142215c..9b0450adb8afb 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -757,6 +757,11 @@ Současně tato akce zruší jakoukoli právě probíhající synchronizaci.No connection to %1 at %2. Bez připojení k %1 na %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1450,6 +1455,11 @@ Současně tato akce zruší jakoukoli právě probíhající synchronizaci.Please update to the latest server and restart the client. Aktualizujte server na nejnovější verzi a pak klienta restartujte. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_da.ts b/translations/client_da.ts index 70b35b26a6174..17baa616f5d01 100644 --- a/translations/client_da.ts +++ b/translations/client_da.ts @@ -753,6 +753,11 @@ Denne handling vil annullere alle i øjeblikket kørende synkroniseringer.No connection to %1 at %2. Ingen forbindelse til %1 hos %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1443,6 +1448,11 @@ Denne handling vil annullere alle i øjeblikket kørende synkroniseringer.Please update to the latest server and restart the client. Venligst opdater til den nyeste server og genstart klienten. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_de.ts b/translations/client_de.ts index 9d1e3e13b1bc2..812f7d005e266 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -757,6 +757,11 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. No connection to %1 at %2. Keine Verbindung zu %1 auf %2 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1450,6 +1455,11 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. Please update to the latest server and restart the client. Aktualisieren Sie auf die neueste Server-Version und starten Sie den Client neu. + + + Network Error + + OCC::DiscoveryPhase @@ -1661,7 +1671,7 @@ Dies kann ein Problem mit Ihren OpenSSL-Bibliotheken sein. Error fetching encrypted folder ID. - + Fehler beim Abrufen der verschlüsselten Ordner-ID. @@ -5087,7 +5097,7 @@ Server antwortete mit Fehler: %2 Could not fetch public key for user %1 - + Öffentlicher Schlüssel für den Benutzer %1 konnte nicht abgerufen werden diff --git a/translations/client_el.ts b/translations/client_el.ts index dad13d738b6e7..77148343d3640 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -749,6 +749,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Δεν υπάρχει σύνδεση με το %1 στο %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1441,6 +1446,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Παρακαλώ ενημερώστε το διακομιστή στη νεώτερη έκδοση και επανεκκινήστε το δέκτη. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index 4b5d77f4e3a56..f2a7551eb6c41 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -758,6 +758,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No connection to %1 at %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Please update to the latest server and restart the client. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_eo.ts b/translations/client_eo.ts index 59c9f42fc7567..b5a97738406de 100644 --- a/translations/client_eo.ts +++ b/translations/client_eo.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Neniu konekto al servilo %1 je la adreso %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Bv. ĝisdatigi la servilon, kaj remalfermi la klienton. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es.ts b/translations/client_es.ts index 145478d4e111e..274a6d002311b 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -759,6 +759,11 @@ Además, esta acción interrumpirá cualquier sincronización en curso.No connection to %1 at %2. Sin conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1452,6 +1457,11 @@ Además, esta acción interrumpirá cualquier sincronización en curso.Please update to the latest server and restart the client. Por favor, actualice a la última versión del servidor y reinicie el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_CL.ts b/translations/client_es_CL.ts index 62e57cd1e449c..322cbafb5f616 100644 --- a/translations/client_es_CL.ts +++ b/translations/client_es_CL.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_CO.ts b/translations/client_es_CO.ts index d746d9302d81a..674bdd922019f 100644 --- a/translations/client_es_CO.ts +++ b/translations/client_es_CO.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_CR.ts b/translations/client_es_CR.ts index 5e6876acbc893..2b5940ea07dd9 100644 --- a/translations/client_es_CR.ts +++ b/translations/client_es_CR.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_DO.ts b/translations/client_es_DO.ts index f3914b140b785..4625a6976e97c 100644 --- a/translations/client_es_DO.ts +++ b/translations/client_es_DO.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_EC.ts b/translations/client_es_EC.ts index f429fbd7b55e6..ea85310c073cc 100644 --- a/translations/client_es_EC.ts +++ b/translations/client_es_EC.ts @@ -755,6 +755,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1446,6 +1451,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_GT.ts b/translations/client_es_GT.ts index a626b79b19da1..59e2b02bda472 100644 --- a/translations/client_es_GT.ts +++ b/translations/client_es_GT.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_HN.ts b/translations/client_es_HN.ts index 44368059a4d96..f9a70fa2379ae 100644 --- a/translations/client_es_HN.ts +++ b/translations/client_es_HN.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_MX.ts b/translations/client_es_MX.ts index 50703d278a279..103e959ca199c 100644 --- a/translations/client_es_MX.ts +++ b/translations/client_es_MX.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_es_SV.ts b/translations/client_es_SV.ts index 581a98346a0c8..2157e4288fe6d 100644 --- a/translations/client_es_SV.ts +++ b/translations/client_es_SV.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. No hay conexión a %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_et.ts b/translations/client_et.ts index 42b4e47950f68..299661d7b3a8c 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Palun uuenda server viimasele versioonile ning taaskäivita klient. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_eu.ts b/translations/client_eu.ts index e4c57b50e548f..515952ccb09ef 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -758,6 +758,11 @@ Ekintza honek unean uneko sinkronizazioa bertan behera utziko du. No connection to %1 at %2. Konexiorik ez %1-ekin %2-en + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Ekintza honek unean uneko sinkronizazioa bertan behera utziko du. Please update to the latest server and restart the client. Mesedez eguneratu zerbitzarira eta berrabiarazi bezeroa. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_fa.ts b/translations/client_fa.ts index 97b90856713fa..a39ef581a3032 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -755,6 +755,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. اتصال به 1% در 2% وجود ندارد. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1447,6 +1452,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. لطفا به آخرین سرور به روز رسانی کنید و مشتری را مجددا راه اندازی نمایید. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 660fa5a9bf24e..fb3364c020653 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -752,6 +752,11 @@ Tämä toiminto peruu kaikki tämänhetkiset synkronoinnit. No connection to %1 at %2. Ei yhteyttä kohteeseen %1 osoitteessa %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1444,6 +1449,11 @@ Tämä toiminto peruu kaikki tämänhetkiset synkronoinnit. Please update to the latest server and restart the client. Päivitä uusimpaan palvelinversioon ja käynnistä asiakasohjelmisto uudelleen. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_fr.ts b/translations/client_fr.ts index f9829e0b8ae11..442ec641ccbf7 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -756,6 +756,11 @@ Vous prenez vos propres risques. No connection to %1 at %2. Aucune connexion au serveur %1 à l'adresse %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1449,6 +1454,11 @@ Vous prenez vos propres risques. Please update to the latest server and restart the client. Veuillez mettre à jour le serveur vers la dernière version et redémarrer le client. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_gl.ts b/translations/client_gl.ts index 033cbdfd2ecf0..091799b9d6436 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -758,6 +758,11 @@ Esta acción interromperá calquera sincronización que estea a executarse actua No connection to %1 at %2. Non hai conexión con %1 en %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Esta acción interromperá calquera sincronización que estea a executarse actua Please update to the latest server and restart the client. Actualice ao último servidor e reinicie o cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_he.ts b/translations/client_he.ts index e3af198fac2ad..28c169d393825 100644 --- a/translations/client_he.ts +++ b/translations/client_he.ts @@ -749,6 +749,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. אין חיבור אל %1 ב־%2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1441,6 +1446,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. נא לעדכן לגרסה החדשה ביותר של השרת ולהפעיל מחדש את הלקוח. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_hr.ts b/translations/client_hr.ts index 3c25692c8fd77..56a7849a8d934 100644 --- a/translations/client_hr.ts +++ b/translations/client_hr.ts @@ -753,6 +753,11 @@ Ova će radnja prekinuti bilo koju trenutačnu sinkronizaciju. No connection to %1 at %2. Ne postoji veza s %1 na %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ Ova će radnja prekinuti bilo koju trenutačnu sinkronizaciju. Please update to the latest server and restart the client. Ažurirajte na najnoviji poslužitelj i ponovno pokrenite klijenta. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 47c56c0e9fc9b..5f836cdb3dfbf 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -755,6 +755,11 @@ Ez a művelet megszakítja a jelenleg futó szinkronizálást. No connection to %1 at %2. Nincs kapcsolat ehhez: %1, itt: %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1447,6 +1452,11 @@ Ez a művelet megszakítja a jelenleg futó szinkronizálást. Please update to the latest server and restart the client. Frissítse a kiszolgálót a legfrissebb verzióra, és indítsa újra a klienst. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_is.ts b/translations/client_is.ts index 700d4819aba9e..8c565b3bfd47a 100644 --- a/translations/client_is.ts +++ b/translations/client_is.ts @@ -754,6 +754,11 @@ skráakerfið eða sameignarmöppur, gætu verið með önnur takmörk.No connection to %1 at %2. Engin tenging við %1 á %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1448,6 +1453,11 @@ gagnageymslur: Please update to the latest server and restart the client. Uppfærðu í nýjasta þjóninn og endurræstu forritið. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_it.ts b/translations/client_it.ts index 5425e3a6d03bf..5bfaf7a166628 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -756,6 +756,11 @@ Questa azione interromperà qualsiasi sincronizzazione attualmente in esecuzione No connection to %1 at %2. Nessuna connessione a %1 su %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1449,6 +1454,11 @@ Questa azione interromperà qualsiasi sincronizzazione attualmente in esecuzione Please update to the latest server and restart the client. Aggiorna all'ultima versione del server e riavvia il client. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_ja.ts b/translations/client_ja.ts index add938ae8dbac..148011ea2ff6c 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -753,6 +753,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. %2 の %1 への接続がありません。 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. サーバーを最新にアップデートして、クライアントを再起動してください。 + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_ko.ts b/translations/client_ko.ts index 74db615467b86..2b628bdf35734 100644 --- a/translations/client_ko.ts +++ b/translations/client_ko.ts @@ -757,6 +757,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. %1와 %2에 연결이 없습니다. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1450,6 +1455,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. 최신 서버로 업데이트 후 클라이언트를 다시 시작해주십시오. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_lt_LT.ts b/translations/client_lt_LT.ts index 55025b58d284b..785f8f11c6474 100644 --- a/translations/client_lt_LT.ts +++ b/translations/client_lt_LT.ts @@ -749,6 +749,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. %2 neturi ryšio su %1. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1441,6 +1446,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Prašome atnaujinkite serverį iki naujausios versijos ir perkraukite klientą. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_lv.ts b/translations/client_lv.ts index 100e3c7f15cd8..80448df68c238 100644 --- a/translations/client_lv.ts +++ b/translations/client_lv.ts @@ -758,6 +758,11 @@ Vienīgā priekšrocība, izslēdzot virtuālo datņu atbalstu, ir tas, ka atkal No connection to %1 at %2. Nav savienojuma ar %1 pie %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Vienīgā priekšrocība, izslēdzot virtuālo datņu atbalstu, ir tas, ka atkal Please update to the latest server and restart the client. Lūdzu atjaunini uz jaunāko servera versiju un atkārtoti palaid klientu. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_mk.ts b/translations/client_mk.ts index c3eb103163c28..027dc9fa98e3a 100644 --- a/translations/client_mk.ts +++ b/translations/client_mk.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Нема врска со %1 на %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1440,6 +1445,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Ве молиме ажурирајте ја верзијата на серверот и рестатирајте го клиентот. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index 7989403a08f6b..94988f457b060 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -353,13 +353,15 @@ Bør en kontoimport forsøkes? %1 accounts were detected from a legacy desktop client. Should the accounts be imported? - + %1 kontoer ble oppdaget fra en eldre skrivebordsklient. +Skal kontoene importeres? 1 account was detected from a legacy desktop client. Should the account be imported? - + 1 konto ble oppdaget fra en eldre skrivebordsklient. +Skal kontoen importeres? @@ -371,12 +373,12 @@ Should the account be imported? Import - + Importer Skip - + Hopp over @@ -756,6 +758,11 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. No connection to %1 at %2. Ingen tilkobling til %1 på %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -982,35 +989,36 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. %1 accounts number of accounts imported - + %1 kontoer 1 account - + 1 konto %1 folders number of folders imported - + %1 mapper 1 folder - + 1 mappe Legacy import - + Eldre import Imported %1 and %2 from a legacy desktop client. %3 number of accounts and folders imported. list of users. - + Importerte %1 og %2 fra en eldre skrivebordsklient. +%3 @@ -1056,12 +1064,12 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. "%1 Failed to unlock encrypted folder %2". - + "%1 Opplåsing av kryptert mappe feilet %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Gal HTTP-kode returnert av serveren. Forventet 204, men fikk "%1 %2". @@ -1448,6 +1456,11 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. Please update to the latest server and restart the client. Vennligst oppdatert til den nyeste serveren og start klienten på nytt. + + + Network Error + + OCC::DiscoveryPhase @@ -1474,7 +1487,7 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. Encrypted metadata setup error! - + Feil ved oppsett av kryptert metadata! @@ -1647,29 +1660,29 @@ Dette kan være et problem med OpenSSL-bibliotekene dine. Error fetching metadata. - + Feil ved henting av metadata. Error locking folder. - + Feil ved låsing av mappe. Error fetching encrypted folder ID. - + Feil ved henting av kryptert mappe-ID. Error parsing or decrypting metadata. - + Feil ved analysering eller dekryptering av metadata. Failed to upload metadata - + Opplasting av metadata feilet @@ -1974,7 +1987,7 @@ Dette betyr at synkroniseringsklienten kanskje ikke laster opp lokale endringer Virtual file download failed with code "%1", status "%2" and error message "%3" - + Virtuell nedlasting av filen feilet med kode "%1", status "%2" og feilmelding "%3" @@ -2571,7 +2584,7 @@ Hvis dette var et uhell og du bestemmer deg for å beholde filene dine, vil de s Show sync folders in &Explorer's navigation pane - + Vis synkroniseringsmapper i &Utforskerens navigasjonsrute @@ -2597,7 +2610,7 @@ Hvis dette var et uhell og du bestemmer deg for å beholde filene dine, vil de s Info - + Info @@ -2607,12 +2620,12 @@ Hvis dette var et uhell og du bestemmer deg for å beholde filene dine, vil de s Desktop client x.x.x - + Skrivebordsklient x.x.x Update channel - + Oppdateringskanal @@ -2639,22 +2652,22 @@ Hvis dette var et uhell og du bestemmer deg for å beholde filene dine, vil de s &Automatically check for updates - + &se etter oppdateringer automatisk Check Now - + Sjekk nå Usage Documentation - + Bruksdokumentasjon Legal Notice - + Juridiske merknader @@ -2669,22 +2682,22 @@ Hvis dette var et uhell og du bestemmer deg for å beholde filene dine, vil de s Use &monochrome icons - + Bruk &monokrome ikoner &Launch on system startup - + &Start ved oppstart av systemet Show server &notifications - + Vis server &varslinger Show call notifications - + Vis anropsvarsler @@ -3108,7 +3121,7 @@ Merk at bruk av alle kommandolinjealternativer for logging vil overstyre denne i No proxy - + Ingen proxy @@ -3123,7 +3136,7 @@ Merk at bruk av alle kommandolinjealternativer for logging vil overstyre denne i Manually specify proxy - + Angi proxy manuelt @@ -4046,7 +4059,7 @@ Dette er en ny, eksperimentell modus. Hvis du bestemmer deg for å bruke den, ve Failed to encrypt a folder %1 - + Kryptering av en mappe feilet %1 @@ -4336,7 +4349,7 @@ Dette er en ny, eksperimentell modus. Hvis du bestemmer deg for å bruke den, ve Error - + Feil @@ -4369,7 +4382,7 @@ Dette er en ny, eksperimentell modus. Hvis du bestemmer deg for å bruke den, ve Could not find local folder for %1 - + Kunne ikke finne lokal mappe for %1 @@ -4890,7 +4903,7 @@ Server svarte med feil: %2 Preparing sync - + Forbereder synkronisering @@ -4994,7 +5007,7 @@ Server svarte med feil: %2 <p>%1 Desktop Client</p><p>Version %1. For more information please click <a href='%2'>here</a>.</p> Example text: "<p>Nextcloud Desktop Client</p>" (%1 is the application name) - + <p>%1Skrivebordsklient</p><p>Versjon %1. For mer informasjon, vennligst klikk <a href='%2'>her</a>.</p> @@ -5011,7 +5024,7 @@ Server svarte med feil: %2 <p>%1 desktop client %2</p> Example text: "<p>Nextcloud Desktop Client</p>" (%1 is the application name) - + <p>%1 skrivebordsklient %2</p> @@ -5053,17 +5066,17 @@ Server svarte med feil: %2 Failed to update folder metadata. - + Oppdatering av metadata for mappe feilet Failed to unlock encrypted folder. - + Opplåsing av kryptert mappe feilet. Failed to finalize item. - + Fullføring av element feilet. @@ -5079,27 +5092,27 @@ Server svarte med feil: %2 Error updating metadata for a folder %1 - + Feil under oppdatering av metadata for en mappe %1 Could not fetch public key for user %1 - + Kunne ikke hente offentlig nøkkel for bruker %1 Could not find root encrypted folder for folder %1 - + Kunne ikke finne den rotkrypterte mappen for mappen %1 Could not add or remove a folder user %1, for folder %2 - + Kan ikke legge til eller fjerne en mappebruker %1, for mappe %2 Failed to unlock a folder. - + Opplåsing av en mappe feilet. @@ -5249,37 +5262,37 @@ Server svarte med feil: %2 Download error - + Nedlastingsfeil Error downloading - + Feil under nedlasting could not be downloaded - + kunne ikke lastes ned > More details - + > Flere detaljer More details - + Flere detaljer Error downloading %1 - + Feil under nedlasting %1 %1 could not be downloaded. - + %1 kunne ikke lastes ned. @@ -5866,7 +5879,7 @@ Server svarte med feil: %2 Sharing is not available for this folder - + Deling av denne mappen er ikke tilgjengelig @@ -6122,7 +6135,7 @@ Server svarte med feil: %2 %L1 TB - + %L1 TB @@ -6260,7 +6273,7 @@ Server svarte med feil: %2 Open local or group folders - + Åpne lokale mapper eller gruppemapper @@ -6285,7 +6298,7 @@ Server svarte med feil: %2 New activities - + Nye aktiviteter @@ -6306,7 +6319,7 @@ Server svarte med feil: %2 <p><small>Built from Git revision <a href="%1">%2</a> on %3, %4 using Qt %5, %6</small></p> - + <p><small>Bygget fra Git-revisjon <a href="%1">%2</a> den %3, %4 med Qt %5, %6</small></p> diff --git a/translations/client_nl.ts b/translations/client_nl.ts index ddaf8eff525dc..074c46299e6e6 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -752,6 +752,11 @@ Dit zal alle synchronisaties, die op dit moment bezig zijn, afbreken.No connection to %1 at %2. Geen verbinding met %1 op %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1444,6 +1449,11 @@ Dit zal alle synchronisaties, die op dit moment bezig zijn, afbreken.Please update to the latest server and restart the client. Werk de server bij naar de nieuwste versie en herstart het programma. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_oc.ts b/translations/client_oc.ts index bfde815213250..91a8313fec575 100644 --- a/translations/client_oc.ts +++ b/translations/client_oc.ts @@ -748,6 +748,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Cap de connexion a %1 a %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1438,6 +1443,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Mercés de metre a nivèl al darrièr servidor e reaviatz lo client. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 9357d0597b6aa..46ec9912eae3a 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -758,6 +758,11 @@ Ta czynność spowoduje przerwanie aktualnie uruchomionej synchronizacji.No connection to %1 at %2. Brak połączenia do %1 z %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Ta czynność spowoduje przerwanie aktualnie uruchomionej synchronizacji.Please update to the latest server and restart the client. Zaktualizuj serwer do najnowszej wersji i zrestartuj klienta. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 6dc3d3072c792..176179057a10d 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -749,6 +749,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Sem ligação para %1 em %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1439,6 +1444,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Por favor, atualize para a ultima versão do servidor e reinicie o cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index 613337b338da9..a1d86696e7355 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -755,6 +755,11 @@ Esta ação irá cancelar qualquer sincronização atualmente em execução.No connection to %1 at %2. Sem conexão para %1 em %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1447,6 +1452,11 @@ Esta ação irá cancelar qualquer sincronização atualmente em execução.Please update to the latest server and restart the client. Por favor, atualize para a última versão e reinicie o cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_ro.ts b/translations/client_ro.ts index e6a7f08520a3f..395c67683fe5a 100644 --- a/translations/client_ro.ts +++ b/translations/client_ro.ts @@ -753,6 +753,11 @@ Această acțiune va opri toate sincronizările în derulare din acest moment.No connection to %1 at %2. Nu există nici-o conexiune către %1 la %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ Această acțiune va opri toate sincronizările în derulare din acest moment.Please update to the latest server and restart the client. Vă rugăm să instalați ultima versiune a serverului și să reporniți clientul + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 51bc3698fb289..9d80ae5921ae5 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -756,6 +756,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Нет соединения с %1 в %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1449,6 +1454,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Обновите сервер до последней версии и перезапустите клиент. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_sc.ts b/translations/client_sc.ts index b33f80e2cb4a1..fb0f85607f643 100644 --- a/translations/client_sc.ts +++ b/translations/client_sc.ts @@ -753,6 +753,11 @@ Custa atzione at a firmare cale si siat sincronizatzione immoe in esecutzione.No connection to %1 at %2. Peruna connessione a %1 in %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ Custa atzione at a firmare cale si siat sincronizatzione immoe in esecutzione.Please update to the latest server and restart the client. Pro praghere agiorna su serbidore a sa versione noa e torra a aviare su cliente. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_sk.ts b/translations/client_sk.ts index ec05eda92f56e..38e8dca1e90ac 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -753,6 +753,11 @@ Táto akcia zruší všetky prebiehajúce synchronizácie. No connection to %1 at %2. Žiadne pripojenie k %1 na %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ Táto akcia zruší všetky prebiehajúce synchronizácie. Please update to the latest server and restart the client. Prosím aktualizujte na najnovšiu verziu servera a reštartujte klienta. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_sl.ts b/translations/client_sl.ts index 386feac3aae0a..3e3ed7b64b6a6 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -753,6 +753,11 @@ S tem dejanjem prav tako prekinete vsa trenutna usklajevanja v izvajanju.No connection to %1 at %2. S strežnikom %1 ni vzpostavljene povezave (%2). + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ S tem dejanjem prav tako prekinete vsa trenutna usklajevanja v izvajanju.Please update to the latest server and restart the client. Posodobite strežnik in ponovno zaženite odjemalca. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_sr.ts b/translations/client_sr.ts index 52a44dd1a73e8..68d0708d8a068 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -758,6 +758,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Нема конекције на %1 са %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Ажурирајте сервер и поново покрените клијента. + + + Network Error + + OCC::DiscoveryPhase @@ -1662,7 +1672,7 @@ This can be an issue with your OpenSSL libraries. Error fetching encrypted folder ID. - + Грешка приликом добављања ID шифрованог фолдера. @@ -5088,7 +5098,7 @@ Server replied with error: %2 Could not fetch public key for user %1 - + Није успело добављање јавног кључа за корисника %1 diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 6b5c88ca5e07d..8e42b41d4d489 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -758,6 +758,11 @@ Den här åtgärden avbryter alla synkroniseringar som körs. No connection to %1 at %2. Ingen anslutning till %1 vid %2. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Den här åtgärden avbryter alla synkroniseringar som körs. Please update to the latest server and restart the client. Vänligen uppdatera till den senaste servern och starta om klienten. + + + Network Error + + OCC::DiscoveryPhase @@ -1662,7 +1672,7 @@ Det kan vara problem med dina OpenSSL-bibliotek. Error fetching encrypted folder ID. - + Fel vid hämtning av krypterat mapp-ID. @@ -5088,7 +5098,7 @@ Servern svarade med fel: %2 Could not fetch public key for user %1 - + Kunde inte hämta publik nyckel för användare %1 diff --git a/translations/client_th.ts b/translations/client_th.ts index dbebd7d4fd69b..2a1fea17ca347 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -752,6 +752,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. ไม่มีการเชื่อมต่อไปยัง %1 ที่ %2 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1444,6 +1449,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. กรุณาอัปเดตเซิร์ฟเวอร์เป็นรุ่นใหม่ล่าสุดและเริ่มต้นไคลเอ็นต์ใหม่ + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_tr.ts b/translations/client_tr.ts index c36e199b8efef..c404ac9c4ce70 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -758,6 +758,11 @@ Bu işlem şu anda yürütülmekte olan eşitleme işlemlerini durdurur.No connection to %1 at %2. %1 ile %2 üzerinde bağlantı yok. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ Bu işlem şu anda yürütülmekte olan eşitleme işlemlerini durdurur.Please update to the latest server and restart the client. Lütfen sunucuyu en son sürüme güncelleyin ve istemciyi yeniden başlatın. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_uk.ts b/translations/client_uk.ts index d96ef4fdd32f6..67657dbba5c14 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -758,6 +758,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. Відсутнє з'єднання між %2 та %1. + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. Будь ласка, оновіть сервер до останньої версії та перезавантажте клієнт. + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index bee4a5a5255c9..3bdf9f2600a07 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -752,6 +752,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. 没有到位于%2中的%1的连接 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1445,6 +1450,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. 请更新到最新的服务器版本然后重启客户端。 + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_zh_HK.ts b/translations/client_zh_HK.ts index ec1b827610549..34b99d5c3a86e 100644 --- a/translations/client_zh_HK.ts +++ b/translations/client_zh_HK.ts @@ -759,6 +759,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. %1 沒有連線到 %2 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1454,6 +1459,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. 請將伺服器端更新到最新版並重新啟動客戶端 + + + Network Error + + OCC::DiscoveryPhase diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 83dff7c16e631..765f928dec09d 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -758,6 +758,11 @@ This action will abort any currently running synchronization. No connection to %1 at %2. 在 %2 沒有連線到 %1。 + + + Unable to connect to %1. + + Server configuration error: %1 at %2. @@ -1451,6 +1456,11 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. 請將伺服器端更新到最新版並重新啟動客戶端。 + + + Network Error + + OCC::DiscoveryPhase From aecc92cdc963da5bb4e583cc07536fc6d7dedfe2 Mon Sep 17 00:00:00 2001 From: Camila Ayres Date: Wed, 7 Feb 2024 19:43:35 +0100 Subject: [PATCH 21/37] Do not create systray notification if there are no errors. Signed-off-by: Camila Ayres Signed-off-by: Uwe Runtemund --- src/gui/connectionvalidator.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/connectionvalidator.cpp b/src/gui/connectionvalidator.cpp index f59d28b3383a6..0be46b9f9160d 100644 --- a/src/gui/connectionvalidator.cpp +++ b/src/gui/connectionvalidator.cpp @@ -329,7 +329,12 @@ void ConnectionValidator::reportConnected() { void ConnectionValidator::reportResult(Status status) { emit connectionResult(status, _errors); - showSystrayErrorMessage(); + + // notify user of errors + if (!_errors.isEmpty()) { + showSystrayErrorMessage(); + } + deleteLater(); } From 60878065b4da3a5a0a8a1ec52f3a2a100f230eb1 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Fri, 9 Feb 2024 02:37:02 +0000 Subject: [PATCH 22/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_ar.ts | 6 ++--- translations/client_bg.ts | 2 +- translations/client_br.ts | 2 +- translations/client_ca.ts | 2 +- translations/client_cs.ts | 2 +- translations/client_da.ts | 2 +- translations/client_de.ts | 6 ++--- translations/client_el.ts | 2 +- translations/client_en_GB.ts | 10 ++++---- translations/client_eo.ts | 2 +- translations/client_es.ts | 2 +- translations/client_es_CL.ts | 2 +- translations/client_es_CO.ts | 2 +- translations/client_es_CR.ts | 2 +- translations/client_es_DO.ts | 2 +- translations/client_es_EC.ts | 2 +- translations/client_es_GT.ts | 2 +- translations/client_es_HN.ts | 2 +- translations/client_es_MX.ts | 2 +- translations/client_es_SV.ts | 2 +- translations/client_et.ts | 2 +- translations/client_eu.ts | 2 +- translations/client_fa.ts | 2 +- translations/client_fi.ts | 2 +- translations/client_fr.ts | 6 ++--- translations/client_gl.ts | 2 +- translations/client_he.ts | 2 +- translations/client_hr.ts | 2 +- translations/client_hu.ts | 2 +- translations/client_is.ts | 2 +- translations/client_it.ts | 2 +- translations/client_ja.ts | 2 +- translations/client_ko.ts | 2 +- translations/client_lt_LT.ts | 2 +- translations/client_lv.ts | 2 +- translations/client_mk.ts | 2 +- translations/client_nb_NO.ts | 2 +- translations/client_nl.ts | 2 +- translations/client_oc.ts | 2 +- translations/client_pl.ts | 2 +- translations/client_pt.ts | 2 +- translations/client_pt_BR.ts | 2 +- translations/client_ro.ts | 2 +- translations/client_ru.ts | 2 +- translations/client_sc.ts | 2 +- translations/client_sk.ts | 2 +- translations/client_sl.ts | 2 +- translations/client_sr.ts | 6 ++--- translations/client_sv.ts | 2 +- translations/client_th.ts | 2 +- translations/client_tr.ts | 2 +- translations/client_uk.ts | 50 ++++++++++++++++++------------------ translations/client_zh_CN.ts | 2 +- translations/client_zh_HK.ts | 10 ++++---- translations/client_zh_TW.ts | 2 +- 55 files changed, 95 insertions(+), 95 deletions(-) diff --git a/translations/client_ar.ts b/translations/client_ar.ts index bab6214821d97..4bffdf05d988f 100644 --- a/translations/client_ar.ts +++ b/translations/client_ar.ts @@ -756,7 +756,7 @@ This action will abort any currently running synchronization. Unable to connect to %1. - + تعذّر الاتصال بـ %1. @@ -1452,9 +1452,9 @@ This action will abort any currently running synchronization. يرجى التحديث إلى الإصدار الأخير من الخادوم ثم إعادة تشغيل العميل. - + Network Error - + خطأ في الشبكة diff --git a/translations/client_bg.ts b/translations/client_bg.ts index c002ff80d76ec..2758b235356c5 100644 --- a/translations/client_bg.ts +++ b/translations/client_bg.ts @@ -1452,7 +1452,7 @@ This action will abort any currently running synchronization. Моля, обновете до по-нов сървър и рестартирайте клиента - + Network Error diff --git a/translations/client_br.ts b/translations/client_br.ts index 91f52d0230aa2..ed085b341ad13 100644 --- a/translations/client_br.ts +++ b/translations/client_br.ts @@ -1445,7 +1445,7 @@ This action will abort any currently running synchronization. Adnevesit ar servijouar divezhañ ha adloc'hit ar c'hliant. - + Network Error diff --git a/translations/client_ca.ts b/translations/client_ca.ts index 0eae68be8bf33..6a8327c9933f9 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -1452,7 +1452,7 @@ Aquesta acció anul·larà qualsevol sincronització en execució. Actualitzeu el servidor a la versió més recent i reinicieu el client. - + Network Error diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 9b0450adb8afb..837cf5ca8795e 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -1456,7 +1456,7 @@ Současně tato akce zruší jakoukoli právě probíhající synchronizaci.Aktualizujte server na nejnovější verzi a pak klienta restartujte. - + Network Error diff --git a/translations/client_da.ts b/translations/client_da.ts index 17baa616f5d01..f4e24da59ac19 100644 --- a/translations/client_da.ts +++ b/translations/client_da.ts @@ -1449,7 +1449,7 @@ Denne handling vil annullere alle i øjeblikket kørende synkroniseringer.Venligst opdater til den nyeste server og genstart klienten. - + Network Error diff --git a/translations/client_de.ts b/translations/client_de.ts index 812f7d005e266..59b4a0aed6eb1 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -760,7 +760,7 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. Unable to connect to %1. - + Verbindung zu %1 kann nicht hergestellt werden. @@ -1456,9 +1456,9 @@ Diese Aktion bricht jede derzeit laufende Synchronisierung ab. Aktualisieren Sie auf die neueste Server-Version und starten Sie den Client neu. - + Network Error - + Netzwerkfehler diff --git a/translations/client_el.ts b/translations/client_el.ts index 77148343d3640..a11021e4fe82a 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -1447,7 +1447,7 @@ This action will abort any currently running synchronization. Παρακαλώ ενημερώστε το διακομιστή στη νεώτερη έκδοση και επανεκκινήστε το δέκτη. - + Network Error diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index f2a7551eb6c41..41456a26b2c8f 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -761,7 +761,7 @@ This action will abort any currently running synchronization. Unable to connect to %1. - + Unable to connect to %1. @@ -1457,9 +1457,9 @@ This action will abort any currently running synchronization. Please update to the latest server and restart the client. - + Network Error - + Network Error @@ -1672,7 +1672,7 @@ This can be an issue with your OpenSSL libraries. Error fetching encrypted folder ID. - + Error fetching encrypted folder ID. @@ -5098,7 +5098,7 @@ Server replied with error: %2 Could not fetch public key for user %1 - + Could not fetch public key for user %1 diff --git a/translations/client_eo.ts b/translations/client_eo.ts index b5a97738406de..52eb4f26c2602 100644 --- a/translations/client_eo.ts +++ b/translations/client_eo.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Bv. ĝisdatigi la servilon, kaj remalfermi la klienton. - + Network Error diff --git a/translations/client_es.ts b/translations/client_es.ts index 274a6d002311b..4e5dd26c2f8cb 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -1458,7 +1458,7 @@ Además, esta acción interrumpirá cualquier sincronización en curso.Por favor, actualice a la última versión del servidor y reinicie el cliente. - + Network Error diff --git a/translations/client_es_CL.ts b/translations/client_es_CL.ts index 322cbafb5f616..7d88f6fe85064 100644 --- a/translations/client_es_CL.ts +++ b/translations/client_es_CL.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_CO.ts b/translations/client_es_CO.ts index 674bdd922019f..8c438f409f4b5 100644 --- a/translations/client_es_CO.ts +++ b/translations/client_es_CO.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_CR.ts b/translations/client_es_CR.ts index 2b5940ea07dd9..b3698984e78ef 100644 --- a/translations/client_es_CR.ts +++ b/translations/client_es_CR.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_DO.ts b/translations/client_es_DO.ts index 4625a6976e97c..7202b8d57b5e8 100644 --- a/translations/client_es_DO.ts +++ b/translations/client_es_DO.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_EC.ts b/translations/client_es_EC.ts index ea85310c073cc..a65ee55fd19b1 100644 --- a/translations/client_es_EC.ts +++ b/translations/client_es_EC.ts @@ -1452,7 +1452,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_GT.ts b/translations/client_es_GT.ts index 59e2b02bda472..a3426eecb262f 100644 --- a/translations/client_es_GT.ts +++ b/translations/client_es_GT.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_HN.ts b/translations/client_es_HN.ts index f9a70fa2379ae..1060950991fd5 100644 --- a/translations/client_es_HN.ts +++ b/translations/client_es_HN.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_MX.ts b/translations/client_es_MX.ts index 103e959ca199c..5d666a2769fa1 100644 --- a/translations/client_es_MX.ts +++ b/translations/client_es_MX.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_es_SV.ts b/translations/client_es_SV.ts index 2157e4288fe6d..44cc8ca56ad6e 100644 --- a/translations/client_es_SV.ts +++ b/translations/client_es_SV.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Por favor actualiza a la versión más reciente del servidor y reinicia el cliente. - + Network Error diff --git a/translations/client_et.ts b/translations/client_et.ts index 299661d7b3a8c..2041081dec5e5 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Palun uuenda server viimasele versioonile ning taaskäivita klient. - + Network Error diff --git a/translations/client_eu.ts b/translations/client_eu.ts index 515952ccb09ef..f8dee2910c31d 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -1457,7 +1457,7 @@ Ekintza honek unean uneko sinkronizazioa bertan behera utziko du. Mesedez eguneratu zerbitzarira eta berrabiarazi bezeroa. - + Network Error diff --git a/translations/client_fa.ts b/translations/client_fa.ts index a39ef581a3032..21d2a3df11f09 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -1453,7 +1453,7 @@ This action will abort any currently running synchronization. لطفا به آخرین سرور به روز رسانی کنید و مشتری را مجددا راه اندازی نمایید. - + Network Error diff --git a/translations/client_fi.ts b/translations/client_fi.ts index fb3364c020653..8bc69d39dced4 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -1450,7 +1450,7 @@ Tämä toiminto peruu kaikki tämänhetkiset synkronoinnit. Päivitä uusimpaan palvelinversioon ja käynnistä asiakasohjelmisto uudelleen. - + Network Error diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 442ec641ccbf7..76938a68ae947 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -759,7 +759,7 @@ Vous prenez vos propres risques. Unable to connect to %1. - + Impossible de se connecter à %1. @@ -1455,9 +1455,9 @@ Vous prenez vos propres risques. Veuillez mettre à jour le serveur vers la dernière version et redémarrer le client. - + Network Error - + Erreur réseau diff --git a/translations/client_gl.ts b/translations/client_gl.ts index 091799b9d6436..007c3e1e1c8bf 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -1457,7 +1457,7 @@ Esta acción interromperá calquera sincronización que estea a executarse actua Actualice ao último servidor e reinicie o cliente. - + Network Error diff --git a/translations/client_he.ts b/translations/client_he.ts index 28c169d393825..f0ca450939cb4 100644 --- a/translations/client_he.ts +++ b/translations/client_he.ts @@ -1447,7 +1447,7 @@ This action will abort any currently running synchronization. נא לעדכן לגרסה החדשה ביותר של השרת ולהפעיל מחדש את הלקוח. - + Network Error diff --git a/translations/client_hr.ts b/translations/client_hr.ts index 56a7849a8d934..1af28a5ba099e 100644 --- a/translations/client_hr.ts +++ b/translations/client_hr.ts @@ -1451,7 +1451,7 @@ Ova će radnja prekinuti bilo koju trenutačnu sinkronizaciju. Ažurirajte na najnoviji poslužitelj i ponovno pokrenite klijenta. - + Network Error diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 5f836cdb3dfbf..923987a12cb7b 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -1453,7 +1453,7 @@ Ez a művelet megszakítja a jelenleg futó szinkronizálást. Frissítse a kiszolgálót a legfrissebb verzióra, és indítsa újra a klienst. - + Network Error diff --git a/translations/client_is.ts b/translations/client_is.ts index 8c565b3bfd47a..fd10eb789854c 100644 --- a/translations/client_is.ts +++ b/translations/client_is.ts @@ -1454,7 +1454,7 @@ gagnageymslur: Uppfærðu í nýjasta þjóninn og endurræstu forritið. - + Network Error diff --git a/translations/client_it.ts b/translations/client_it.ts index 5bfaf7a166628..e780928dc448f 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -1455,7 +1455,7 @@ Questa azione interromperà qualsiasi sincronizzazione attualmente in esecuzione Aggiorna all'ultima versione del server e riavvia il client. - + Network Error diff --git a/translations/client_ja.ts b/translations/client_ja.ts index 148011ea2ff6c..c8181fec429e5 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -1451,7 +1451,7 @@ This action will abort any currently running synchronization. サーバーを最新にアップデートして、クライアントを再起動してください。 - + Network Error diff --git a/translations/client_ko.ts b/translations/client_ko.ts index 2b628bdf35734..32f6daf8dc5c5 100644 --- a/translations/client_ko.ts +++ b/translations/client_ko.ts @@ -1456,7 +1456,7 @@ This action will abort any currently running synchronization. 최신 서버로 업데이트 후 클라이언트를 다시 시작해주십시오. - + Network Error diff --git a/translations/client_lt_LT.ts b/translations/client_lt_LT.ts index 785f8f11c6474..c5b40dbc0222b 100644 --- a/translations/client_lt_LT.ts +++ b/translations/client_lt_LT.ts @@ -1447,7 +1447,7 @@ This action will abort any currently running synchronization. Prašome atnaujinkite serverį iki naujausios versijos ir perkraukite klientą. - + Network Error diff --git a/translations/client_lv.ts b/translations/client_lv.ts index 80448df68c238..397b4657955e2 100644 --- a/translations/client_lv.ts +++ b/translations/client_lv.ts @@ -1457,7 +1457,7 @@ Vienīgā priekšrocība, izslēdzot virtuālo datņu atbalstu, ir tas, ka atkal Lūdzu atjaunini uz jaunāko servera versiju un atkārtoti palaid klientu. - + Network Error diff --git a/translations/client_mk.ts b/translations/client_mk.ts index 027dc9fa98e3a..fdbcb7144fc4b 100644 --- a/translations/client_mk.ts +++ b/translations/client_mk.ts @@ -1446,7 +1446,7 @@ This action will abort any currently running synchronization. Ве молиме ажурирајте ја верзијата на серверот и рестатирајте го клиентот. - + Network Error diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index 94988f457b060..0bdec85605f42 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -1457,7 +1457,7 @@ Denne handlingen vil avbryte enhver synkronisering som kjører. Vennligst oppdatert til den nyeste serveren og start klienten på nytt. - + Network Error diff --git a/translations/client_nl.ts b/translations/client_nl.ts index 074c46299e6e6..decd444c84bf2 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -1450,7 +1450,7 @@ Dit zal alle synchronisaties, die op dit moment bezig zijn, afbreken.Werk de server bij naar de nieuwste versie en herstart het programma. - + Network Error diff --git a/translations/client_oc.ts b/translations/client_oc.ts index 91a8313fec575..84b3313b5612a 100644 --- a/translations/client_oc.ts +++ b/translations/client_oc.ts @@ -1444,7 +1444,7 @@ This action will abort any currently running synchronization. Mercés de metre a nivèl al darrièr servidor e reaviatz lo client. - + Network Error diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 46ec9912eae3a..7ec80174282e8 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -1457,7 +1457,7 @@ Ta czynność spowoduje przerwanie aktualnie uruchomionej synchronizacji.Zaktualizuj serwer do najnowszej wersji i zrestartuj klienta. - + Network Error diff --git a/translations/client_pt.ts b/translations/client_pt.ts index 176179057a10d..aa20931f83975 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -1445,7 +1445,7 @@ This action will abort any currently running synchronization. Por favor, atualize para a ultima versão do servidor e reinicie o cliente. - + Network Error diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index a1d86696e7355..803891f7300ae 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -1453,7 +1453,7 @@ Esta ação irá cancelar qualquer sincronização atualmente em execução.Por favor, atualize para a última versão e reinicie o cliente. - + Network Error diff --git a/translations/client_ro.ts b/translations/client_ro.ts index 395c67683fe5a..9c594b9ac7e4c 100644 --- a/translations/client_ro.ts +++ b/translations/client_ro.ts @@ -1451,7 +1451,7 @@ Această acțiune va opri toate sincronizările în derulare din acest moment.Vă rugăm să instalați ultima versiune a serverului și să reporniți clientul - + Network Error diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 9d80ae5921ae5..325eed229ed36 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -1455,7 +1455,7 @@ This action will abort any currently running synchronization. Обновите сервер до последней версии и перезапустите клиент. - + Network Error diff --git a/translations/client_sc.ts b/translations/client_sc.ts index fb0f85607f643..f9668018645e3 100644 --- a/translations/client_sc.ts +++ b/translations/client_sc.ts @@ -1451,7 +1451,7 @@ Custa atzione at a firmare cale si siat sincronizatzione immoe in esecutzione.Pro praghere agiorna su serbidore a sa versione noa e torra a aviare su cliente. - + Network Error diff --git a/translations/client_sk.ts b/translations/client_sk.ts index 38e8dca1e90ac..6dd9a453aa43a 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -1451,7 +1451,7 @@ Táto akcia zruší všetky prebiehajúce synchronizácie. Prosím aktualizujte na najnovšiu verziu servera a reštartujte klienta. - + Network Error diff --git a/translations/client_sl.ts b/translations/client_sl.ts index 3e3ed7b64b6a6..f1c214d0c7196 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -1451,7 +1451,7 @@ S tem dejanjem prav tako prekinete vsa trenutna usklajevanja v izvajanju.Posodobite strežnik in ponovno zaženite odjemalca. - + Network Error diff --git a/translations/client_sr.ts b/translations/client_sr.ts index 68d0708d8a068..eba15601ca0f6 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -761,7 +761,7 @@ This action will abort any currently running synchronization. Unable to connect to %1. - + Није успело повезивање са %1. @@ -1457,9 +1457,9 @@ This action will abort any currently running synchronization. Ажурирајте сервер и поново покрените клијента. - + Network Error - + Мрежна грешка diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 8e42b41d4d489..f077ef8c9c374 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -1457,7 +1457,7 @@ Den här åtgärden avbryter alla synkroniseringar som körs. Vänligen uppdatera till den senaste servern och starta om klienten. - + Network Error diff --git a/translations/client_th.ts b/translations/client_th.ts index 2a1fea17ca347..684c9dbc82ae5 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -1450,7 +1450,7 @@ This action will abort any currently running synchronization. กรุณาอัปเดตเซิร์ฟเวอร์เป็นรุ่นใหม่ล่าสุดและเริ่มต้นไคลเอ็นต์ใหม่ - + Network Error diff --git a/translations/client_tr.ts b/translations/client_tr.ts index c404ac9c4ce70..9a328a61401fa 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -1457,7 +1457,7 @@ Bu işlem şu anda yürütülmekte olan eşitleme işlemlerini durdurur.Lütfen sunucuyu en son sürüme güncelleyin ve istemciyi yeniden başlatın. - + Network Error diff --git a/translations/client_uk.ts b/translations/client_uk.ts index 67657dbba5c14..a4e500bcff0ed 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -761,7 +761,7 @@ This action will abort any currently running synchronization. Unable to connect to %1. - + Не вдалося з'єднатися із %1. @@ -1064,12 +1064,12 @@ This action will abort any currently running synchronization. "%1 Failed to unlock encrypted folder %2". - + "%1 Не вдалося розблокувати зашифрований каталог %2". Wrong HTTP code returned by server. Expected 204, but received "%1 %2". - + Сервер повернув хибний код HTTP. Очікувалося 204, проте отримано "%1 %2". @@ -1457,9 +1457,9 @@ This action will abort any currently running synchronization. Будь ласка, оновіть сервер до останньої версії та перезавантажте клієнт. - + Network Error - + Помилка мережі @@ -1487,7 +1487,7 @@ This action will abort any currently running synchronization. Encrypted metadata setup error! - + Помилка з налаштуванням шифрування метаданих! @@ -1660,29 +1660,29 @@ This can be an issue with your OpenSSL libraries. Error fetching metadata. - + Помилка з отриманням метаданих. Error locking folder. - + Помилка з блокуванням каталогу. Error fetching encrypted folder ID. - + Помилка з отриманням ідентифікатора зашифрованого каталогу. Error parsing or decrypting metadata. - + Помилка з опрацюванням або розшифровуванням метаданих. Failed to upload metadata - + Не вдалося завантажити метадані @@ -3571,7 +3571,7 @@ Note that using any logging command line options will override this setting. Connection to %1 could not be established. Please check again. - Підключення до %1 встановити не вдалося. Будь ласка, перевірте ще раз. + Не вдалося встановити з'єднання із %1. Будь ласка, перевірте ще раз. @@ -4060,7 +4060,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Failed to encrypt a folder %1 - + Не вдалося зашифрувати каталог %1 @@ -4350,7 +4350,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Error - + Помилка @@ -4383,7 +4383,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Could not find local folder for %1 - + Не вдалося знайти каталог на пристрої для %1 @@ -5067,17 +5067,17 @@ Server replied with error: %2 Failed to update folder metadata. - + Не вдалося оновити метадані каталогу. Failed to unlock encrypted folder. - + Не вдалося розблокувати зашифрований каталог. Failed to finalize item. - + Не вдалося фіналізувати ресурс. @@ -5093,27 +5093,27 @@ Server replied with error: %2 Error updating metadata for a folder %1 - + Помилка під час оновлення метаданих для каталогу %1 Could not fetch public key for user %1 - + Не вдалося отримати публічний ключ для користувача %1 Could not find root encrypted folder for folder %1 - + Не вдалося знайти кореневий зашифрований каталог для каталогу %1 Could not add or remove a folder user %1, for folder %2 - + Не вдалося створити або вилучити каталог користувача %1, для каталогу %2 Failed to unlock a folder. - + Не вдалося розблокувати каталог. @@ -5880,7 +5880,7 @@ Server replied with error: %2 Sharing is not available for this folder - + Для цього каталогу недоступне надання у спільний доступ @@ -6136,7 +6136,7 @@ Server replied with error: %2 %L1 TB - + %L1 ТБ diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 3bdf9f2600a07..7e647cc463535 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -1451,7 +1451,7 @@ This action will abort any currently running synchronization. 请更新到最新的服务器版本然后重启客户端。 - + Network Error diff --git a/translations/client_zh_HK.ts b/translations/client_zh_HK.ts index 34b99d5c3a86e..ab83056c7b00c 100644 --- a/translations/client_zh_HK.ts +++ b/translations/client_zh_HK.ts @@ -762,7 +762,7 @@ This action will abort any currently running synchronization. Unable to connect to %1. - + 無法連接至 %1。 @@ -1460,9 +1460,9 @@ This action will abort any currently running synchronization. 請將伺服器端更新到最新版並重新啟動客戶端 - + Network Error - + 網絡錯誤 @@ -1674,7 +1674,7 @@ This can be an issue with your OpenSSL libraries. Error fetching encrypted folder ID. - + 錯誤擷取已加密資料夾ID。 @@ -5099,7 +5099,7 @@ Server replied with error: %2 Could not fetch public key for user %1 - + 無法擷取用戶 %1 的公鑰 diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 765f928dec09d..7ec9c9408a584 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -1457,7 +1457,7 @@ This action will abort any currently running synchronization. 請將伺服器端更新到最新版並重新啟動客戶端。 - + Network Error From 42c8d7499d832425cb171b668e808a4773766eba Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Sun, 11 Feb 2024 02:39:01 +0000 Subject: [PATCH 23/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_sv.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/client_sv.ts b/translations/client_sv.ts index f077ef8c9c374..26ef85891ce21 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -761,7 +761,7 @@ Den här åtgärden avbryter alla synkroniseringar som körs. Unable to connect to %1. - + Kan inte ansluta till %1. @@ -1459,7 +1459,7 @@ Den här åtgärden avbryter alla synkroniseringar som körs. Network Error - + Nätverksfel From a370f4b34e8edbb70401596f52fc55fe44ef18a2 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Mon, 12 Feb 2024 02:36:05 +0000 Subject: [PATCH 24/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_sc.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translations/client_sc.ts b/translations/client_sc.ts index f9668018645e3..91c45fd0675a5 100644 --- a/translations/client_sc.ts +++ b/translations/client_sc.ts @@ -1549,7 +1549,7 @@ Custa atzione at a firmare cale si siat sincronizatzione immoe in esecutzione. Could not start editing locally. - + Impossìbile aviare s'editzione in locale. @@ -5780,7 +5780,7 @@ Server replied with error: %2 View only - + Isceti in visualizatzione From f3090e6c0e177a98c649486d75ea812ebbccf66a Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Tue, 13 Feb 2024 02:38:02 +0000 Subject: [PATCH 25/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_fr.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 76938a68ae947..49fbbdaa5eb69 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -1665,12 +1665,12 @@ Cela peut être un problème avec vos bibliothèques OpenSSL. Error locking folder. - + Erreur de verrouillage du dossier. Error fetching encrypted folder ID. - + Erreur dans la récupération de l'ID du dossier chiffré. @@ -5070,7 +5070,7 @@ Le serveur a répondu avec l'erreur : %2 Failed to unlock encrypted folder. - + Échec du déverrouillage du dossier chiffré. @@ -5111,7 +5111,7 @@ Le serveur a répondu avec l'erreur : %2 Failed to unlock a folder. - + Échec du déverrouillage d'un dossier. From 8ae927a73ced5a549397bd23d3ff6013e51f3a20 Mon Sep 17 00:00:00 2001 From: Josh Richards Date: Mon, 20 Nov 2023 11:45:26 -0500 Subject: [PATCH 26/37] docs(conffile) Update chunk sizes to match v2 chunking PR Signed-off-by: Josh Richards Signed-off-by: Uwe Runtemund --- doc/conffile.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conffile.rst b/doc/conffile.rst index 670cc4d607540..058826eeaea47 100644 --- a/doc/conffile.rst +++ b/doc/conffile.rst @@ -43,9 +43,9 @@ Some interesting values that can be set on the configuration file are: +----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+ | ``forceLoginV2`` | ``false`` | If the client should force the new login flow, eventhough some circumstances might need the old flow. | +----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+ -| ``minChunkSize`` | ``1000000`` (1 MB) | Specifies the minimum chunk size of uploaded files in bytes. | +| ``minChunkSize`` | ``5000000`` (5 MB) | Specifies the minimum chunk size of uploaded files in bytes. | +----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+ -| ``maxChunkSize`` | ``1000000000`` (1000 MB) | Specifies the maximum chunk size of uploaded files in bytes. | +| ``maxChunkSize`` | ``5000000000`` (5000 MB) | Specifies the maximum chunk size of uploaded files in bytes. | +----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+ | ``targetChunkUploadDuration`` | ``60000`` (1 minute) | Target duration in milliseconds for chunk uploads. | | | | The client adjusts the chunk size until each chunk upload takes approximately this long. | From 430bb5836433d25fc489e0005122bdee28d1c99f Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 4 Apr 2023 14:36:11 +0800 Subject: [PATCH 27/37] Properly report Undefined sync status when multiple folders syncing Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- src/gui/folderman.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 2361862fddc4c..1dd600a6279b3 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1681,6 +1681,8 @@ void FolderMan::trayOverallStatus(const QList &folders, *status = SyncResult::SyncRunning; } else if (goodSeen > 0) { *status = SyncResult::Success; + } else if (various > 0) { + *status = SyncResult::Undefined; } } } From d3d1ad7cec718b9e5a880a148dee68a5ca4763ea Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 4 Apr 2023 14:41:42 +0800 Subject: [PATCH 28/37] Use auto and const auto where possible in trayOverallStatus Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- src/gui/folderman.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 1dd600a6279b3..4bd027f96e12b 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1604,7 +1604,7 @@ void FolderMan::trayOverallStatus(const QList &folders, *status = SyncResult::Undefined; *unresolvedConflicts = false; - int cnt = folders.count(); + const auto cnt = folders.count(); // if one folder: show the state of the one folder. // if more folders: @@ -1613,7 +1613,7 @@ void FolderMan::trayOverallStatus(const QList &folders, // do not show "problem" in the tray // if (cnt == 1) { - Folder *folder = folders.at(0); + const auto folder = folders.at(0); if (folder) { auto syncResult = folder->syncResult(); if (folder->syncPaused()) { @@ -1635,17 +1635,18 @@ void FolderMan::trayOverallStatus(const QList &folders, *unresolvedConflicts = syncResult.hasUnresolvedConflicts(); } } else { - int errorsSeen = 0; - int goodSeen = 0; - int abortOrPausedSeen = 0; - int runSeen = 0; + auto errorsSeen = 0; + auto goodSeen = 0; + auto abortOrPausedSeen = 0; + auto runSeen = 0; + auto various = 0; for (const Folder *folder : qAsConst(folders)) { - SyncResult folderResult = folder->syncResult(); + const auto folderResult = folder->syncResult(); if (folder->syncPaused()) { abortOrPausedSeen++; } else { - SyncResult::Status syncStatus = folderResult.status(); + const auto syncStatus = folderResult.status(); switch (syncStatus) { case SyncResult::Undefined: From 001d77388c71db7fbbc4849db191feaf42c6a5f9 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 4 Apr 2023 14:42:18 +0800 Subject: [PATCH 29/37] Use bools rather than unnecessary ints in folderman trayoverallstatus Signed-off-by: Claudio Cambra f Signed-off-by: Uwe Runtemund --- src/gui/folderman.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 4bd027f96e12b..03314a205fe8a 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1635,54 +1635,55 @@ void FolderMan::trayOverallStatus(const QList &folders, *unresolvedConflicts = syncResult.hasUnresolvedConflicts(); } } else { - auto errorsSeen = 0; - auto goodSeen = 0; - auto abortOrPausedSeen = 0; - auto runSeen = 0; - auto various = 0; + auto errorsSeen = false; + auto goodSeen = false; + auto abortOrPausedSeen = false; + auto runSeen = false; + auto various = false; for (const Folder *folder : qAsConst(folders)) { const auto folderResult = folder->syncResult(); if (folder->syncPaused()) { - abortOrPausedSeen++; + abortOrPausedSeen = true; } else { const auto syncStatus = folderResult.status(); switch (syncStatus) { case SyncResult::Undefined: case SyncResult::NotYetStarted: + various = true; break; case SyncResult::SyncPrepare: case SyncResult::SyncRunning: - runSeen++; + runSeen = true; break; case SyncResult::Problem: // don't show the problem icon in tray. case SyncResult::Success: - goodSeen++; + goodSeen = true; break; case SyncResult::Error: case SyncResult::SetupError: - errorsSeen++; + errorsSeen = true; break; case SyncResult::SyncAbortRequested: case SyncResult::Paused: - abortOrPausedSeen++; + abortOrPausedSeen = true; // no default case on purpose, check compiler warnings } } if (folderResult.hasUnresolvedConflicts()) *unresolvedConflicts = true; } - if (errorsSeen > 0) { + if (errorsSeen) { *status = SyncResult::Error; - } else if (abortOrPausedSeen > 0 && abortOrPausedSeen == cnt) { + } else if (abortOrPausedSeen) { // only if all folders are paused *status = SyncResult::Paused; - } else if (runSeen > 0) { + } else if (runSeen) { *status = SyncResult::SyncRunning; - } else if (goodSeen > 0) { + } else if (goodSeen) { *status = SyncResult::Success; - } else if (various > 0) { + } else if (various) { *status = SyncResult::Undefined; } } From c7998a8341ad6a910b4e3a9a62e8c086f890eec5 Mon Sep 17 00:00:00 2001 From: Claudio Cambra Date: Tue, 4 Apr 2023 14:48:15 +0800 Subject: [PATCH 30/37] Do an early break of folder state check loop when errors seen Signed-off-by: Claudio Cambra Signed-off-by: Uwe Runtemund --- src/gui/folderman.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 03314a205fe8a..36aedb2b29464 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1642,7 +1642,14 @@ void FolderMan::trayOverallStatus(const QList &folders, auto various = false; for (const Folder *folder : qAsConst(folders)) { + // We've already seen an error, worst case met. + // No need to check the remaining folders. + if (errorsSeen) { + break; + } + const auto folderResult = folder->syncResult(); + if (folder->syncPaused()) { abortOrPausedSeen = true; } else { @@ -1671,9 +1678,12 @@ void FolderMan::trayOverallStatus(const QList &folders, // no default case on purpose, check compiler warnings } } - if (folderResult.hasUnresolvedConflicts()) + + if (folderResult.hasUnresolvedConflicts()) { *unresolvedConflicts = true; + } } + if (errorsSeen) { *status = SyncResult::Error; } else if (abortOrPausedSeen) { From c0cc3b2d637f9b95596a8ec7e2815cdf09e279b1 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 8 Feb 2024 09:36:58 +0100 Subject: [PATCH 31/37] fix binding loop with palette assignments Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- src/gui/filedetails/ShareDetailsPage.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/filedetails/ShareDetailsPage.qml b/src/gui/filedetails/ShareDetailsPage.qml index c09f42abd6dc2..15ffbe61a14b4 100644 --- a/src/gui/filedetails/ShareDetailsPage.qml +++ b/src/gui/filedetails/ShareDetailsPage.qml @@ -332,7 +332,7 @@ Page { mid: Style.darkerHover dark: Style.menuBorder button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox + window: Style.menuBorder base: Style.backgroundColor toolTipBase: Style.backgroundColor toolTipText: Style.ncTextColor @@ -549,7 +549,7 @@ Page { mid: Style.darkerHover dark: Style.menuBorder button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox + window: Style.menuBorder base: Style.backgroundColor toolTipBase: Style.backgroundColor toolTipText: Style.ncTextColor @@ -682,7 +682,7 @@ Page { mid: Style.darkerHover dark: Style.menuBorder button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox + window: Style.menuBorder base: Style.backgroundColor toolTipBase: Style.backgroundColor toolTipText: Style.ncTextColor @@ -790,7 +790,7 @@ Page { mid: Style.darkerHover dark: Style.menuBorder button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the checkbox + window: Style.menuBorder base: Style.backgroundColor toolTipBase: Style.backgroundColor toolTipText: Style.ncTextColor From 1ea0fc80e42a23211acc01e0c9604f1e60d888fd Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 8 Feb 2024 09:37:52 +0100 Subject: [PATCH 32/37] ensure role hideDownload from ShareModel has a boolean default value Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- src/gui/filedetails/sharemodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp index 0f65454ddbcf0..b09c8c81783c5 100644 --- a/src/gui/filedetails/sharemodel.cpp +++ b/src/gui/filedetails/sharemodel.cpp @@ -189,6 +189,7 @@ QVariant ShareModel::data(const QModelIndex &index, const int role) const // Deal with roles that only return certain values for link or user/group share types case NoteEnabledRole: case ExpireDateEnabledRole: + case HideDownloadEnabledRole: return false; case LinkRole: case LinkShareNameRole: From 72c2d03a3706f6fac4c572fcca8cd6a89e81ab37 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 8 Feb 2024 11:10:55 +0100 Subject: [PATCH 33/37] fix QML warnings about accessing undefined parent property we are defining a component, by definition there will be no parent setting the size if the responsibility of the stack view, not of the component (or even its definition) Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- src/gui/filedetails/ShareDelegate.qml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml index e25a23f9b0602..baf378fe8ce6f 100644 --- a/src/gui/filedetails/ShareDelegate.qml +++ b/src/gui/filedetails/ShareDelegate.qml @@ -262,8 +262,6 @@ GridLayout { ShareDetailsPage { id: shareDetailsPage - width: parent.width - height: parent.height backgroundsVisible: root.backgroundsVisible accentColor: root.accentColor From 638d32efa0df25c19098abcabc919ed402404240 Mon Sep 17 00:00:00 2001 From: Matthieu Gallien Date: Thu, 8 Feb 2024 11:57:09 +0100 Subject: [PATCH 34/37] fix more issues from qml in share dialog Signed-off-by: Matthieu Gallien Signed-off-by: Uwe Runtemund --- src/gui/filedetails/ShareeSearchField.qml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gui/filedetails/ShareeSearchField.qml b/src/gui/filedetails/ShareeSearchField.qml index 300b9c36004b5..4246d54e9ed8d 100644 --- a/src/gui/filedetails/ShareeSearchField.qml +++ b/src/gui/filedetails/ShareeSearchField.qml @@ -185,8 +185,7 @@ TextField { interactive: true highlight: Rectangle { - width: shareeListView.currentItem.width - height: shareeListView.currentItem.height + anchors.fill: shareeListView.currentItem color: palette.highlight } highlightFollowsCurrentItem: true @@ -200,8 +199,7 @@ TextField { model: root.shareeModel delegate: ShareeDelegate { - anchors.left: parent.left - anchors.right: parent.right + width: shareeListView.contentItem.width enabled: model.type !== Sharee.LookupServerSearchResults hoverEnabled: model.type !== Sharee.LookupServerSearchResults From 666a751abbc6f169e9227cd8a2e307c9083dbce5 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Wed, 14 Feb 2024 02:38:37 +0000 Subject: [PATCH 35/37] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot Signed-off-by: Uwe Runtemund --- translations/client_ar.ts | 2 +- translations/client_bg.ts | 2 +- translations/client_br.ts | 2 +- translations/client_ca.ts | 2 +- translations/client_cs.ts | 2 +- translations/client_da.ts | 2 +- translations/client_de.ts | 2 +- translations/client_el.ts | 2 +- translations/client_en_GB.ts | 2 +- translations/client_eo.ts | 2 +- translations/client_es.ts | 2 +- translations/client_es_CL.ts | 2 +- translations/client_es_CO.ts | 2 +- translations/client_es_CR.ts | 2 +- translations/client_es_DO.ts | 2 +- translations/client_es_EC.ts | 2 +- translations/client_es_GT.ts | 2 +- translations/client_es_HN.ts | 2 +- translations/client_es_MX.ts | 2 +- translations/client_es_SV.ts | 2 +- translations/client_et.ts | 2 +- translations/client_eu.ts | 2 +- translations/client_fa.ts | 2 +- translations/client_fi.ts | 2 +- translations/client_fr.ts | 2 +- translations/client_gl.ts | 2 +- translations/client_he.ts | 2 +- translations/client_hr.ts | 2 +- translations/client_hu.ts | 2 +- translations/client_is.ts | 2 +- translations/client_it.ts | 2 +- translations/client_ja.ts | 2 +- translations/client_ko.ts | 2 +- translations/client_lt_LT.ts | 2 +- translations/client_lv.ts | 2 +- translations/client_mk.ts | 2 +- translations/client_nb_NO.ts | 2 +- translations/client_nl.ts | 2 +- translations/client_oc.ts | 2 +- translations/client_pl.ts | 2 +- translations/client_pt.ts | 2 +- translations/client_pt_BR.ts | 2 +- translations/client_ro.ts | 2 +- translations/client_ru.ts | 2 +- translations/client_sc.ts | 2 +- translations/client_sk.ts | 2 +- translations/client_sl.ts | 2 +- translations/client_sr.ts | 2 +- translations/client_sv.ts | 2 +- translations/client_th.ts | 2 +- translations/client_tr.ts | 2 +- translations/client_uk.ts | 2 +- translations/client_zh_CN.ts | 2 +- translations/client_zh_HK.ts | 2 +- translations/client_zh_TW.ts | 2 +- 55 files changed, 55 insertions(+), 55 deletions(-) diff --git a/translations/client_ar.ts b/translations/client_ar.ts index 4bffdf05d988f..4f53ee9d43c24 100644 --- a/translations/client_ar.ts +++ b/translations/client_ar.ts @@ -4361,7 +4361,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss إفلات آمن للملف - + Could not find local folder for %1 تعذّر إيجاد المجلد المَحلّي لـ %1 diff --git a/translations/client_bg.ts b/translations/client_bg.ts index 2758b235356c5..2764d4e8fff5a 100644 --- a/translations/client_bg.ts +++ b/translations/client_bg.ts @@ -4375,7 +4375,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Защитено пускане на файлове - + Could not find local folder for %1 diff --git a/translations/client_br.ts b/translations/client_br.ts index ed085b341ad13..2bf72628cb4f4 100644 --- a/translations/client_br.ts +++ b/translations/client_br.ts @@ -4351,7 +4351,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_ca.ts b/translations/client_ca.ts index 6a8327c9933f9..7b5d68d8b7dd9 100644 --- a/translations/client_ca.ts +++ b/translations/client_ca.ts @@ -4352,7 +4352,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_cs.ts b/translations/client_cs.ts index 837cf5ca8795e..cf4dd1eb22081 100644 --- a/translations/client_cs.ts +++ b/translations/client_cs.ts @@ -4379,7 +4379,7 @@ Toto je nový, experimentální režim. Pokud se jej rozhodnete používat, pros Bezpečný příjem souboru - + Could not find local folder for %1 diff --git a/translations/client_da.ts b/translations/client_da.ts index f4e24da59ac19..94e90cfb097f5 100644 --- a/translations/client_da.ts +++ b/translations/client_da.ts @@ -4356,7 +4356,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_de.ts b/translations/client_de.ts index 59b4a0aed6eb1..14ba01e1ac692 100644 --- a/translations/client_de.ts +++ b/translations/client_de.ts @@ -4380,7 +4380,7 @@ Dies ist ein neuer, experimenteller Modus. Wenn Sie sich entscheiden, ihn zu ver Sichere Dateiablage - + Could not find local folder for %1 Lokaler Ordner für %1 nicht gefunden diff --git a/translations/client_el.ts b/translations/client_el.ts index a11021e4fe82a..ee676edea6997 100644 --- a/translations/client_el.ts +++ b/translations/client_el.ts @@ -4359,7 +4359,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_en_GB.ts b/translations/client_en_GB.ts index 41456a26b2c8f..f2bd4259ee7e7 100644 --- a/translations/client_en_GB.ts +++ b/translations/client_en_GB.ts @@ -4381,7 +4381,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Secure file drop - + Could not find local folder for %1 Could not find local folder for %1 diff --git a/translations/client_eo.ts b/translations/client_eo.ts index 52eb4f26c2602..1c3dab846fe56 100644 --- a/translations/client_eo.ts +++ b/translations/client_eo.ts @@ -4350,7 +4350,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es.ts b/translations/client_es.ts index 4e5dd26c2f8cb..c42dd456aba07 100644 --- a/translations/client_es.ts +++ b/translations/client_es.ts @@ -4383,7 +4383,7 @@ Esta es un modo nuevo y experimental. Si decides usarlo, por favor, informa de c Entrega de archivos segura - + Could not find local folder for %1 No se ha podido encontrar una carpeta local para %1 diff --git a/translations/client_es_CL.ts b/translations/client_es_CL.ts index 7d88f6fe85064..57ab3c18d8217 100644 --- a/translations/client_es_CL.ts +++ b/translations/client_es_CL.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_CO.ts b/translations/client_es_CO.ts index 8c438f409f4b5..97fc62251d305 100644 --- a/translations/client_es_CO.ts +++ b/translations/client_es_CO.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_CR.ts b/translations/client_es_CR.ts index b3698984e78ef..d739859eb5f0f 100644 --- a/translations/client_es_CR.ts +++ b/translations/client_es_CR.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_DO.ts b/translations/client_es_DO.ts index 7202b8d57b5e8..ac058a62e0161 100644 --- a/translations/client_es_DO.ts +++ b/translations/client_es_DO.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_EC.ts b/translations/client_es_EC.ts index a65ee55fd19b1..fd7c2923e84da 100644 --- a/translations/client_es_EC.ts +++ b/translations/client_es_EC.ts @@ -4375,7 +4375,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Envío seguro de archivos - + Could not find local folder for %1 diff --git a/translations/client_es_GT.ts b/translations/client_es_GT.ts index a3426eecb262f..be9362803a5df 100644 --- a/translations/client_es_GT.ts +++ b/translations/client_es_GT.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_HN.ts b/translations/client_es_HN.ts index 1060950991fd5..f67b1d36cbc94 100644 --- a/translations/client_es_HN.ts +++ b/translations/client_es_HN.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_MX.ts b/translations/client_es_MX.ts index 5d666a2769fa1..7a73b9d439bb2 100644 --- a/translations/client_es_MX.ts +++ b/translations/client_es_MX.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_es_SV.ts b/translations/client_es_SV.ts index 44cc8ca56ad6e..080ac833777c3 100644 --- a/translations/client_es_SV.ts +++ b/translations/client_es_SV.ts @@ -4342,7 +4342,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_et.ts b/translations/client_et.ts index 2041081dec5e5..6a9f29fc11814 100644 --- a/translations/client_et.ts +++ b/translations/client_et.ts @@ -4340,7 +4340,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_eu.ts b/translations/client_eu.ts index f8dee2910c31d..852221561ccf2 100644 --- a/translations/client_eu.ts +++ b/translations/client_eu.ts @@ -4381,7 +4381,7 @@ Modu hau berria eta experimentala da. Erabiltzea erabakitzen baduzu, agertzen di Fitxategi-jartze segurua - + Could not find local folder for %1 diff --git a/translations/client_fa.ts b/translations/client_fa.ts index 21d2a3df11f09..53b0374e0c98a 100644 --- a/translations/client_fa.ts +++ b/translations/client_fa.ts @@ -4377,7 +4377,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Secure file drop - + Could not find local folder for %1 diff --git a/translations/client_fi.ts b/translations/client_fi.ts index 8bc69d39dced4..5425cb9ed0ae8 100644 --- a/translations/client_fi.ts +++ b/translations/client_fi.ts @@ -4353,7 +4353,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_fr.ts b/translations/client_fr.ts index 49fbbdaa5eb69..201ba801387d9 100644 --- a/translations/client_fr.ts +++ b/translations/client_fr.ts @@ -4379,7 +4379,7 @@ Il s'agit d'un nouveau mode expérimental. Si vous décidez de l' Dépôt de fichier sécurisé - + Could not find local folder for %1 Impossible de trouver le dossier local pour %1 diff --git a/translations/client_gl.ts b/translations/client_gl.ts index 007c3e1e1c8bf..b0a40d410133d 100644 --- a/translations/client_gl.ts +++ b/translations/client_gl.ts @@ -4381,7 +4381,7 @@ Este é un novo modo experimental. Se decide usalo, agradecémoslle que informe Entrega segura de ficheiros - + Could not find local folder for %1 diff --git a/translations/client_he.ts b/translations/client_he.ts index f0ca450939cb4..c0d39422976a7 100644 --- a/translations/client_he.ts +++ b/translations/client_he.ts @@ -4347,7 +4347,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_hr.ts b/translations/client_hr.ts index 1af28a5ba099e..ae91326011624 100644 --- a/translations/client_hr.ts +++ b/translations/client_hr.ts @@ -4374,7 +4374,7 @@ Ovo je novi, eksperimentalni način rada. Ako se odlučite aktivirati ga, prijav - + Could not find local folder for %1 diff --git a/translations/client_hu.ts b/translations/client_hu.ts index 923987a12cb7b..64087e96c1f18 100644 --- a/translations/client_hu.ts +++ b/translations/client_hu.ts @@ -4377,7 +4377,7 @@ Ez egy új, kísérleti mód. Ha úgy dönt, hogy használja, akkor jelezze nek Biztonságos fájllerakat - + Could not find local folder for %1 diff --git a/translations/client_is.ts b/translations/client_is.ts index fd10eb789854c..6401fdf061c2a 100644 --- a/translations/client_is.ts +++ b/translations/client_is.ts @@ -4355,7 +4355,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_it.ts b/translations/client_it.ts index e780928dc448f..019bdd0bcde68 100644 --- a/translations/client_it.ts +++ b/translations/client_it.ts @@ -4370,7 +4370,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_ja.ts b/translations/client_ja.ts index c8181fec429e5..b1542268b7a0a 100644 --- a/translations/client_ja.ts +++ b/translations/client_ja.ts @@ -4373,7 +4373,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_ko.ts b/translations/client_ko.ts index 32f6daf8dc5c5..03125b28927ad 100644 --- a/translations/client_ko.ts +++ b/translations/client_ko.ts @@ -4382,7 +4382,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss 안전한 파일 드롭 - + Could not find local folder for %1 %1의 로컬 폴더를 찾을 수 없습니다. diff --git a/translations/client_lt_LT.ts b/translations/client_lt_LT.ts index c5b40dbc0222b..642ebf92a901d 100644 --- a/translations/client_lt_LT.ts +++ b/translations/client_lt_LT.ts @@ -4352,7 +4352,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_lv.ts b/translations/client_lv.ts index 397b4657955e2..bfcc2cb8d4ab3 100644 --- a/translations/client_lv.ts +++ b/translations/client_lv.ts @@ -4362,7 +4362,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_mk.ts b/translations/client_mk.ts index fdbcb7144fc4b..057ccdf60a3e4 100644 --- a/translations/client_mk.ts +++ b/translations/client_mk.ts @@ -4349,7 +4349,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_nb_NO.ts b/translations/client_nb_NO.ts index 0bdec85605f42..ffacee223df64 100644 --- a/translations/client_nb_NO.ts +++ b/translations/client_nb_NO.ts @@ -4380,7 +4380,7 @@ Dette er en ny, eksperimentell modus. Hvis du bestemmer deg for å bruke den, ve Sikker filslipp - + Could not find local folder for %1 Kunne ikke finne lokal mappe for %1 diff --git a/translations/client_nl.ts b/translations/client_nl.ts index decd444c84bf2..8a3fc3aa225aa 100644 --- a/translations/client_nl.ts +++ b/translations/client_nl.ts @@ -4373,7 +4373,7 @@ Dit is een nieuwe, experimentele modus. Als je besluit het te gebruiken, vragen - + Could not find local folder for %1 diff --git a/translations/client_oc.ts b/translations/client_oc.ts index 84b3313b5612a..485c0c17e3b98 100644 --- a/translations/client_oc.ts +++ b/translations/client_oc.ts @@ -4340,7 +4340,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_pl.ts b/translations/client_pl.ts index 7ec80174282e8..400d2120dbbdf 100644 --- a/translations/client_pl.ts +++ b/translations/client_pl.ts @@ -4381,7 +4381,7 @@ To nowy, eksperymentalny tryb. Jeśli zdecydujesz się z niego skorzystać, zgł Bezpieczne upuszczanie plików - + Could not find local folder for %1 diff --git a/translations/client_pt.ts b/translations/client_pt.ts index aa20931f83975..1a64fd772fa2a 100644 --- a/translations/client_pt.ts +++ b/translations/client_pt.ts @@ -4345,7 +4345,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_pt_BR.ts b/translations/client_pt_BR.ts index 803891f7300ae..42856396e8629 100644 --- a/translations/client_pt_BR.ts +++ b/translations/client_pt_BR.ts @@ -4375,7 +4375,7 @@ Este é um novo modo experimental. Se você decidir usá-lo, relate quaisquer pr Secure file drop - + Could not find local folder for %1 diff --git a/translations/client_ro.ts b/translations/client_ro.ts index 9c594b9ac7e4c..b37626938038d 100644 --- a/translations/client_ro.ts +++ b/translations/client_ro.ts @@ -4357,7 +4357,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_ru.ts b/translations/client_ru.ts index 325eed229ed36..badf079f25d56 100644 --- a/translations/client_ru.ts +++ b/translations/client_ru.ts @@ -4377,7 +4377,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Безопасное удаление файла - + Could not find local folder for %1 diff --git a/translations/client_sc.ts b/translations/client_sc.ts index 91c45fd0675a5..1a514f90d7538 100644 --- a/translations/client_sc.ts +++ b/translations/client_sc.ts @@ -4374,7 +4374,7 @@ Custa est una modalidade noa, isperimentale. Si detzides de dda impreare, sinnal - + Could not find local folder for %1 diff --git a/translations/client_sk.ts b/translations/client_sk.ts index 6dd9a453aa43a..9bbe6f94a89fc 100644 --- a/translations/client_sk.ts +++ b/translations/client_sk.ts @@ -4375,7 +4375,7 @@ Toto je nový experimentálny režim. Ak sa ho rozhodnete použiť, nahláste v - + Could not find local folder for %1 diff --git a/translations/client_sl.ts b/translations/client_sl.ts index f1c214d0c7196..51287de2dd3a9 100644 --- a/translations/client_sl.ts +++ b/translations/client_sl.ts @@ -4374,7 +4374,7 @@ To je nov preizkusni način. Če ga boste uporabili, pošljite tudi poročila o - + Could not find local folder for %1 diff --git a/translations/client_sr.ts b/translations/client_sr.ts index eba15601ca0f6..15c326634b0c2 100644 --- a/translations/client_sr.ts +++ b/translations/client_sr.ts @@ -4381,7 +4381,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Сигурно место за упуштање фајлова - + Could not find local folder for %1 Не може да се пронађе локални фолдер за %1 diff --git a/translations/client_sv.ts b/translations/client_sv.ts index 26ef85891ce21..db7043418c6ce 100644 --- a/translations/client_sv.ts +++ b/translations/client_sv.ts @@ -4381,7 +4381,7 @@ Detta är ett nytt experimentellt läge. Om du bestämmer dig för att använda Säker gömd fillista - + Could not find local folder for %1 Kunde inte hitta lokal mapp för %1 diff --git a/translations/client_th.ts b/translations/client_th.ts index 684c9dbc82ae5..03be97c295fa1 100644 --- a/translations/client_th.ts +++ b/translations/client_th.ts @@ -4350,7 +4350,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss - + Could not find local folder for %1 diff --git a/translations/client_tr.ts b/translations/client_tr.ts index 9a328a61401fa..482328c21c3e8 100644 --- a/translations/client_tr.ts +++ b/translations/client_tr.ts @@ -4380,7 +4380,7 @@ Bu yeni ve deneysel bir özelliktir. Kullanmaya karar verirseniz, lütfen karş Güvenli dosya bırakma - + Could not find local folder for %1 diff --git a/translations/client_uk.ts b/translations/client_uk.ts index a4e500bcff0ed..71ebaac866953 100644 --- a/translations/client_uk.ts +++ b/translations/client_uk.ts @@ -4381,7 +4381,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss Безпечне копіювання файлів - + Could not find local folder for %1 Не вдалося знайти каталог на пристрої для %1 diff --git a/translations/client_zh_CN.ts b/translations/client_zh_CN.ts index 7e647cc463535..fac76091314e5 100644 --- a/translations/client_zh_CN.ts +++ b/translations/client_zh_CN.ts @@ -4366,7 +4366,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss 安全文件拖放 - + Could not find local folder for %1 diff --git a/translations/client_zh_HK.ts b/translations/client_zh_HK.ts index ab83056c7b00c..e207810bedffa 100644 --- a/translations/client_zh_HK.ts +++ b/translations/client_zh_HK.ts @@ -4382,7 +4382,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss 安全檔案投放 - + Could not find local folder for %1 找不到 %1 的近端資料夾 diff --git a/translations/client_zh_TW.ts b/translations/client_zh_TW.ts index 7ec9c9408a584..bca7097485fb4 100644 --- a/translations/client_zh_TW.ts +++ b/translations/client_zh_TW.ts @@ -4381,7 +4381,7 @@ This is a new, experimental mode. If you decide to use it, please report any iss 安全檔案投放 - + Could not find local folder for %1 找不到 %1 的本機資料夾 From 8abe081cd73820940f2467dacb845661426c3acb Mon Sep 17 00:00:00 2001 From: alex-z Date: Sat, 10 Feb 2024 13:56:34 +0100 Subject: [PATCH 36/37] Do not upload modified PDF file while it is open in Kofax PowerPDF on Windows. Prevents signature verification failure. Signed-off-by: alex-z Signed-off-by: Uwe Runtemund --- src/common/utility.h | 12 ++++++ src/common/utility_mac.mm | 6 +++ src/common/utility_unix.cpp | 6 +++ src/common/utility_win.cpp | 68 +++++++++++++++++++++++++++++++++ src/csync/ConfigureChecks.cmake | 2 +- src/libsync/discovery.cpp | 56 +++++++++++++++++++++++++++ src/libsync/discovery.h | 2 + 7 files changed, 151 insertions(+), 1 deletion(-) diff --git a/src/common/utility.h b/src/common/utility.h index bd62a5d8c78ce..a76527c1caa81 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -50,6 +50,18 @@ Q_DECLARE_LOGGING_CATEGORY(lcUtility) * @{ */ namespace Utility { + struct ProcessInfosForOpenFile { + ulong processId; + QString processName; + }; + /** + * @brief Queries the OS for processes that are keeping the file open(using it) + * + * @param filePath absolute file path + * @return list of ProcessInfosForOpenFile + */ + OCSYNC_EXPORT QVector queryProcessInfosKeepingFileOpen(const QString &filePath); + OCSYNC_EXPORT int rand(); OCSYNC_EXPORT void sleep(int sec); OCSYNC_EXPORT void usleep(int usec); diff --git a/src/common/utility_mac.mm b/src/common/utility_mac.mm index 9d0f4f03d4979..b7e74c7130522 100644 --- a/src/common/utility_mac.mm +++ b/src/common/utility_mac.mm @@ -31,6 +31,12 @@ namespace OCC { +QVector Utility::queryProcessInfosKeepingFileOpen(const QString &filePath) +{ + Q_UNUSED(filePath) + return {}; +} + void Utility::setupFavLink(const QString &folder) { // Finder: Place under "Places"/"Favorites" on the left sidebar diff --git a/src/common/utility_unix.cpp b/src/common/utility_unix.cpp index 0b47ee78e13bc..091f16ab6548e 100644 --- a/src/common/utility_unix.cpp +++ b/src/common/utility_unix.cpp @@ -31,6 +31,12 @@ namespace OCC { +QVector Utility::queryProcessInfosKeepingFileOpen(const QString &filePath) +{ + Q_UNUSED(filePath) + return {}; +} + void Utility::setupFavLink(const QString &folder) { // Nautilus: add to ~/.gtk-bookmarks diff --git a/src/common/utility_win.cpp b/src/common/utility_win.cpp index 9852cde16b382..ba4d2ba859156 100644 --- a/src/common/utility_win.cpp +++ b/src/common/utility_win.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include #include @@ -43,6 +45,72 @@ static const char runPathC[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\C namespace OCC { +QVector Utility::queryProcessInfosKeepingFileOpen(const QString &filePath) +{ + QVector results; + + DWORD restartManagerSession = 0; + WCHAR restartManagerSessionKey[CCH_RM_SESSION_KEY + 1] = {0}; + auto errorStatus = RmStartSession(&restartManagerSession, 0, restartManagerSessionKey); + if (errorStatus != ERROR_SUCCESS) { + return results; + } + + LPCWSTR files[] = {reinterpret_cast(filePath.utf16())}; + errorStatus = RmRegisterResources(restartManagerSession, 1, files, 0, NULL, 0, NULL); + if (errorStatus != ERROR_SUCCESS) { + RmEndSession(restartManagerSession); + return results; + } + + DWORD rebootReasons = 0; + UINT rmProcessInfosNeededCount = 0; + std::vector rmProcessInfos; + auto rmProcessInfosRequestedCount = static_cast(rmProcessInfos.size()); + errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons); + + if (errorStatus == ERROR_MORE_DATA) { + rmProcessInfos.resize(rmProcessInfosNeededCount, {}); + rmProcessInfosRequestedCount = static_cast(rmProcessInfos.size()); + errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons); + } + + if (errorStatus != ERROR_SUCCESS || rmProcessInfos.empty()) { + RmEndSession(restartManagerSession); + return results; + } + + for (size_t i = 0; i < rmProcessInfos.size(); ++i) { + const auto processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rmProcessInfos[i].Process.dwProcessId); + if (!processHandle) { + continue; + } + + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (!GetProcessTimes(processHandle, &ftCreate, &ftExit, &ftKernel, &ftUser) + || CompareFileTime(&rmProcessInfos[i].Process.ProcessStartTime, &ftCreate) != 0) { + CloseHandle(processHandle); + continue; + } + + WCHAR processFullPath[MAX_PATH]; + DWORD processFullPathLength = MAX_PATH; + if (QueryFullProcessImageNameW(processHandle, 0, processFullPath, &processFullPathLength) && processFullPathLength <= MAX_PATH) { + const auto processFullPathString = QDir::fromNativeSeparators(QString::fromWCharArray(processFullPath)); + const QFileInfo fileInfoForProcess(processFullPathString); + const auto processName = fileInfoForProcess.fileName(); + if (!processName.isEmpty()) { + results.push_back(Utility::ProcessInfosForOpenFile{rmProcessInfos[i].Process.dwProcessId, processName}); + } + } + CloseHandle(processHandle); + } + RmEndSession(restartManagerSession); + + return results; +} + void Utility::setupFavLink(const QString &folder) { // First create a Desktop.ini so that the folder and favorite link show our application's icon. diff --git a/src/csync/ConfigureChecks.cmake b/src/csync/ConfigureChecks.cmake index 64bdf1770ee6f..bd1a74be11f02 100644 --- a/src/csync/ConfigureChecks.cmake +++ b/src/csync/ConfigureChecks.cmake @@ -31,7 +31,7 @@ if (NOT LINUX) endif (NOT LINUX) if(WIN32) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32 Rstrtmgr) endif() check_function_exists(utimes HAVE_UTIMES) diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index e4ff310163523..e0cf5e49df7f1 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -35,6 +35,12 @@ #include "csync_exclude.h" #include "csync.h" +namespace +{ +constexpr const char *editorNamesForDelayedUpload[] = {"PowerPDF"}; +constexpr const char *fileExtensionsToCheckIfOpenForSigning[] = {".pdf"}; +constexpr auto delayIntervalForSyncRetryForOpenedForSigningFilesSeconds = 60; +} namespace OCC { @@ -1043,6 +1049,19 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo( item->_status = SyncFileItem::Status::NormalError; } + { + const auto foundEditorsKeepingFileBusy = queryEditorsKeepingFileBusy(item, path); + if (!foundEditorsKeepingFileBusy.isEmpty()) { + item->_instruction = CSYNC_INSTRUCTION_ERROR; + const auto editorsString = foundEditorsKeepingFileBusy.join(", "); + qCInfo(lcDisco) << "Failed, because it is open in the editor." << item->_file << "direction" << item->_direction << editorsString; + item->_errorString = tr("Could not upload file, because it is open in \"%1\".").arg(editorsString); + item->_status = SyncFileItem::Status::SoftError; + _discoveryData->_anotherSyncNeeded = true; + _discoveryData->_filesNeedingScheduledSync.insert(path._original, delayIntervalForSyncRetryForOpenedForSigningFilesSeconds); + } + } + if (dbEntry.isValid() && item->isDirectory()) { item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(dbEntry._e2eEncryptionStatus); if (item->isEncrypted()) { @@ -1860,6 +1879,43 @@ bool ProcessDirectoryJob::isRename(const QString &originalPath) const */ } +QStringList ProcessDirectoryJob::queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const +{ + QStringList matchingEditorsKeepingFileBusy; + + if (item->isDirectory() || item->_direction != SyncFileItem::Up) { + return matchingEditorsKeepingFileBusy; + } + + const auto isMatchingFileExtension = std::find_if(std::cbegin(fileExtensionsToCheckIfOpenForSigning), std::cend(fileExtensionsToCheckIfOpenForSigning), + [path](const auto &matchingExtension) { + return path._local.endsWith(matchingExtension, Qt::CaseInsensitive); + }) != std::cend(fileExtensionsToCheckIfOpenForSigning); + + if (!isMatchingFileExtension) { + return matchingEditorsKeepingFileBusy; + } + + const QString fullLocalPath(_discoveryData->_localDir + path._local); + const auto editorsKeepingFileBusy = Utility::queryProcessInfosKeepingFileOpen(fullLocalPath); + + for (const auto &detectedEditorName : editorsKeepingFileBusy) { + const auto isMatchingEditorFound = std::find_if(std::cbegin(editorNamesForDelayedUpload), std::cend(editorNamesForDelayedUpload), + [detectedEditorName](const auto &matchingEditorName) { + return detectedEditorName.processName.startsWith(matchingEditorName, Qt::CaseInsensitive); + }) != std::cend(editorNamesForDelayedUpload); + if (isMatchingEditorFound) { + matchingEditorsKeepingFileBusy.push_back(detectedEditorName.processName); + } + } + + if (!matchingEditorsKeepingFileBusy.isEmpty()) { + matchingEditorsKeepingFileBusy.push_back("PowerPDF.exe"); + } + + return matchingEditorsKeepingFileBusy; +} + auto ProcessDirectoryJob::checkMovePermissions(RemotePermissions srcPerm, const QString &srcPath, bool isDirectory) -> MovePermissionResult diff --git a/src/libsync/discovery.h b/src/libsync/discovery.h index eaa2657697a91..62d1f493c5132 100644 --- a/src/libsync/discovery.h +++ b/src/libsync/discovery.h @@ -191,6 +191,8 @@ class ProcessDirectoryJob : public QObject [[nodiscard]] bool isRename(const QString &originalPath) const; + [[nodiscard]] QStringList queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const; + struct MovePermissionResult { // whether moving/renaming the source is ok From 32021e0e5de4c4922691c24f793f3128d133874f Mon Sep 17 00:00:00 2001 From: Uwe Runtemund Date: Wed, 14 Feb 2024 12:18:00 +0100 Subject: [PATCH 37/37] Improved cliend side sync process for tags. Some code clean-up. Signed-off-by: Uwe Runtemund --- src/common/syncjournaldb.cpp | 4 +- src/gui/folder.cpp | 2 - src/gui/folderwatcher.cpp | 1 - src/libsync/discovery.cpp | 21 +- src/libsync/discoveryphase.cpp | 12 +- src/libsync/filesystem.cpp | 2 +- src/libsync/filetags.cpp | 836 ++++++++++++++++-------------- src/libsync/filetags.h | 165 +++--- src/libsync/networkjobs.cpp | 75 ++- src/libsync/networkjobs.h | 8 +- src/libsync/propagatedownload.cpp | 4 +- src/libsync/syncengine.cpp | 2 - src/libsync/syncfileitem.cpp | 12 +- 13 files changed, 602 insertions(+), 542 deletions(-) diff --git a/src/common/syncjournaldb.cpp b/src/common/syncjournaldb.cpp index b7aaa8f0559b7..66f67487a1838 100644 --- a/src/common/syncjournaldb.cpp +++ b/src/common/syncjournaldb.cpp @@ -1506,7 +1506,7 @@ bool SyncJournalDb::updateMetadataTagList(const QString &filename,const QByteArr { QMutexLocker locker(&_mutex); - qCInfo(lcDb) << "Updating file tag lists " << filename; + qCInfo(lcDb) << "Updating file tag list " << filename; if (!checkConnect()) { qCWarning(lcDb) << "Failed to connect database."; @@ -1525,7 +1525,6 @@ bool SyncJournalDb::updateMetadataTagList(const QString &filename,const QByteArr } query->bindValue(1, phash); query->bindValue(2, *tagList); - printf("RMD UPDATE TAG\n"); return query->exec(); } @@ -1552,7 +1551,6 @@ QByteArray SyncJournalDb::tagList(const QString &file) if (!next.ok || !next.hasData)return QByteArray(); QByteArray result = query->baValue(0); - printf("RMD YES %lld %s!\n",phash,result.data()); return result; } diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 983392b03d27a..fb3062da87f1b 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -558,7 +558,6 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) qCDebug(lcFolder) << "Changed path is not contained in folder, ignoring:" << path; return; } - printf("RMD Change 2\n"); auto relativePath = path.midRef(this->path().size()); if (pathIsIgnored(path)) { @@ -628,7 +627,6 @@ void Folder::slotWatchedPathChanged(const QString &path, ChangeReason reason) } } warnOnNewExcludedItem(record, relativePath); - printf("RMD Change 3\n"); emit watchedFileChangedExternally(path); // Also schedule this folder for a sync, but only after some delay: diff --git a/src/gui/folderwatcher.cpp b/src/gui/folderwatcher.cpp index 0bcf518e8506e..07436463a81ab 100644 --- a/src/gui/folderwatcher.cpp +++ b/src/gui/folderwatcher.cpp @@ -263,7 +263,6 @@ void FolderWatcher::changeDetected(const QStringList &paths) qCInfo(lcFolderWatcher) << "Detected changes in paths:" << changedPaths; for (const auto &path : changedPaths) { - printf("RMD Change 1\n"); emit pathChanged(path); } } diff --git a/src/libsync/discovery.cpp b/src/libsync/discovery.cpp index e0cf5e49df7f1..b9f2404b316bc 100644 --- a/src/libsync/discovery.cpp +++ b/src/libsync/discovery.cpp @@ -495,20 +495,15 @@ void ProcessDirectoryJob::processFile(PathTuple path, if(localEntry.isValid() && dbEntry.isValid()) { - FileTagManager* tm = FileTagManager::GetInstance(); - // Try tag pull by default (order pull,push is relevant!) - tm->pullTags(_discoveryData->_localDir % path._original, - localEntry, - dbEntry); - - // Only if all three entries are valid, we make a full sync. - if(serverEntry.isValid()) { - tm->pushTags(_discoveryData->_localDir % path._original, - localEntry, - serverEntry, - dbEntry); - } + // Tag synchronization + FileTagManager::syncTags(_discoveryData->_account, + _discoveryData->_statedb, + _discoveryData->_localDir, + path._original, + localEntry, + serverEntry, + dbEntry); } if (_discoveryData->isRenamed(path._original)) { diff --git a/src/libsync/discoveryphase.cpp b/src/libsync/discoveryphase.cpp index 96acc1c203791..ce89d2cffae1a 100644 --- a/src/libsync/discoveryphase.cpp +++ b/src/libsync/discoveryphase.cpp @@ -395,7 +395,8 @@ void DiscoverySingleDirectoryJob::start() << "http://owncloud.org/ns:permissions" << "http://owncloud.org/ns:checksums" << "http://owncloud.org/ns:tags" - << "http://nextcloud.org/ns:system-tags"; + << "http://nextcloud.org/ns:system-tags" + << "http://nextcloud.org/ns:metadata_etag"; if (_isRootPath) props << "http://owncloud.org/ns:data-fingerprint"; @@ -543,11 +544,10 @@ static void propertyMapToRemoteInfo(const QMap &map, RemoteInf } } if(property == "system-tags" || property == "tags"){ - QByteArray list =FileTagManager::fromPropertiesToTagList(value); - if(list.size()>0){ - if(result.tagList.size()>0)result.tagList.append('\n'); - result.tagList.append(list); - } + FileTagManager::fromPropertiesToTagList(result.tagList,value); + } + if(property == "metadata_etag"){ + //TODO: RMD Handle metadata etag for tag synchronization } } diff --git a/src/libsync/filesystem.cpp b/src/libsync/filesystem.cpp index c850deced58c4..4b2c2c174b896 100644 --- a/src/libsync/filesystem.cpp +++ b/src/libsync/filesystem.cpp @@ -91,7 +91,7 @@ bool FileSystem::fileChanged(const QString &fileName, { return getSize(fileName) != previousSize || getModTime(fileName) != previousMtime - || FileTagManager::GetInstance()->readTagListFromLocalFile(fileName) != taglist; + || FileTagManager::readTagListFromLocalFile(fileName) != taglist; } bool FileSystem::verifyFileUnchanged(const QString &fileName, diff --git a/src/libsync/filetags.cpp b/src/libsync/filetags.cpp index 2ce13124ba51b..26fdb4e1ebd42 100644 --- a/src/libsync/filetags.cpp +++ b/src/libsync/filetags.cpp @@ -24,421 +24,465 @@ #include #include "common/syncjournalfilerecord.h" +#include "discoveryphase.h" #include "filetags.h" namespace OCC{ + +Q_LOGGING_CATEGORY(lcFTM, "nextcloud.sync.filetags", QtInfoMsg) + +FileTagManager* gFileTagManger=NULL; - FileTagManager* gFileTagManger=NULL; - - FileTagManager::FileTagManager(AccountPtr account,SyncJournalDb* journal):QObject(NULL) - { - _account=account; - _journal=journal; - } - - void FileTagManager::Init(AccountPtr account,SyncJournalDb* journal) - { - ASSERT(gFileTagManger==NULL); - gFileTagManger=new FileTagManager(account,journal); - } - - FileTagManager* FileTagManager::GetInstance() - { - ASSERT(gFileTagManger!=NULL); - return gFileTagManger; - } - - - QByteArray FileTagManager::fromPropertiesToTagList(const QString &properties) - { - if(properties==NULL || properties.isEmpty()) return QByteArray(); - - QStringList tags; - QString token; - QXmlStreamReader reader(""%properties%""); - bool insideTag = false; - - while (!reader.atEnd()) - { - QXmlStreamReader::TokenType type = reader.readNext(); - QString name = reader.name().toString(); - - // Start elements with DAV: - if (type == QXmlStreamReader::StartElement && (name=="system-tag" || name=="tag")) - { - insideTag=true; - } - else if(type== QXmlStreamReader::Characters && insideTag) - { - token.append(reader.text()); - } - else if (type == QXmlStreamReader::EndElement && (name == "system-tag" || name=="tag")) - { - if(token.size()>0)tags << token; - token.clear(); - insideTag=false; - } - } - - tags.sort(Qt::CaseInsensitive); - printf("RMD TAG %s\n",tags.join(QChar(';')).toUtf8().data()); - return tags.join(QChar(0x000A)).toUtf8(); - } - +FileTagManager::FileTagManager():QObject(NULL) +{} + +FileTagManager* FileTagManager::GetInstance() +{ + if(gFileTagManger==NULL)gFileTagManger=new FileTagManager(); + return gFileTagManger; +} + +void FileTagManager::fromPropertiesToTagList(QByteArray &list,const QString &properties) +{ + if(properties==NULL || properties.isEmpty()) return; + + QStringList tags; + QString token; + QXmlStreamReader reader(""%properties%""); + bool insideTag = false; + + while (!reader.atEnd()){ + QXmlStreamReader::TokenType type = reader.readNext(); + QString name = reader.name().toString(); + + // Start elements with DAV: + if (type == QXmlStreamReader::StartElement && (name=="system-tag" || name=="tag")){ + insideTag=true; + } + else if(type== QXmlStreamReader::Characters && insideTag){ + token.append(reader.text()); + } + else if (type == QXmlStreamReader::EndElement && (name == "system-tag" || name=="tag")){ + if(token.size()>0)tags << token; + token.clear(); + insideTag=false; + } + } + + if(tags.size()>0){ + if(list.size()>0)tags.append(QString(list).split(QChar(0x000A))); + tags.removeDuplicates(); + tags.sort(Qt::CaseInsensitive); + list=tags.join(QChar(0x000A)).toUtf8(); + } +} + QByteArray FileTagManager::readTagListFromLocalFile(const QString &path) { #ifdef __APPLE__ - // Create necessary system related objects - CFStringRef cfstr = path.toCFString(); - CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, - cfstr, - kCFURLPOSIXPathStyle, - false); - - // Query tags - CFArrayRef labels=NULL; - Boolean result = CFURLCopyResourcePropertyForKey(urlref, - kCFURLTagNamesKey, - &labels, - NULL); - - QStringList tagarray; - - if(result==true && labels != NULL){ - // Extract the labels to our array - int count = (int) CFArrayGetCount(labels); - - if(count>0){ - - for(int index=0;index0)tagarray << QString::fromCFString(str); - } - } - } - - tagarray.sort(Qt::CaseInsensitive); - - // Clean up - CFRelease(cfstr); - CFRelease(urlref); - if(labels!=NULL)CFRelease(labels); - + // Create necessary system related objects + CFStringRef cfstr = path.toCFString(); + CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, + cfstr, + kCFURLPOSIXPathStyle, + false); + + // Query tags + CFArrayRef labels=NULL; + Boolean result = CFURLCopyResourcePropertyForKey(urlref, + kCFURLTagNamesKey, + &labels, + NULL); + + QStringList tagarray; + + if(result==true && labels != NULL){ + // Extract the labels to our array + int count = (int) CFArrayGetCount(labels); + + if(count>0){ + for(int index=0;index0)tagarray << QString::fromCFString(str); + } + } + } + + tagarray.sort(Qt::CaseInsensitive); + + // Clean up + CFRelease(cfstr); + CFRelease(urlref); + if(labels!=NULL)CFRelease(labels); + return tagarray.join(QChar(0x000A)).toUtf8(); #endif - #ifdef __linux__ - const int sz=4096; - char buffer[sz]; - int result = getxattr(path.toUtf8().constData(),"user.xdg.tags",buffer,sz); - - if(result>0) - { - // Data is stored usually as comma(,) separated list. - // So we just need to replace ',' by '\n'. - QByteArray tagList = QByteArray(buffer,result); - tagList.replace(',','\n'); - printf("%s\n",buffer); - return tagList; - } - else return QByteArray(); + const int sz=4096; + char buffer[sz]; + int result = getxattr(path.toUtf8().constData(),"user.xdg.tags",buffer,sz); + + if(result>0){ + // Data is stored usually as comma(,) separated list. + // So we just need to replace ',' by '\n'. + QByteArray tagList = QByteArray(buffer,result); + tagList.replace(',','\n'); + return tagList; + } + else return QByteArray(); #endif } bool FileTagManager::writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist) { -#ifdef __APPLE__ - printf("RMD SYNC LOCAL FS\n"); - QStringList strlist=QString(taglist).split(QChar(0x000A)); - - //Create new array and append tag - int newsize=strlist.size(); - CFStringRef* strs = new CFStringRef[newsize]; - for(int index=0;indextagList(file)); - } - - void FileTagManager::pushTags(const QString &fullpath, - const LocalInfo &localEntry, - const RemoteInfo &serverEntry, - const SyncJournalFileRecord &dbEntry) - { - if(localEntry.isValid() - && dbEntry.isValid() - && serverEntry.isValid()) - { - - if(localEntry.tagList!= dbEntry._tagList - || localEntry.tagList!=serverEntry.tagList - || dbEntry._tagList!=serverEntry.tagList) - { - printf("RMD TAG PUSH %s\n",fullpath.toUtf8().data()); - - // For syncing tags, the following matrix applies: - // - // LC: Local, DB: SyncDatabase, RM: Remote - // X: Tag is present, -: Tag is not present - //----------------------------------------- - // LC DB RM What happend ACTION - //----------------------------------------- - // 1 - - - no tags NOP - // 2 X X X tag present NOP - // 3 X - X lc/rm added ADD (DB) - // 4 X - - lc added ADD (RM+DB) - // 5 - - X rm added ADD (LC+DB) - // 6 - X - lc/rm deleted DEL (DB) - // 7 - X X lc deleted DEL (RM+DB) - // 8 X X - rm deleted DEL (LC+DB) - //------------------------------------------ - - // Note: The lists from the entries are always alphabetically sorted. - QStringList lcTags; - QStringList dbTags; - QStringList rmTags; - QStringList syncedTagList; - - // Avoid adding empty entries to the list - if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); - if(serverEntry.tagList.size()>0)rmTags=QString(serverEntry.tagList).split(QChar(0x000A)); - if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); - - - // The "new" synced list ist a joined list of all present tags, and then deleting - // Tags according to row 6-8 of above table - syncedTagList << lcTags; - syncedTagList << rmTags; - syncedTagList << dbTags; - syncedTagList.removeDuplicates(); - syncedTagList.sort(Qt::CaseInsensitive); - - for(int index=0;index"); - tags.append(list[index]); - tags.append(""); - } - - QByteArray newTagList = list.join(QChar(0x000A)).toUtf8(); - - QMap properties; - properties.insert(QString("http://owncloud.org/ns:tags").toUtf8(),tags.toUtf8()); - - ProppatchJob* job = new ProppatchJob(_account,dbEntry._path); - job->setProperties(properties); - - QObject::connect(job, &ProppatchJob::success, this, [=](){ - pushTagsStep2(fullpath,newTagList,dbEntry,syncLocal,syncDb); - }); - - QObject::connect(job, &ProppatchJob::finishedWithError, this,[=]() { - // Do nothing, ignore. - printf("RMD PROPPATCH failure\n"); - }); - - job->start(); - } - - void FileTagManager::pushTagsStep2(const QString &fullpath,const QByteArray &newTagList,const SyncJournalFileRecord &dbEntry,bool syncLocal,bool syncDb) - { - printf("RMD PUSH STEP 2 %i,%i\n",syncLocal,syncDb); - bool localSuccess=true; - if(syncLocal) - { - localSuccess=writeTagListToLocalFile(fullpath,newTagList); - printf("RMD LOCAL WRITE %i\n",localSuccess); - } - - if(localSuccess && syncDb) - { - bool success=_journal->updateMetadataTagList(dbEntry._path,&newTagList); - printf("RMD DB UPDATE %i\n",success); - } - }; - - - void FileTagManager::pullTags(const QString &fullpath, - const LocalInfo &localEntry, - const SyncJournalFileRecord &dbEntry) - { - if(localEntry.isValid() - && dbEntry.isValid()) - { - - if(localEntry.tagList!= dbEntry._tagList) - { - printf("RMD TAG PULL %s\n",fullpath.toUtf8().data()); - - // For syncing tags, the following matrix applies: - // - // LC DB What happened Action - // 1 - - no tags NOP - // 2 X X tag present NOP - // 3 - X rm added ADD (LC) - // 4 X - rm added ADD (RM) - // - // If db taglist is empty, we assume an initial sync for that file and push the tags to the - // server, even if we - - // Note: The lists from the entries are always alphabetically sorted. - QStringList lcTags; - QStringList dbTags; - QStringList syncedTagList; - bool initialSync=false; - - // Avoid addint empty entries to the list - if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); - if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); - if(dbEntry._tagList.size()==0)initialSync=true; - - // The "new" synced list ist just a joined list of all present tags, because we do not - // delete here any tags. - syncedTagList << lcTags; - syncedTagList << dbTags; - syncedTagList.removeDuplicates(); - syncedTagList.sort(Qt::CaseInsensitive); - - QByteArray newTagList = syncedTagList.join(QChar(0x000A)).toUtf8(); - - bool syncLocal = newTagList != localEntry.tagList; - bool syncDb = newTagList != dbEntry._tagList; - - printf("RMD pull sync tags %s (%s) %i,%i\n", - fullpath.toUtf8().data(), - syncedTagList.join(QChar((int)' ')).toUtf8().data(), - syncLocal, - syncDb); - - // We sync in that order: - // 1. Local - // 2. Database (only if local is successfull) - - if(initialSync) - { - printf("RMD INITIAL!\n"); - } - - bool localSuccess=true; - if(syncLocal) - { - localSuccess=writeTagListToLocalFile(fullpath,newTagList); - printf("RMD LOCAL WRITE %i\n",localSuccess); - } - - if(localSuccess && syncDb) - { - bool success=_journal->updateMetadataTagList(dbEntry._path,&newTagList); - printf("RMD DB UPDATE %i\n",success); - } - - } - } - } - + +bool FileTagManager::restoreLocalFileTags(SyncJournalDb* journal, + const QString &localdir, + const QString &file) +{ + qCInfo(lcFTM) << "Restore file tag list in file system: " << file; + return writeTagListToLocalFile(localdir % file,journal->tagList(file)); +} + +void FileTagManager::syncTags(AccountPtr account, + SyncJournalDb* journal, + const QString &localDir, + const QString &filePath, + const LocalInfo &localEntry, + const RemoteInfo &serverEntry, + const SyncJournalFileRecord &dbEntry) +{ + QString fullpath=localDir%filePath; + + if(localEntry.isValid() + && dbEntry.isValid() + && serverEntry.isValid()){ + + if(localEntry.tagList!= dbEntry._tagList + || localEntry.tagList!=serverEntry.tagList + || dbEntry._tagList!=serverEntry.tagList){ + + // For syncing tags, the following matrix applies: + // + // LC: Local, DB: SyncDatabase, RM: Remote + // X: Tag is present, -: Tag is not present + //----------------------------------------- + // LC DB RM What happend ACTION + //----------------------------------------- + // 1 - - - no tags NOP + // 2 X X X tag present NOP + // 3 X - X lc/rm added ADD (DB) + // 4 X - - lc added ADD (RM+DB) + // 5 - - X rm added ADD (LC+DB) + // 6 - X - lc/rm deleted DEL (DB) + // 7 - X X lc deleted DEL (RM+DB) + // 8 X X - rm deleted DEL (LC+DB) + //------------------------------------------ + + // Note: The lists from the entries are always alphabetically sorted. + QStringList lcTags; + QStringList dbTags; + QStringList rmTags; + QStringList syncedTagList; + + // Avoid adding empty entries to the list + if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); + if(serverEntry.tagList.size()>0)rmTags=QString(serverEntry.tagList).split(QChar(0x000A)); + if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); + + // The "new" synced list ist a joined list of all present tags, and then deleting + // Tags according to row 6-8 of above table + syncedTagList << lcTags; + syncedTagList << rmTags; + syncedTagList << dbTags; + syncedTagList.removeDuplicates(); + syncedTagList.sort(Qt::CaseInsensitive); + + for(int index=0;indexsyncTagsStep1(account, + journal, + fullpath, + syncedTagList, + dbEntry, + syncLocal, + syncDb); + } + else{ + GetInstance()->syncTagsStep2(journal, + fullpath, + newTagList, + dbEntry, + syncLocal, + syncDb); + } + } + } + else{ + localSync(account, + journal, + fullpath, + filePath, + localEntry, + dbEntry); + } +} + +void FileTagManager::syncTagsStep1(AccountPtr account, + SyncJournalDb* journal, + const QString &fullpath, + QStringList list, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb) +{ + // SYNCING REMOTE CAN ONLY SET oc:tags, not nc:system-tags + // oc:tags can double nc:system-tags, but we try to prevent that. + // We do following: + // 1. propfind oc:tags list and od:system-tags of file + // 3. remove system-tags from list (we cannot delete them acually) + // 3. deside wich tag to set or remove + // 4. proppatch oc:tags + // TODO: Update this procedure, as soon as Nextcloud Server supports PROPPATCH of nc:system-tags + // Check: https://github.com/nextcloud/server/blob/master/apps/dav/lib/SystemTag/SystemTagPlugin.php + + qCInfo(lcFTM) << "Start remote sync: " + << fullpath <<' ' + << (syncLocal?'L':'-') + << (syncDb?'D':'-'); + + // Note: We must not delete tags explicitly. The server removes non present tags automatically. + QString tags; + for(int index=0;index"); + tags.append(list[index]); + tags.append(""); + } + + QMap setproperties; + setproperties.insert(QString("http://owncloud.org/ns:tags").toUtf8(),tags.toUtf8()); + + ProppatchJob* job = new ProppatchJob(account,dbEntry._path); + if(!setproperties.isEmpty())job->setProperties(setproperties); + + QByteArray newTagList = list.join(QChar(0x000A)).toUtf8(); + + QObject::connect(job, &ProppatchJob::success, GetInstance(), [=](){ + qCInfo(lcFTM) << "Remote sync successfull: " << fullpath; + syncTagsStep2(journal,fullpath,newTagList,dbEntry,syncLocal,syncDb); + }); + + QObject::connect(job, &ProppatchJob::finishedWithError, GetInstance(),[=]() { + // Do nothing, ignore. + qCInfo(lcFTM) << "Remote sync with errors: " << fullpath; + }); + + job->start(); +} + +void FileTagManager::syncTagsStep2(SyncJournalDb* journal, + const QString &fullpath, + const QByteArray &newTagList, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb) +{ + bool localSuccess=true; + if(syncLocal) + { + localSuccess=writeTagListToLocalFile(fullpath,newTagList); + } + + if(localSuccess && syncDb) + { + journal->updateMetadataTagList(dbEntry._path,&newTagList); + } +}; + +void FileTagManager::localSync(AccountPtr account, + SyncJournalDb* journal, + const QString &localDir, + const QString &filePath, + const LocalInfo &localEntry, + const SyncJournalFileRecord &dbEntry) +{ + QString fullpath=localDir%filePath; + + if(localEntry.isValid() && dbEntry.isValid()){ + + if(localEntry.tagList!= dbEntry._tagList){ + + // For syncing tags, the following matrix applies: + // + // LC DB What happened Action + // 1 - - no tags NOP + // 2 X X tag present NOP + // 3 - X rm add or lc del FULL SYNC + // 4 X - lc add or rm del FULL SYNC + // + // Note: The lists from the entries are always alphabetically sorted. + QStringList lcTags; + QStringList dbTags; + QStringList syncedTagList; + + // Avoid adding empty entries to the list + if(localEntry.tagList.size()>0)lcTags=QString(localEntry.tagList).split(QChar(0x000A)); + if(dbEntry._tagList.size()>0)dbTags=QString(dbEntry._tagList).split(QChar(0x000A)); + + // The "new" synced list ist just a joined list of all present tags. + syncedTagList << lcTags; + syncedTagList << dbTags; + syncedTagList.removeDuplicates(); + syncedTagList.sort(Qt::CaseInsensitive); + + QByteArray newTagList = syncedTagList.join(QChar(0x000A)).toUtf8(); + + bool fullSync = newTagList != localEntry.tagList || newTagList != dbEntry._tagList; + + qCInfo(lcFTM) << "Local sync: " + << filePath + << '('<< syncedTagList.join(QChar((int)',')).toUtf8().data() << ')' + << (fullSync?'F':'N'); + + if(fullSync){ + const auto propfindJob = new PropfindJob(account, filePath, GetInstance()); + propfindJob->setProperties({ QByteArrayLiteral("http://owncloud.org/ns:tags"), + QByteArrayLiteral("http://nextcloud.org/ns:system-tags") }); + + connect(propfindJob, &PropfindJob::result, GetInstance(), [=](const QVariantMap &result){ + qCInfo(lcFTM) << "Fetching remote info ok: " << fullpath; + + RemoteInfo remoteInfo; + int slash = filePath.lastIndexOf('/'); + remoteInfo.name = filePath.mid(slash + 1); + + //TODO: SNIPPTE TAKEN FROM fieldtagmodel.h, but I quess doubling it is not good, + // also I prefer to remove the "special" code from PropfindJob and handle it + const auto normalTags = result.value(QStringLiteral("tags")).toStringList(); + const auto systemTags = result.value(QStringLiteral("system-tags")).toList(); + + QStringList systemTagStringList = QStringList(); + for (const auto &systemTagMapVariant : systemTags) { + const auto systemTagMap = systemTagMapVariant.toMap(); + const auto systemTag = systemTagMap.value(QStringLiteral("tag")).toString(); + systemTagStringList << systemTag; + } + + QStringList tags=QStringList(); + tags << normalTags << systemTagStringList; + tags.removeDuplicates(); + tags.removeAll(QString("")); + tags.sort(Qt::CaseInsensitive); + + remoteInfo.tagList=tags.join(QChar(0x000A)).toUtf8().constData(); + + QByteArray rl =remoteInfo.tagList; + rl.replace('\n',','); + + syncTags(account, + journal, + localDir, + filePath, + localEntry, + remoteInfo, + dbEntry); + }); + + connect(propfindJob, &PropfindJob::finishedWithError, GetInstance(), [=](QNetworkReply*){ + //Do nothing here. + qCInfo(lcFTM) << "Fetching remote info with errors: " << fullpath; + }); + + propfindJob->start(); + } + } + } +} + } diff --git a/src/libsync/filetags.h b/src/libsync/filetags.h index 34b07d0dab213..075d397512b35 100644 --- a/src/libsync/filetags.h +++ b/src/libsync/filetags.h @@ -72,99 +72,86 @@ namespace OCC * LC/DB or RM/DB, so we have to deal a little bit different, to reach the same approach. * To avoid an massive increasment of the network polls, we use following approach: * - * - DB is frequently overridden by the client with the RM value, so we assume, the db value is the - * same as remote: - * LC DB What happened Action - * 1 - - no tags NOP - * 2 X X tag present NOP - * 3 - X rm added ADD (LC) - * 4 X - lc added ADD (RM) - * - * So this kind of sync will not delete tags. That is important to prevent local setted tags befor - * adding the tag feature will not be deleted. That also means, that it is not possible to "delete" - * local tags at the moment. - * - * In "language" of git, the first sync step with 3 data points available is like a "PULL & COMMIT & PUSH" - * an the second one is like a "PULL". - * - * TODO: CURRENTLY IT SEEMS THAT PROPPATCH IS NOT IMPLEMENTED LOOK AT: https://github.com/nextcloud/server/blob/master/apps/dav/lib/SystemTag/SystemTagPlugin.php - * TODO: BUT IT IS IMPLEMENTED FOR OC:TAG (NOT SYSTEM:TAG), OC:TAG IS NOT VISIBLE ON THE WEB! WHY? - * TODO: THAT MEAN IN FACT, THAT JUST A "DOWN-SYNC" IS POSSIBLE, SO WE HAVE TO ENSURE, NOT LOOSING - * TODO: LOCAL TAGS. AND PROCEED RESEARCH - * - *curl 'https://cloud.runtemund.de/remote.php/dav/files/USER/Anleitung.md' \ - --user USER:XXX \ - * --request PROPPATCH \ - --data ' - - - Test - - ' + * - DB is frequently overridden by the client with the RM value, so we cannot say for sure the + * correct sync status + * LC DB What happened Action + * 1 - - no tags NOP + * 2 X X tag present NOP + * 3 - X rm add or lc del FULL SYNC + * 4 X - lc add or rm del FULL SYNC * */ - class OCSYNC_EXPORT FileTagManager : public QObject - { - Q_OBJECT +class OCSYNC_EXPORT FileTagManager : public QObject +{ + Q_OBJECT public: - - //! Returns an instance of the FileTagManager - static FileTagManager* GetInstance(); - //! Initialize the global FileTagManager, must be called as early as possible/necessary. - static void Init(AccountPtr account,SyncJournalDb* journal); - -/*! - * \brief Converts the given XML fragment of system-tag entries to a sorted list of tags. - * - * The list is a QByteArray with the content. The tags are delimited by 0x0A ('\\n') - */ -static QByteArray fromPropertiesToTagList(const QString &properties); - -//! Reads the tags from the local file system. -QByteArray readTagListFromLocalFile(const QString &localpath); - -/// Restore the tags in the local file system. -/// \note: is needed after file download, -bool restoreLocalFileTags(const QString &localdir,const QString &file); - -//! Makes a "full" synchronization. Will only do, if all data entries are valid. -void pushTags(const QString &fullpath, - const LocalInfo &localEntry, - const RemoteInfo &serverEntry, - const SyncJournalFileRecord &dbEntry); - -//! Makes a "local" synchronization. Will do a server sync, if initial sync is assumed -void pullTags(const QString &fullpath, - const LocalInfo &localEntry, - const SyncJournalFileRecord &dbEntry); - - private: - - //! Reference to account. Needed for network jobs. - AccountPtr _account; - - //! Reference to sync journal. Needed for database queries. - SyncJournalDb* _journal; - - //! Private constructor. Needs to be private, to avoid multiple instances (we are a singleton). - //! TODO: It seems, that this approach is not good with multiple accounts. - FileTagManager(AccountPtr account,SyncJournalDb* journal); - - //! Helper method for pushTags - void pushTagsStep1(const QString &fullpath, - QStringList list, - const SyncJournalFileRecord &dbEntry, - bool syncLocal,bool syncDb); - - //! Helper method for pushTags - void pushTagsStep2(const QString &fullpath, - const QByteArray &newTagList, - const SyncJournalFileRecord &dbEntry, - bool syncLocal,bool syncDb); - - //! Write the tag list to the local file. - bool writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist); + /*! + * \brief Converts the given XML fragment of system-tag entries to a sorted list of tags. + * + * The list is a QByteArray with the content. The tags are delimited by 0x0A ('\\n') + */ + static void fromPropertiesToTagList(QByteArray &list,const QString &properties); + + /*! + * \brief Reads the tags from the local file system. + */ + static QByteArray readTagListFromLocalFile(const QString &localpath); + + /*! Restore the tags in the local file system. + * \note: is needed after file download. + */ + static bool restoreLocalFileTags(SyncJournalDb* journal, + const QString &localdir, + const QString &file); + + /*! + * \brief Makes a "full" synchronization. If all data is valid, full synchronization is directly + * done. If not, local sync is checked, if data is not synchronized, a full sync is started. + */ + static void syncTags(AccountPtr account, + SyncJournalDb* journal, + const QString &localDir, + const QString &filePath, + const LocalInfo &localEntry, + const RemoteInfo &serverEntry, + const SyncJournalFileRecord &dbEntry); + +private: + + //! Private constructor. No need for an instance actually. + FileTagManager(); + + //! Needed for QT connect + static FileTagManager* GetInstance(); + + //! Helper method for syncTags + void syncTagsStep1(AccountPtr account, + SyncJournalDb* journal, + const QString &fullpath, + QStringList list, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb); + + //! Helper method for synTags + void syncTagsStep2(SyncJournalDb* journal, + const QString &fullpath, + const QByteArray &newTagList, + const SyncJournalFileRecord &dbEntry, + bool syncLocal,bool syncDb); + + /*! + * \brief Makes a "local" synchronization. Will do a server sync, if initial sync is assumed. + */ + static void localSync(AccountPtr account, + SyncJournalDb* journal, + const QString &localDir, + const QString &filePath, + const LocalInfo &localEntry, + const SyncJournalFileRecord &dbEntry); + + //! Write the tag list to the local file. + static bool writeTagListToLocalFile(const QString &localpath,const QByteArray &taglist); }; diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index e1bc8e6aa8012..e168dbd985efb 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -861,13 +861,36 @@ ProppatchJob::ProppatchJob(AccountPtr account, const QString &path, QObject *par void ProppatchJob::start() { - if (_properties.isEmpty()) { + if (_setproperties.isEmpty()) { qCWarning(lcProppatchJob) << "Proppatch with no properties!"; } QNetworkRequest req; - QByteArray propStr; - QMapIterator it(_properties); + // Properties which shall be set + QByteArray setPropStr; + QMapIterator it(_setproperties); + while (it.hasNext()) { + it.next(); + QByteArray keyName = it.key(); + QByteArray keyNs; + if (keyName.contains(':')) { + int colIdx = keyName.lastIndexOf(":"); + keyNs = keyName.left(colIdx); + keyName = keyName.mid(colIdx + 1); + } + + setPropStr += " <" + keyName; + if (!keyNs.isEmpty()) { + setPropStr += " xmlns=\"" + keyNs + "\" "; + } + setPropStr += ">"; + setPropStr += it.value(); + setPropStr += "\n"; + } + + // Properties which shall be removed + QByteArray remPropStr; + it = QMapIterator(_removeproperties); while (it.hasNext()) { it.next(); QByteArray keyName = it.key(); @@ -878,20 +901,30 @@ void ProppatchJob::start() keyName = keyName.mid(colIdx + 1); } - propStr += " <" + keyName; + remPropStr += " <" + keyName; if (!keyNs.isEmpty()) { - propStr += " xmlns=\"" + keyNs + "\" "; + remPropStr += " xmlns=\"" + keyNs + "\" "; } - propStr += ">"; - propStr += it.value(); - propStr += "\n"; + remPropStr += ">"; + remPropStr += it.value(); + remPropStr += "\n"; } + QByteArray xml = "\n" - "\n" - " \n" - + propStr + " \n" - "\n"; - printf("RMD QUERY: %s\n",xml.data()); + "\n"; + + if(!setPropStr.isEmpty()){ + xml += " \n" + + setPropStr + " \n"; + } + + if(!remPropStr.isEmpty()){ + xml += " \n" + + remPropStr + " \n"; + } + + xml += "\n"; + auto *buf = new QBuffer(this); buf->setData(xml); buf->open(QIODevice::ReadOnly); @@ -901,12 +934,22 @@ void ProppatchJob::start() void ProppatchJob::setProperties(QMap properties) { - _properties = properties; + _setproperties = properties; } -QMap ProppatchJob::properties() const +QMap ProppatchJob::propertiesToSet() const { - return _properties; + return _setproperties; +} + +void ProppatchJob::removeProperties(QMap properties) +{ + _removeproperties = properties; +} + +QMap ProppatchJob::propertiesToRemove() const +{ + return _removeproperties; } bool ProppatchJob::finished() diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index f623345021238..c050972ace6a8 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -276,7 +276,10 @@ class OWNCLOUDSYNC_EXPORT ProppatchJob : public AbstractNetworkJob * e.g. "ns:with:colons:bar", which is "bar" in the "ns:with:colons" namespace */ void setProperties(QMap properties); - [[nodiscard]] QMap properties() const; + [[nodiscard]] QMap propertiesToSet() const; + + void removeProperties(QMap properties); + [[nodiscard]] QMap propertiesToRemove() const; signals: void success(); @@ -286,7 +289,8 @@ private slots: bool finished() override; private: - QMap _properties; + QMap _setproperties; + QMap _removeproperties; }; /** diff --git a/src/libsync/propagatedownload.cpp b/src/libsync/propagatedownload.cpp index d826276a5cb23..a6add91ed851c 100644 --- a/src/libsync/propagatedownload.cpp +++ b/src/libsync/propagatedownload.cpp @@ -1366,7 +1366,9 @@ void PropagateDownloadFile::updateMetadata(bool isConflict) qCWarning(lcPropagateDownload) << "WARNING: Unexpectedly slow connection, took" << duration << "msec for" << _item->_size - _resumeStart << "bytes for" << _item->_file; } - FileTagManager::GetInstance()->restoreLocalFileTags(propagator()->localPath(),_item->_file); + FileTagManager::restoreLocalFileTags(propagator()->_journal, + propagator()->localPath(), + _item->_file); } void PropagateDownloadFile::slotDownloadProgress(qint64 received, qint64) diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 164d99ef36f45..22e8be6a67430 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -116,8 +116,6 @@ SyncEngine::SyncEngine(AccountPtr account, connect(this, &SyncEngine::finished, [this](bool /* finished */) { _journal->keyValueStoreSet("last_sync", QDateTime::currentSecsSinceEpoch()); }); - - FileTagManager::Init(account,journal); } SyncEngine::~SyncEngine() diff --git a/src/libsync/syncfileitem.cpp b/src/libsync/syncfileitem.cpp index b77bb252485e1..4cc7c2d500aaa 100644 --- a/src/libsync/syncfileitem.cpp +++ b/src/libsync/syncfileitem.cpp @@ -243,18 +243,10 @@ SyncFileItemPtr SyncFileItem::fromProperties(const QString &filePath, const QMap // Tags if (properties.contains(QStringLiteral("tags"))) { - QByteArray list =FileTagManager::fromPropertiesToTagList(properties.value("tags")); - if(list.size()>0){ // Avoid empty entries! - if(item->_tagList.size()>0)item->_tagList.append('\n'); - item->_tagList.append(list); - } + FileTagManager::fromPropertiesToTagList(item->_tagList,properties.value("tags")); } if (properties.contains(QStringLiteral("system-tags"))) { - QByteArray list =FileTagManager::fromPropertiesToTagList(properties.value("system-tags")); - if(list.size()>0){ // Avoid empty entries! - if(item->_tagList.size()>0)item->_tagList.append('\n'); - item->_tagList.append(list); - } + FileTagManager::fromPropertiesToTagList(item->_tagList,properties.value("system-tags")); } return item;