diff --git a/resources.qrc b/resources.qrc
index fc65956940bf4..79a38f1e61370 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -12,6 +12,7 @@
src/gui/filedetails/FileDetailsView.qml
src/gui/filedetails/FileDetailsWindow.qml
src/gui/filedetails/FileTag.qml
+ src/gui/filedetails/NCInputDateField.qml
src/gui/filedetails/NCInputTextEdit.qml
src/gui/filedetails/NCInputTextField.qml
src/gui/filedetails/NCTabButton.qml
@@ -61,6 +62,10 @@
src/gui/ResolveConflictsDialog.qml
src/gui/ConflictDelegate.qml
src/gui/ConflictItemFileInfo.qml
- src/gui/filedetails/NCInputDateField.qml
+ src/gui/macOS/ui/FileProviderSettings.qml
+ src/gui/macOS/ui/FileProviderFileDelegate.qml
+ src/gui/macOS/ui/FileProviderEvictionDialog.qml
+ src/gui/macOS/ui/FileProviderSyncStatus.qml
+ src/gui/macOS/ui/FileProviderStorageInfo.qml
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift
index f58ff0e908878..6edb6ab4be9f5 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift
@@ -26,5 +26,27 @@ extension Logger {
static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations")
static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase")
static let materialisedFileHandling = Logger(
- subsystem: subsystem, category: "materialisedfilehandling")
+ subsystem: subsystem, category: "materialisedfilehandling"
+ )
+ static let logger = Logger(subsystem: subsystem, category: "logger")
+
+ @available(macOSApplicationExtension 12.0, *)
+ static func logEntries(interval: TimeInterval = -3600) -> (Array?, Error?) {
+ do {
+ let logStore = try OSLogStore(scope: .currentProcessIdentifier)
+ let timeDate = Date().addingTimeInterval(interval)
+ let logPosition = logStore.position(date: timeDate)
+ let entries = try logStore.getEntries(at: logPosition)
+
+ return (entries
+ .compactMap { $0 as? OSLogEntryLog }
+ .filter { $0.subsystem == Logger.subsystem }
+ .map { $0.composedMessage }, nil)
+
+ } catch let error {
+ Logger.logger.error("Could not acquire os log store: \(error)");
+ return (nil, error)
+ }
+ }
}
+
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h
index 766eab1f6778d..47eb6c95bfec6 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationProtocol.h
@@ -24,6 +24,7 @@
serverUrl:(NSString *)serverUrl
password:(NSString *)password;
- (void)removeAccountConfig;
+- (void)createDebugLogStringWithCompletionHandler:(void(^)(NSString *debugLogString, NSError *error))completionHandler;
@end
diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift
index 83d26ae3a84ce..ccdc1dc902099 100644
--- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift
+++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/ClientCommunicationService.swift
@@ -60,4 +60,21 @@ class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCLi
func removeAccountConfig() {
self.fpExtension.removeAccountConfig()
}
+
+ func createDebugLogString(completionHandler: ((String?, Error?) -> Void)!) {
+ if #available(macOSApplicationExtension 12.0, *) {
+ let (logs, error) = Logger.logEntries()
+ guard error == nil else {
+ Logger.logger.error("Cannot create debug archive, received error: \(error, privacy: .public)")
+ completionHandler(nil, error)
+ return
+ }
+ guard let logs = logs else {
+ Logger.logger.error("Canot create debug archive with nil logs.")
+ completionHandler(nil, nil)
+ return
+ }
+ completionHandler(logs.joined(separator: "\n"), nil)
+ }
+ }
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a2a595faab2f8..cf1e13032f2e2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,12 @@ set_package_properties(Qt5Concurrent PROPERTIES
TYPE REQUIRED
)
+find_package(Qt5QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET)
+set_package_properties(Qt5QuickWidgets PROPERTIES
+ DESCRIPTION "Qt5 QuickWidgets component."
+ TYPE REQUIRED
+)
+
find_package(Qt5WebEngineWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT BUILD_WITH_WEBENGINE)
set_package_properties(Qt5WebEngineWidgets PROPERTIES
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 4a6e4d27fb1b1..097e4df7909e0 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -1,5 +1,5 @@
project(gui)
-find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 Xml Network)
+find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 QuickWidgets Xml Network)
find_package(KF5Archive REQUIRED)
if(QUICK_COMPILER)
@@ -293,15 +293,31 @@ IF( APPLE )
macOS/fileprovider_mac.mm
macOS/fileproviderdomainmanager.h
macOS/fileproviderdomainmanager_mac.mm
+ macOS/fileproviderdomainsyncstatus.h
+ macOS/fileproviderdomainsyncstatus_mac.mm
+ macOS/fileprovideritemmetadata.h
+ macOS/fileprovideritemmetadata.cpp
+ macOS/fileprovideritemmetadata_mac.mm
+ macOS/fileprovidermaterialiseditemsmodel.h
+ macOS/fileprovidermaterialiseditemsmodel.cpp
+ macOS/fileprovidermaterialiseditemsmodel_mac.mm
+ macOS/fileprovidersettingscontroller.h
+ macOS/fileprovidersettingscontroller_mac.mm
macOS/fileprovidersocketcontroller.h
macOS/fileprovidersocketcontroller.cpp
macOS/fileprovidersocketserver.h
macOS/fileprovidersocketserver.cpp
macOS/fileprovidersocketserver_mac.mm
+ macOS/fileproviderstorageuseenumerationobserver.h
+ macOS/fileproviderstorageuseenumerationobserver.m
+ macOS/fileproviderutils.h
+ macOS/fileproviderutils_mac.mm
macOS/fileproviderxpc.h
macOS/fileproviderxpc_mac.mm
macOS/fileproviderxpc_mac_utils.h
- macOS/fileproviderxpc_mac_utils.mm)
+ macOS/fileproviderxpc_mac_utils.mm
+ macOS/progressobserver.h
+ macOS/progressobserver.m)
endif()
if(SPARKLE_FOUND AND BUILD_UPDATER)
@@ -543,6 +559,7 @@ target_link_libraries(nextcloudCore
Qt5::Qml
Qt5::Quick
Qt5::QuickControls2
+ Qt5::QuickWidgets
KF5::Archive
)
diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp
index 8387c25c0bdca..e61043feda1e9 100644
--- a/src/gui/accountsettings.cpp
+++ b/src/gui/accountsettings.cpp
@@ -60,6 +60,10 @@
#include
#include
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "macOS/fileprovider.h"
+#endif
+
#include "account.h"
namespace {
@@ -193,6 +197,24 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
#endif
new ToolTipUpdater(_ui->_folderList);
+#if defined(BUILD_FILE_PROVIDER_MODULE)
+ if (Mac::FileProvider::fileProviderAvailable()) {
+ const auto fileProviderTab = _ui->fileProviderTab;
+ const auto fpSettingsLayout = new QVBoxLayout(fileProviderTab);
+ const auto fpAccountUserIdAtHost = _accountState->account()->userIdAtHostWithPort();
+ const auto fpSettingsController = Mac::FileProviderSettingsController::instance();
+ const auto fpSettingsWidget = fpSettingsController->settingsViewWidget(fpAccountUserIdAtHost, fileProviderTab);
+ fpSettingsLayout->setMargin(0);
+ fpSettingsLayout->addWidget(fpSettingsWidget);
+ fileProviderTab->setLayout(fpSettingsLayout);
+ } else {
+ disguiseTabWidget();
+ }
+#else
+ disguiseTabWidget();
+ _ui->tabWidget->setCurrentIndex(0);
+#endif
+
const auto mouseCursorChanger = new MouseCursorChanger(this);
mouseCursorChanger->folderList = _ui->_folderList;
mouseCursorChanger->model = _model;
@@ -1688,6 +1710,14 @@ void AccountSettings::initializeE2eEncryptionSettingsMessage()
connect(actionEnableE2e, &QAction::triggered, this, &AccountSettings::slotE2eEncryptionGenerateKeys);
}
+void AccountSettings::disguiseTabWidget() const
+{
+ // Ensure all elements of the tab widget are hidden.
+ // Document mode lets the child view take up the whole view.
+ _ui->tabWidget->setDocumentMode(true);
+ _ui->tabWidget->tabBar()->hide();
+}
+
} // namespace OCC
#include "accountsettings.moc"
diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h
index 1ddbc097d413e..117a3aa536a0a 100644
--- a/src/gui/accountsettings.h
+++ b/src/gui/accountsettings.h
@@ -27,6 +27,10 @@
#include "owncloudgui.h"
#include "folderstatusmodel.h"
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "macOS/fileprovidersettingscontroller.h"
+#endif
+
class QModelIndex;
class QNetworkReply;
class QListWidgetItem;
@@ -137,6 +141,8 @@ private slots:
/// Returns the alias of the selected folder, empty string if none
[[nodiscard]] QString selectedFolderAlias() const;
+ void disguiseTabWidget() const;
+
Ui::AccountSettings *_ui;
FolderStatusModel *_model;
diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui
index 77224160c7dc3..bc1594e8f3189 100644
--- a/src/gui/accountsettings.ui
+++ b/src/gui/accountsettings.ui
@@ -6,14 +6,68 @@
0
0
- 588
- 557
+ 1028
+ 871
Form
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ Storage space: …
+
+
+ Qt::PlainText
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 7
+
+
+
+ 100
+
+
+ -1
+
+
+ false
+
+
+
+
+
-
@@ -131,82 +185,9 @@
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
-
-
- Storage space: …
-
-
- Qt::PlainText
-
-
- false
-
-
-
- -
-
-
- false
-
-
-
- 0
- 0
-
-
-
-
- 16777215
- 7
-
-
-
- 100
-
-
- -1
-
-
- false
-
-
-
-
-
-
- -
-
-
-
- 0
- 5
-
-
-
- Qt::CustomContextMenu
-
-
- QAbstractItemView::NoEditTriggers
-
-
- true
-
-
-
-
@@ -276,6 +257,59 @@
+ -
+
+
+ 1
+
+
+
+ Standard file sync
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::CustomContextMenu
+
+
+ false
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ true
+
+
+
+
+
+
+
+ Virtual file sync
+
+
+
+
diff --git a/src/gui/macOS/fileprovider.h b/src/gui/macOS/fileprovider.h
index e9d89b7a5ba8a..215a62dfb2530 100644
--- a/src/gui/macOS/fileprovider.h
+++ b/src/gui/macOS/fileprovider.h
@@ -39,6 +39,9 @@ class FileProvider : public QObject
static bool fileProviderAvailable();
+public slots:
+ void createDebugArchiveForDomain(const QString &domainIdentifier, const QString &filename) const;
+
private slots:
void configureXPC();
diff --git a/src/gui/macOS/fileprovider_mac.mm b/src/gui/macOS/fileprovider_mac.mm
index f45b915569467..94b1d9f0190e6 100644
--- a/src/gui/macOS/fileprovider_mac.mm
+++ b/src/gui/macOS/fileprovider_mac.mm
@@ -12,13 +12,15 @@
* for more details.
*/
-#import
+
+#include "fileprovider.h"
#include
-#include "configfile.h"
+#include "libsync/configfile.h"
+#include "gui/macOS/fileproviderxpc.h"
-#include "fileprovider.h"
+#import
namespace OCC {
@@ -37,10 +39,6 @@
qCInfo(lcMacFileProvider) << "File provider system is not available on this version of macOS.";
deleteLater();
return;
- } else if (!ConfigFile().macFileProviderModuleEnabled()) {
- qCInfo(lcMacFileProvider) << "File provider module is not enabled in application config.";
- deleteLater();
- return;
}
qCInfo(lcMacFileProvider) << "Initialising file provider domain manager.";
@@ -65,9 +63,6 @@
if (!fileProviderAvailable()) {
qCInfo(lcMacFileProvider) << "File provider system is not available on this version of macOS.";
return nullptr;
- } else if (!ConfigFile().macFileProviderModuleEnabled()) {
- qCInfo(lcMacFileProvider) << "File provider module is not enabled in application config.";
- return nullptr;
}
if (!_instance) {
@@ -102,5 +97,10 @@
}
}
+void FileProvider::createDebugArchiveForDomain(const QString &domainIdentifier, const QString &filename) const
+{
+ _xpc->createDebugArchiveForExtension(domainIdentifier, filename);
+}
+
} // namespace Mac
} // namespace OCC
diff --git a/src/gui/macOS/fileproviderdomainmanager.h b/src/gui/macOS/fileproviderdomainmanager.h
index 33347dc5e1a30..8de3fc1f24939 100644
--- a/src/gui/macOS/fileproviderdomainmanager.h
+++ b/src/gui/macOS/fileproviderdomainmanager.h
@@ -33,6 +33,8 @@ class FileProviderDomainManager : public QObject
~FileProviderDomainManager() override;
static AccountStatePtr accountStateFromFileProviderDomainIdentifier(const QString &domainIdentifier);
+ static QString fileProviderDomainIdentifierFromAccountState(const AccountStatePtr &accountState);
+
void start();
signals:
@@ -40,6 +42,7 @@ class FileProviderDomainManager : public QObject
private slots:
void setupFileProviderDomains();
+ void updateFileProviderDomains();
void addFileProviderDomainForAccount(const OCC::AccountState * const accountState);
void removeFileProviderDomainForAccount(const OCC::AccountState * const accountState);
diff --git a/src/gui/macOS/fileproviderdomainmanager_mac.mm b/src/gui/macOS/fileproviderdomainmanager_mac.mm
index d6c9acda6dc4b..331c44df54dbf 100644
--- a/src/gui/macOS/fileproviderdomainmanager_mac.mm
+++ b/src/gui/macOS/fileproviderdomainmanager_mac.mm
@@ -19,6 +19,7 @@
#include "config.h"
#include "fileproviderdomainmanager.h"
+#include "fileprovidersettingscontroller.h"
#include "pushnotifications.h"
#include "gui/accountmanager.h"
@@ -223,6 +224,8 @@ void removeFileProviderDomain(const AccountState * const accountState)
NSFileProviderDomain * const domain = _registeredDomains.take(domainId);
[domain release];
+
+ _registeredDomains.remove(domainId);
}];
}
}
@@ -437,6 +440,9 @@ QStringList configuredDomainIds() const
const auto trReason = tr("%1 application has been closed. Reopen to reconnect.").arg(APPLICATION_NAME);
disconnectFileProviderDomainForAccount(accountState, trReason);
});
+
+ connect(FileProviderSettingsController::instance(), &FileProviderSettingsController::vfsEnabledAccountsChanged,
+ this, &FileProviderDomainManager::updateFileProviderDomains);
}
void FileProviderDomainManager::setupFileProviderDomains()
@@ -446,11 +452,33 @@ QStringList configuredDomainIds() const
}
d->findExistingFileProviderDomains();
+ updateFileProviderDomains();
+}
+
+void FileProviderDomainManager::updateFileProviderDomains()
+{
+ if (!d) {
+ return;
+ }
- for(auto &accountState : AccountManager::instance()->accounts()) {
+ const auto vfsEnabledAccounts = FileProviderSettingsController::instance()->vfsEnabledAccounts();
+ auto configuredDomains = d->configuredDomainIds();
+
+ for (const auto &accountUserIdAtHost : vfsEnabledAccounts) {
+ if (configuredDomains.contains(accountUserIdAtHost)) {
+ configuredDomains.removeAll(accountUserIdAtHost);
+ continue;
+ }
+
+ const auto accountState = AccountManager::instance()->accountFromUserId(accountUserIdAtHost);
addFileProviderDomainForAccount(accountState.data());
}
+ for (const auto &remainingDomainUserId : configuredDomains) {
+ const auto accountState = AccountManager::instance()->accountFromUserId(remainingDomainUserId);
+ removeFileProviderDomainForAccount(accountState.data());
+ }
+
emit domainSetupComplete();
}
@@ -649,6 +677,11 @@ QStringList configuredDomainIds() const
return accountForReceivedDomainIdentifier;
}
+QString FileProviderDomainManager::fileProviderDomainIdentifierFromAccountState(const AccountStatePtr &accountState)
+{
+ return domainIdentifierForAccount(accountState->account());
+}
+
} // namespace Mac
} // namespace OCC
diff --git a/src/gui/macOS/fileproviderdomainsyncstatus.h b/src/gui/macOS/fileproviderdomainsyncstatus.h
new file mode 100644
index 0000000000000..b6b1256acf9ec
--- /dev/null
+++ b/src/gui/macOS/fileproviderdomainsyncstatus.h
@@ -0,0 +1,98 @@
+/*
+* Copyright (C) 2024 by Claudio Cambra
+*
+* 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.
+*/
+
+#include
+#include
+
+#pragma once
+
+namespace OCC::Mac
+{
+
+class FileProviderDomainSyncStatus : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_UNCREATABLE("FileProviderDomainSyncStatus cannot be instantiated from QML")
+ Q_PROPERTY(bool syncing READ syncing NOTIFY syncingChanged)
+ Q_PROPERTY(bool downloading READ downloading NOTIFY downloadingChanged)
+ Q_PROPERTY(bool uploading READ uploading NOTIFY uploadingChanged)
+ Q_PROPERTY(double fractionCompleted READ fractionCompleted NOTIFY fractionCompletedChanged)
+ Q_PROPERTY(double downloadFractionCompleted READ downloadFractionCompleted NOTIFY downloadFractionCompletedChanged)
+ Q_PROPERTY(double uploadFractionCompleted READ uploadFractionCompleted NOTIFY uploadFractionCompletedChanged)
+ Q_PROPERTY(int downloadFileTotalCount READ downloadFileTotalCount NOTIFY downloadFileTotalCountChanged)
+ Q_PROPERTY(int downloadFileCompletedCount READ downloadFileCompletedCount NOTIFY downloadFileCompletedCountChanged)
+ Q_PROPERTY(int uploadFileTotalCount READ uploadFileTotalCount NOTIFY uploadFileTotalCountChanged)
+ Q_PROPERTY(int uploadFileCompletedCount READ uploadFileCompletedCount NOTIFY uploadFileCompletedCountChanged)
+ // TODO: more detailed reporting (time remaining, megabytes, etc.)
+ Q_PROPERTY(QUrl icon READ icon NOTIFY iconChanged)
+
+public:
+ explicit FileProviderDomainSyncStatus(const QString &domainIdentifier, QObject *parent = nullptr);
+ ~FileProviderDomainSyncStatus() override;
+
+ [[nodiscard]] bool syncing() const;
+ [[nodiscard]] bool downloading() const;
+ [[nodiscard]] bool uploading() const;
+ [[nodiscard]] double fractionCompleted() const;
+ [[nodiscard]] double downloadFractionCompleted() const;
+ [[nodiscard]] double uploadFractionCompleted() const;
+ [[nodiscard]] int downloadFileTotalCount() const;
+ [[nodiscard]] int downloadFileCompletedCount() const;
+ [[nodiscard]] int uploadFileTotalCount() const;
+ [[nodiscard]] int uploadFileCompletedCount() const;
+ [[nodiscard]] QUrl icon() const;
+
+signals:
+ void syncingChanged(bool syncing);
+ void downloadingChanged(bool downloading);
+ void uploadingChanged(bool uploading);
+ void fractionCompletedChanged(double fractionCompleted);
+ void downloadFractionCompletedChanged(double downloadFractionCompleted);
+ void uploadFractionCompletedChanged(double uploadFractionCompleted);
+ void downloadFileTotalCountChanged(int downloadFileTotalCount);
+ void downloadFileCompletedCountChanged(int downloadFileCompletedCount);
+ void uploadFileTotalCountChanged(int uploadFileTotalCount);
+ void uploadFileCompletedCountChanged(int uploadFileCompletedCount);
+ void iconChanged(const QUrl &icon);
+
+private:
+ void setDownloading(const bool syncing);
+ void setUploading(const bool syncing);
+ void setDownloadFractionCompleted(const double fractionCompleted);
+ void setUploadFractionCompleted(const double fractionCompleted);
+ void setDownloadFileTotalCount(const int fileTotalCount);
+ void setDownloadFileCompletedCount(const int fileCompletedCount);
+ void setUploadFileTotalCount(const int fileTotalCount);
+ void setUploadFileCompletedCount(const int fileCompletedCount);
+ void setIcon(const QUrl &icon);
+ void updateIcon();
+
+ bool _downloading = false;
+ bool _uploading = false;
+ double _downloadFractionCompleted = 0.0;
+ double _uploadFractionCompleted = 0.0;
+ int _downloadFileTotalCount = 0;
+ int _downloadFileCompletedCount = 0;
+ int _uploadFileTotalCount = 0;
+ int _uploadFileCompletedCount = 0;
+ QUrl _icon;
+
+ class MacImplementation;
+ std::unique_ptr d;
+};
+
+} // OCC::Mac
+
+Q_DECLARE_METATYPE(OCC::Mac::FileProviderDomainSyncStatus*)
diff --git a/src/gui/macOS/fileproviderdomainsyncstatus_mac.mm b/src/gui/macOS/fileproviderdomainsyncstatus_mac.mm
new file mode 100644
index 0000000000000..6cc7efd42ae4c
--- /dev/null
+++ b/src/gui/macOS/fileproviderdomainsyncstatus_mac.mm
@@ -0,0 +1,261 @@
+/*
+* Copyright (C) 2024 by Claudio Cambra
+*
+* 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.
+*/
+
+#include "fileproviderdomainsyncstatus.h"
+
+#include
+
+#include "gui/macOS/fileproviderutils.h"
+#include "libsync/theme.h"
+
+#import
+
+#import "gui/macOS/progressobserver.h"
+
+namespace OCC::Mac
+{
+
+Q_LOGGING_CATEGORY(lcMacFileProviderDomainSyncStatus, "nextcloud.gui.macfileproviderdomainsyncstatus", QtInfoMsg)
+
+class FileProviderDomainSyncStatus::MacImplementation
+{
+public:
+ explicit MacImplementation(const QString &domainIdentifier, FileProviderDomainSyncStatus *parent)
+ : q(parent)
+ {
+ _domain = FileProviderUtils::domainForIdentifier(domainIdentifier);
+ _manager = [NSFileProviderManager managerForDomain:_domain];
+
+ if (_manager == nil) {
+ qCWarning(lcMacFileProviderDomainSyncStatus) << "Could not get manager for domain" << domainIdentifier;
+ return;
+ }
+
+ NSProgress *const downloadProgress = [_manager globalProgressForKind:NSProgressFileOperationKindDownloading];
+ NSProgress *const uploadProgress = [_manager globalProgressForKind:NSProgressFileOperationKindUploading];
+ _downloadProgressObserver = [[ProgressObserver alloc] initWithProgress:downloadProgress];
+ _uploadProgressObserver = [[ProgressObserver alloc] initWithProgress:uploadProgress];
+
+ _downloadProgressObserver.progressKVOChangeHandler = ^(NSProgress *const progress){
+ updateDownload(progress);
+ };
+ _uploadProgressObserver.progressKVOChangeHandler = ^(NSProgress *const progress){
+ updateUpload(progress);
+ };
+ }
+
+ ~MacImplementation() = default;
+
+ void updateDownload(NSProgress *const progress) const
+ {
+ qCInfo(lcMacFileProviderDomainSyncStatus) << "Download progress changed" << progress.localizedDescription;
+ if (progress == nil || q == nullptr) {
+ return;
+ }
+
+ q->setDownloading(!progress.paused && !progress.cancelled && !progress.finished);
+ q->setDownloadFractionCompleted(progress.fractionCompleted);
+ q->setDownloadFileTotalCount(progress.fileTotalCount.intValue);
+ q->setDownloadFileCompletedCount(progress.fileCompletedCount.intValue);
+ q->updateIcon();
+ }
+
+ void updateUpload(NSProgress *const progress) const
+ {
+ qCInfo(lcMacFileProviderDomainSyncStatus) << "Upload progress changed" << progress.localizedDescription;
+ if (progress == nil || q == nullptr) {
+ return;
+ }
+
+ q->setUploading(!progress.paused && !progress.cancelled && !progress.finished);
+ q->setUploadFractionCompleted(progress.fractionCompleted);
+ q->setUploadFileTotalCount(progress.fileTotalCount.intValue);
+ q->setUploadFileCompletedCount(progress.fileCompletedCount.intValue);
+ q->updateIcon();
+ }
+
+private:
+ NSFileProviderDomain *_domain = nil;
+ NSFileProviderManager *_manager = nil;
+ ProgressObserver *_downloadProgressObserver = nullptr;
+ ProgressObserver *_uploadProgressObserver = nullptr;
+ FileProviderDomainSyncStatus *q = nullptr;
+};
+
+FileProviderDomainSyncStatus::FileProviderDomainSyncStatus(const QString &domainIdentifier, QObject *parent)
+ : QObject(parent)
+ , d(std::make_unique(domainIdentifier, this))
+{
+ qRegisterMetaType("FileProviderDomainSyncStatus*");
+ updateIcon();
+}
+
+FileProviderDomainSyncStatus::~FileProviderDomainSyncStatus() = default;
+
+bool FileProviderDomainSyncStatus::syncing() const
+{
+ return downloading() || uploading();
+}
+
+bool FileProviderDomainSyncStatus::downloading() const
+{
+ return _downloading;
+}
+
+bool FileProviderDomainSyncStatus::uploading() const
+{
+ return _uploading;
+}
+
+double FileProviderDomainSyncStatus::fractionCompleted() const
+{
+ return (downloadFractionCompleted() + uploadFractionCompleted()) / 2;
+}
+
+double FileProviderDomainSyncStatus::downloadFractionCompleted() const
+{
+ return _downloadFractionCompleted;
+}
+
+double FileProviderDomainSyncStatus::uploadFractionCompleted() const
+{
+ return _uploadFractionCompleted;
+}
+
+int FileProviderDomainSyncStatus::downloadFileTotalCount() const
+{
+ return _downloadFileTotalCount;
+}
+
+int FileProviderDomainSyncStatus::downloadFileCompletedCount() const
+{
+ return _downloadFileCompletedCount;
+}
+
+int FileProviderDomainSyncStatus::uploadFileTotalCount() const
+{
+ return _uploadFileTotalCount;
+}
+
+int FileProviderDomainSyncStatus::uploadFileCompletedCount() const
+{
+ return _uploadFileCompletedCount;
+}
+
+QUrl FileProviderDomainSyncStatus::icon() const
+{
+ return _icon;
+}
+
+void FileProviderDomainSyncStatus::setDownloading(const bool downloading)
+{
+ if (_downloading == downloading) {
+ return;
+ }
+
+ _downloading = downloading;
+ emit downloadingChanged(_downloading);
+ emit syncingChanged(syncing());
+}
+
+void FileProviderDomainSyncStatus::setUploading(const bool uploading)
+{
+ if (_uploading == uploading) {
+ return;
+ }
+
+ _uploading = uploading;
+ emit uploadingChanged(_uploading);
+ emit syncingChanged(syncing());
+}
+
+void FileProviderDomainSyncStatus::setDownloadFractionCompleted(const double downloadFractionCompleted)
+{
+ if (_downloadFractionCompleted == downloadFractionCompleted) {
+ return;
+ }
+
+ _downloadFractionCompleted = downloadFractionCompleted;
+ emit downloadFractionCompletedChanged(_downloadFractionCompleted);
+ emit fractionCompletedChanged(fractionCompleted());
+}
+
+void FileProviderDomainSyncStatus::setUploadFractionCompleted(const double uploadFractionCompleted)
+{
+ if (_uploadFractionCompleted == uploadFractionCompleted) {
+ return;
+ }
+
+ _uploadFractionCompleted = uploadFractionCompleted;
+ emit uploadFractionCompletedChanged(_uploadFractionCompleted);
+ emit fractionCompletedChanged(fractionCompleted());
+}
+
+void FileProviderDomainSyncStatus::setDownloadFileTotalCount(const int fileTotalCount)
+{
+ if (_downloadFileTotalCount == fileTotalCount) {
+ return;
+ }
+
+ _downloadFileTotalCount = fileTotalCount;
+ emit downloadFileTotalCountChanged(_downloadFileTotalCount);
+}
+
+void FileProviderDomainSyncStatus::setDownloadFileCompletedCount(const int fileCompletedCount)
+{
+ if (_downloadFileCompletedCount == fileCompletedCount) {
+ return;
+ }
+
+ _downloadFileCompletedCount = fileCompletedCount;
+ emit downloadFileCompletedCountChanged(_downloadFileCompletedCount);
+}
+
+void FileProviderDomainSyncStatus::setUploadFileTotalCount(const int fileTotalCount)
+{
+ if (_uploadFileTotalCount == fileTotalCount) {
+ return;
+ }
+
+ _uploadFileTotalCount = fileTotalCount;
+ emit uploadFileTotalCountChanged(_uploadFileTotalCount);
+}
+
+void FileProviderDomainSyncStatus::setUploadFileCompletedCount(const int fileCompletedCount)
+{
+ if (_uploadFileCompletedCount == fileCompletedCount) {
+ return;
+ }
+
+ _uploadFileCompletedCount = fileCompletedCount;
+ emit uploadFileCompletedCountChanged(_uploadFileCompletedCount);
+}
+
+void FileProviderDomainSyncStatus::setIcon(const QUrl &icon)
+{
+ if (_icon == icon) {
+ return;
+ }
+
+ _icon = icon;
+ emit iconChanged(_icon);
+}
+
+void FileProviderDomainSyncStatus::updateIcon()
+{
+ const auto iconUrl = syncing() ? Theme::instance()->syncStatusRunning() : Theme::instance()->syncStatusOk();
+ setIcon(iconUrl);
+}
+
+} // OCC::Mac
diff --git a/src/gui/macOS/fileprovideritemmetadata.cpp b/src/gui/macOS/fileprovideritemmetadata.cpp
new file mode 100644
index 0000000000000..2219a22262d7b
--- /dev/null
+++ b/src/gui/macOS/fileprovideritemmetadata.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileprovideritemmetadata.h"
+
+namespace OCC {
+
+namespace Mac {
+
+QString FileProviderItemMetadata::identifier() const
+{
+ return _identifier;
+}
+
+QString FileProviderItemMetadata::parentItemIdentifier() const
+{
+ return _parentItemIdentifier;
+}
+
+QString FileProviderItemMetadata::domainIdentifier() const
+{
+ return _domainIdentifier;
+}
+
+QString FileProviderItemMetadata::filename() const
+{
+ return _filename;
+}
+
+QString FileProviderItemMetadata::typeIdentifier() const
+{
+ return _typeIdentifier;
+}
+
+QString FileProviderItemMetadata::symlinkTargetPath() const
+{
+ return _symlinkTargetPath;
+}
+
+QString FileProviderItemMetadata::uploadingError() const
+{
+ return _uploadingError;
+}
+
+QString FileProviderItemMetadata::downloadingError() const
+{
+ return _downloadingError;
+}
+
+QString FileProviderItemMetadata::mostRecentEditorName() const
+{
+ return _mostRecentEditorName;
+}
+
+QString FileProviderItemMetadata::ownerName() const
+{
+ return _ownerName;
+}
+
+QDateTime FileProviderItemMetadata::contentModificationDate() const
+{
+ return _contentModificationDate;
+}
+
+QDateTime FileProviderItemMetadata::creationDate() const
+{
+ return _creationDate;
+}
+
+QDateTime FileProviderItemMetadata::lastUsedDate() const
+{
+ return _lastUsedDate;
+}
+
+QByteArray FileProviderItemMetadata::contentVersion() const
+{
+ return _contentVersion;
+}
+
+QByteArray FileProviderItemMetadata::metadataVersion() const
+{
+ return _metadataVersion;
+}
+
+QByteArray FileProviderItemMetadata::tagData() const
+{
+ return _tagData;
+}
+
+QHash FileProviderItemMetadata::extendedAttributes() const
+{
+ return _extendedAttributes;
+}
+
+int FileProviderItemMetadata::capabilities() const
+{
+ return _capabilities;
+}
+
+int FileProviderItemMetadata::fileSystemFlags() const
+{
+ return _fileSystemFlags;
+}
+
+unsigned int FileProviderItemMetadata::childItemCount() const
+{
+ return _childItemCount;
+}
+
+unsigned int FileProviderItemMetadata::typeOsCode() const
+{
+ return _typeOsCode;
+}
+
+unsigned int FileProviderItemMetadata::creatorOsCode() const
+{
+ return _creatorOsCode;
+}
+
+unsigned long long FileProviderItemMetadata::documentSize() const
+{
+ return _documentSize;
+}
+
+bool FileProviderItemMetadata::mostRecentVersionDownloaded() const
+{
+ return _mostRecentVersionDownloaded;
+}
+
+bool FileProviderItemMetadata::uploading() const
+{
+ return _uploading;
+}
+
+bool FileProviderItemMetadata::uploaded() const
+{
+ return _uploaded;
+}
+
+bool FileProviderItemMetadata::downloading() const
+{
+ return _downloading;
+}
+
+bool FileProviderItemMetadata::downloaded() const
+{
+ return _downloaded;
+}
+
+bool FileProviderItemMetadata::shared() const
+{
+ return _shared;
+}
+
+bool FileProviderItemMetadata::sharedByCurrentUser() const
+{
+ return _sharedByCurrentUser;
+}
+
+QString FileProviderItemMetadata::userVisiblePath() const
+{
+ return _userVisiblePath;
+}
+
+QString FileProviderItemMetadata::fileTypeString() const
+{
+ return _fileTypeString;
+}
+
+bool operator==(const FileProviderItemMetadata &lhs, const FileProviderItemMetadata &rhs)
+{
+ return lhs.identifier() == rhs.identifier() &&
+ lhs.contentVersion() == rhs.contentVersion() &&
+ lhs.metadataVersion() == rhs.metadataVersion();
+}
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileprovideritemmetadata.h b/src/gui/macOS/fileprovideritemmetadata.h
new file mode 100644
index 0000000000000..e3e0b0a0cd232
--- /dev/null
+++ b/src/gui/macOS/fileprovideritemmetadata.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+namespace OCC {
+
+namespace Mac {
+
+class FileProviderItemMetadata
+{
+ Q_GADGET
+
+ Q_PROPERTY(QString identifier READ identifier CONSTANT)
+ Q_PROPERTY(QString parentItemIdentifier READ parentItemIdentifier CONSTANT)
+ Q_PROPERTY(QString domainIdentifier READ domainIdentifier CONSTANT)
+ Q_PROPERTY(QString filename READ filename CONSTANT)
+ Q_PROPERTY(QString typeIdentifier READ typeIdentifier CONSTANT)
+ Q_PROPERTY(QString symlinkTargetPath READ symlinkTargetPath CONSTANT)
+ Q_PROPERTY(QString uploadingError READ uploadingError CONSTANT)
+ Q_PROPERTY(QString downloadingError READ downloadingError CONSTANT)
+ Q_PROPERTY(QString mostRecentEditorName READ mostRecentEditorName CONSTANT)
+ Q_PROPERTY(QString ownerName READ ownerName CONSTANT)
+ Q_PROPERTY(QDateTime contentModificationDate READ contentModificationDate CONSTANT)
+ Q_PROPERTY(QDateTime creationDate READ creationDate CONSTANT)
+ Q_PROPERTY(QDateTime lastUsedDate READ lastUsedDate CONSTANT)
+ Q_PROPERTY(QByteArray contentVersion READ contentVersion CONSTANT)
+ Q_PROPERTY(QByteArray metadataVersion READ metadataVersion CONSTANT)
+ Q_PROPERTY(QByteArray tagData READ tagData CONSTANT)
+ Q_PROPERTY(QHash extendedAttributes READ extendedAttributes CONSTANT)
+ Q_PROPERTY(int capabilities READ capabilities CONSTANT)
+ Q_PROPERTY(int fileSystemFlags READ fileSystemFlags CONSTANT)
+ Q_PROPERTY(unsigned int childItemCount READ childItemCount CONSTANT)
+ Q_PROPERTY(unsigned int typeOsCode READ typeOsCode CONSTANT)
+ Q_PROPERTY(unsigned int creatorOsCode READ creatorOsCode CONSTANT)
+ Q_PROPERTY(unsigned long long documentSize READ documentSize CONSTANT)
+ Q_PROPERTY(bool mostRecentVersionDownloaded READ mostRecentVersionDownloaded CONSTANT)
+ Q_PROPERTY(bool uploading READ uploading CONSTANT)
+ Q_PROPERTY(bool uploaded READ uploaded CONSTANT)
+ Q_PROPERTY(bool downloading READ downloading CONSTANT)
+ Q_PROPERTY(bool downloaded READ downloaded CONSTANT)
+ Q_PROPERTY(bool shared READ shared CONSTANT)
+ Q_PROPERTY(bool sharedByCurrentUser READ sharedByCurrentUser CONSTANT)
+
+ Q_PROPERTY(QString userVisiblePath READ userVisiblePath CONSTANT)
+
+public:
+ static FileProviderItemMetadata fromNSFileProviderItem(const void *const nsFileProviderItem, const QString &domainIdentifier);
+
+ [[nodiscard]] QString identifier() const;
+ [[nodiscard]] QString parentItemIdentifier() const;
+ [[nodiscard]] QString domainIdentifier() const;
+ [[nodiscard]] QString filename() const;
+ [[nodiscard]] QString typeIdentifier() const;
+ [[nodiscard]] QString symlinkTargetPath() const;
+ [[nodiscard]] QString uploadingError() const;
+ [[nodiscard]] QString downloadingError() const;
+ [[nodiscard]] QString mostRecentEditorName() const;
+ [[nodiscard]] QString ownerName() const;
+ [[nodiscard]] QDateTime contentModificationDate() const;
+ [[nodiscard]] QDateTime creationDate() const;
+ [[nodiscard]] QDateTime lastUsedDate() const;
+ [[nodiscard]] QByteArray contentVersion() const;
+ [[nodiscard]] QByteArray metadataVersion() const;
+ [[nodiscard]] QByteArray tagData() const;
+ [[nodiscard]] QHash extendedAttributes() const;
+ [[nodiscard]] int capabilities() const;
+ [[nodiscard]] int fileSystemFlags() const;
+ [[nodiscard]] unsigned int childItemCount() const;
+ [[nodiscard]] unsigned int typeOsCode() const;
+ [[nodiscard]] unsigned int creatorOsCode() const;
+ [[nodiscard]] unsigned long long documentSize() const;
+ [[nodiscard]] bool mostRecentVersionDownloaded() const;
+ [[nodiscard]] bool uploading() const;
+ [[nodiscard]] bool uploaded() const;
+ [[nodiscard]] bool downloading() const;
+ [[nodiscard]] bool downloaded() const;
+ [[nodiscard]] bool shared() const;
+ [[nodiscard]] bool sharedByCurrentUser() const;
+
+ [[nodiscard]] QString userVisiblePath() const;
+ [[nodiscard]] QString fileTypeString() const;
+
+ // Check equality via identifier, contentVersion, and metadataVersion
+ friend bool operator==(const FileProviderItemMetadata &lhs, const FileProviderItemMetadata &rhs);
+
+private:
+ [[nodiscard]] QString getUserVisiblePath() const;
+
+ QString _identifier;
+ QString _parentItemIdentifier;
+ QString _domainIdentifier;
+ QString _filename;
+ QString _typeIdentifier;
+ QString _symlinkTargetPath;
+ QString _uploadingError;
+ QString _downloadingError;
+ QString _mostRecentEditorName;
+ QString _ownerName;
+ QDateTime _contentModificationDate;
+ QDateTime _creationDate;
+ QDateTime _lastUsedDate;
+ QByteArray _contentVersion;
+ QByteArray _metadataVersion;
+ QByteArray _tagData;
+ QHash _extendedAttributes;
+ quint64 _favoriteRank = 0;
+ int _capabilities = 0;
+ int _fileSystemFlags = 0;
+ unsigned int _childItemCount = 0;
+ unsigned int _typeOsCode = 0;
+ unsigned int _creatorOsCode = 0;
+ unsigned long long _documentSize = 0;
+ bool _mostRecentVersionDownloaded = false;
+ bool _uploading = false;
+ bool _uploaded = false;
+ bool _downloading = false;
+ bool _downloaded = false;
+ bool _shared = false;
+ bool _sharedByCurrentUser = false;
+ bool _trashed = false;
+
+ QString _userVisiblePath;
+ QString _fileTypeString;
+};
+
+}
+
+}
diff --git a/src/gui/macOS/fileprovideritemmetadata_mac.mm b/src/gui/macOS/fileprovideritemmetadata_mac.mm
new file mode 100644
index 0000000000000..d8e64abb68832
--- /dev/null
+++ b/src/gui/macOS/fileprovideritemmetadata_mac.mm
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileprovideritemmetadata.h"
+
+#include
+#include
+
+#import
+#import
+#import
+
+#include "fileproviderutils.h"
+
+namespace {
+
+QString nsNameComponentsToLocalisedQString(NSPersonNameComponents *const nameComponents)
+{
+ if (nameComponents == nil) {
+ return {};
+ }
+
+ NSString *const name = [NSPersonNameComponentsFormatter localizedStringFromPersonNameComponents:nameComponents style:NSPersonNameComponentsFormatterStyleDefault options:0];
+ return QString::fromNSString(name);
+}
+
+QHash extendedAttributesToHash(NSDictionary *const extendedAttributes)
+{
+ QHash hash;
+ for (NSString *const key in extendedAttributes) {
+ NSData *const value = [extendedAttributes objectForKey:key];
+ hash.insert(QString::fromNSString(key), QByteArray::fromNSData(value));
+ }
+ return hash;
+}
+
+}
+
+namespace OCC {
+
+namespace Mac {
+
+Q_LOGGING_CATEGORY(lcMacImplFileProviderItemMetadata, "nextcloud.gui.macfileprovideritemmetadatamacimpl", QtInfoMsg)
+
+FileProviderItemMetadata FileProviderItemMetadata::fromNSFileProviderItem(const void *const nsFileProviderItem, const QString &domainIdentifier)
+{
+ FileProviderItemMetadata metadata;
+ const id bridgedNsFileProviderItem = (__bridge id)nsFileProviderItem;
+ if (bridgedNsFileProviderItem == nil) {
+ return {};
+ }
+
+ metadata._identifier = QString::fromNSString(bridgedNsFileProviderItem.itemIdentifier);
+ metadata._parentItemIdentifier = QString::fromNSString(bridgedNsFileProviderItem.parentItemIdentifier);
+ metadata._domainIdentifier = domainIdentifier;
+ metadata._filename = QString::fromNSString(bridgedNsFileProviderItem.filename);
+ metadata._typeIdentifier = QString::fromNSString(bridgedNsFileProviderItem.contentType.identifier);
+ metadata._symlinkTargetPath = QString::fromNSString(bridgedNsFileProviderItem.symlinkTargetPath);
+ metadata._uploadingError = QString::fromNSString(bridgedNsFileProviderItem.uploadingError.localizedDescription);
+ metadata._downloadingError = QString::fromNSString(bridgedNsFileProviderItem.downloadingError.localizedDescription);
+ metadata._mostRecentEditorName = nsNameComponentsToLocalisedQString(bridgedNsFileProviderItem.mostRecentEditorNameComponents);
+ metadata._ownerName = nsNameComponentsToLocalisedQString(bridgedNsFileProviderItem.ownerNameComponents);
+ metadata._contentModificationDate = QDateTime::fromNSDate(bridgedNsFileProviderItem.contentModificationDate);
+ metadata._creationDate = QDateTime::fromNSDate(bridgedNsFileProviderItem.creationDate);
+ metadata._lastUsedDate = QDateTime::fromNSDate(bridgedNsFileProviderItem.lastUsedDate);
+ metadata._contentVersion = QByteArray::fromNSData(bridgedNsFileProviderItem.itemVersion.contentVersion);
+ metadata._metadataVersion = QByteArray::fromNSData(bridgedNsFileProviderItem.itemVersion.metadataVersion);
+ metadata._tagData = QByteArray::fromNSData(bridgedNsFileProviderItem.tagData);
+ metadata._extendedAttributes = extendedAttributesToHash(bridgedNsFileProviderItem.extendedAttributes);
+ metadata._capabilities = bridgedNsFileProviderItem.capabilities;
+ metadata._fileSystemFlags = bridgedNsFileProviderItem.fileSystemFlags;
+ metadata._childItemCount = bridgedNsFileProviderItem.childItemCount.unsignedIntegerValue;
+ metadata._typeOsCode = bridgedNsFileProviderItem.typeAndCreator.type;
+ metadata._creatorOsCode = bridgedNsFileProviderItem.typeAndCreator.creator;
+ metadata._documentSize = bridgedNsFileProviderItem.documentSize.unsignedLongLongValue;
+ metadata._mostRecentVersionDownloaded = bridgedNsFileProviderItem.mostRecentVersionDownloaded;
+ metadata._uploading = bridgedNsFileProviderItem.uploading;
+ metadata._uploaded = bridgedNsFileProviderItem.uploaded;
+ metadata._downloading = bridgedNsFileProviderItem.downloading;
+ metadata._downloaded = bridgedNsFileProviderItem.downloaded;
+ metadata._shared = bridgedNsFileProviderItem.shared;
+ metadata._sharedByCurrentUser = bridgedNsFileProviderItem.sharedByCurrentUser;
+
+ metadata._userVisiblePath = metadata.getUserVisiblePath();
+ metadata._fileTypeString = QString::fromNSString(bridgedNsFileProviderItem.contentType.localizedDescription);
+
+ if (metadata._documentSize == 0) {
+ // If the document size is 0, we can try to get the size of the file
+ // directly from its path. These are all materialised files anyway
+ // so the size will be properly represented
+ const auto path = metadata.userVisiblePath();
+ const auto fileInfo = QFileInfo(path);
+ metadata._documentSize = fileInfo.size();
+ }
+
+ return metadata;
+}
+
+QString FileProviderItemMetadata::getUserVisiblePath() const
+{
+ qCDebug(lcMacImplFileProviderItemMetadata) << "Getting user visible path";
+
+ const auto id = identifier();
+ const auto domainId = domainIdentifier();
+
+ if (id.isEmpty() || domainId.isEmpty()) {
+ qCWarning(lcMacImplFileProviderItemMetadata) << "Could not fetch user visible path for item, no identifier or domainIdentifier";
+ return QStringLiteral("Unknown");
+ }
+
+ __block QString returnPath = QObject::tr("Unknown");
+ NSFileProviderManager *manager = FileProviderUtils::managerForDomainIdentifier(domainId);
+
+ if (manager == nil) {
+ qCWarning(lcMacImplFileProviderItemMetadata) << "Null manager, cannot get item path";
+ return returnPath;
+ }
+
+ NSString *const nsItemIdentifier = id.toNSString();
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+
+ // getUserVisibleUrl is async, so wait here
+
+ [manager getUserVisibleURLForItemIdentifier:nsItemIdentifier
+ completionHandler:^(NSURL *const userVisibleFile, NSError *const error) {
+
+ if (error != nil) {
+ qCWarning(lcMacImplFileProviderItemMetadata) << "Error fetching user visible url for item identifier." << error.localizedDescription;
+ } else {
+ returnPath = QString::fromNSString(userVisibleFile.path);
+ }
+
+ dispatch_semaphore_signal(semaphore);
+ }];
+
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
+ dispatch_release(semaphore);
+
+ return returnPath;
+}
+
+}
+
+}
diff --git a/src/gui/macOS/fileprovidermaterialiseditemsmodel.cpp b/src/gui/macOS/fileprovidermaterialiseditemsmodel.cpp
new file mode 100644
index 0000000000000..2e18666c39b67
--- /dev/null
+++ b/src/gui/macOS/fileprovidermaterialiseditemsmodel.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2023 (c) Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileprovidermaterialiseditemsmodel.h"
+
+#include
+
+namespace OCC {
+
+namespace Mac {
+
+FileProviderMaterialisedItemsModel::FileProviderMaterialisedItemsModel(QObject * const parent)
+ : QAbstractListModel(parent)
+{
+}
+
+int FileProviderMaterialisedItemsModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return _items.count();
+}
+
+QVariant FileProviderMaterialisedItemsModel::data(const QModelIndex &index, int role) const
+{
+ const auto item = _items.at(index.row());
+
+ switch (role) {
+ case Qt::DisplayRole:
+ case FilenameRole:
+ return item.filename();
+ case IdentifierRole:
+ return item.identifier();
+ case ParentItemIdentifierRole:
+ return item.parentItemIdentifier();
+ case DomainIdentifierRole:
+ return item.domainIdentifier();
+ case TypeIdentifierRole:
+ return item.typeIdentifier();
+ case SymlinkTargetPathRole:
+ return item.symlinkTargetPath();
+ case UploadingErrorRole:
+ return item.uploadingError();
+ case DownloadingErrorRole:
+ return item.downloadingError();
+ case MostRecentEditorNameRole:
+ return item.mostRecentEditorName();
+ case OwnerNameRole:
+ return item.ownerName();
+ case ContentModificationDateRole:
+ return item.contentModificationDate();
+ case CreationDateRole:
+ return item.creationDate();
+ case LastUsedDateRole:
+ return item.lastUsedDate();
+ case ContentVersionRole:
+ return item.contentVersion();
+ case MetadataVersionRole:
+ return item.metadataVersion();
+ case TagDataRole:
+ return item.tagData();
+ case CapabilitiesRole:
+ return item.capabilities();
+ case FileSystemFlagsRole:
+ return item.fileSystemFlags();
+ case ChildItemCountRole:
+ return item.childItemCount();
+ case TypeOsCodeRole:
+ return item.typeOsCode();
+ case CreatorOsCodeRole:
+ return item.creatorOsCode();
+ case DocumentSizeRole:
+ return item.documentSize();
+ case MostRecentVersionDownloadedRole:
+ return item.mostRecentVersionDownloaded();
+ case UploadingRole:
+ return item.uploading();
+ case UploadedRole:
+ return item.uploaded();
+ case DownloadingRole:
+ return item.downloading();
+ case DownloadedRole:
+ return item.downloaded();
+ case SharedRole:
+ return item.shared();
+ case SharedByCurrentUserRole:
+ return item.sharedByCurrentUser();
+ case UserVisiblePathRole:
+ return item.userVisiblePath();
+ case FileTypeStringRole:
+ return item.fileTypeString();
+ case FileSizeStringRole:
+ return _locale.formattedDataSize(item.documentSize());
+ }
+ return {};
+}
+
+QHash FileProviderMaterialisedItemsModel::roleNames() const
+{
+ auto roleNames = QAbstractListModel::roleNames();
+ roleNames.insert({
+ { IdentifierRole, "identifier" },
+ { ParentItemIdentifierRole, "parentItemIdentifier" },
+ { DomainIdentifierRole, "domainIdentifier" },
+ { FilenameRole, "fileName" },
+ { TypeIdentifierRole, "typeIdentifier" },
+ { SymlinkTargetPathRole, "symlinkTargetPath" },
+ { UploadingErrorRole, "uploadingError" },
+ { DownloadingErrorRole, "downloadingError" },
+ { MostRecentEditorNameRole, "mostRecentEditorName" },
+ { OwnerNameRole, "ownerName" },
+ { ContentModificationDateRole, "contentModificationDate" },
+ { CreationDateRole, "creationDate" },
+ { LastUsedDateRole, "lastUsedDate" },
+ { ContentVersionRole, "contentVersion" },
+ { MetadataVersionRole, "metadataVersion" },
+ { TagDataRole, "tagData" },
+ { CapabilitiesRole, "capabilities" },
+ { FileSystemFlagsRole, "fileSystemFlags" },
+ { ChildItemCountRole, "childItemCount" },
+ { TypeOsCodeRole, "typeOsCode" },
+ { CreatorOsCodeRole, "creatorOsCode" },
+ { DocumentSizeRole, "documentSize" },
+ { MostRecentVersionDownloadedRole, "mostRecentVersionDownloaded" },
+ { UploadingRole, "uploading" },
+ { UploadedRole, "uploaded" },
+ { DownloadingRole, "downloading" },
+ { DownloadedRole, "downloaded" },
+ { SharedRole, "shared" },
+ { SharedByCurrentUserRole, "sharedByCurrentUser" },
+ { UserVisiblePathRole, "userVisiblePath" },
+ { FileTypeStringRole, "fileTypeString" },
+ { FileSizeStringRole, "fileSizeString" },
+ });
+ return roleNames;
+}
+
+QVector FileProviderMaterialisedItemsModel::items() const
+{
+ return _items;
+}
+
+void FileProviderMaterialisedItemsModel::setItems(const QVector &items)
+{
+ if (items == _items) {
+ return;
+ }
+
+ beginResetModel();
+ _items = items;
+ endResetModel();
+
+ Q_EMIT itemsChanged();
+}
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileprovidermaterialiseditemsmodel.h b/src/gui/macOS/fileprovidermaterialiseditemsmodel.h
new file mode 100644
index 0000000000000..3d34dd9f2b7fe
--- /dev/null
+++ b/src/gui/macOS/fileprovidermaterialiseditemsmodel.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 (c) Claudio Cambra
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "gui/macOS/fileprovideritemmetadata.h"
+
+namespace OCC {
+
+namespace Mac {
+
+class FileProviderMaterialisedItemsModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QVector items READ items WRITE setItems NOTIFY itemsChanged)
+
+public:
+ enum Roles {
+ IdentifierRole = Qt::UserRole + 1,
+ ParentItemIdentifierRole,
+ DomainIdentifierRole,
+ FilenameRole,
+ TypeIdentifierRole,
+ SymlinkTargetPathRole,
+ UploadingErrorRole,
+ DownloadingErrorRole,
+ MostRecentEditorNameRole,
+ OwnerNameRole,
+ ContentModificationDateRole,
+ CreationDateRole,
+ LastUsedDateRole,
+ ContentVersionRole,
+ MetadataVersionRole,
+ TagDataRole,
+ CapabilitiesRole,
+ FileSystemFlagsRole,
+ ChildItemCountRole,
+ TypeOsCodeRole,
+ CreatorOsCodeRole,
+ DocumentSizeRole,
+ MostRecentVersionDownloadedRole,
+ UploadingRole,
+ UploadedRole,
+ DownloadingRole,
+ DownloadedRole,
+ SharedRole,
+ SharedByCurrentUserRole,
+ UserVisiblePathRole,
+ FileTypeStringRole,
+ FileSizeStringRole,
+ };
+ Q_ENUM(Roles)
+
+ explicit FileProviderMaterialisedItemsModel(QObject *parent = nullptr);
+ [[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override;
+ [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ [[nodiscard]] QHash roleNames() const override;
+
+ [[nodiscard]] QVector items() const;
+
+signals:
+ void itemsChanged();
+
+public slots:
+ void setItems(const QVector &items);
+ void evictItem(const QString &identifier, const QString &domainIdentifier);
+
+private:
+ QVector _items;
+ QLocale _locale;
+};
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileprovidermaterialiseditemsmodel_mac.mm b/src/gui/macOS/fileprovidermaterialiseditemsmodel_mac.mm
new file mode 100644
index 0000000000000..ec1dd2ce0c98b
--- /dev/null
+++ b/src/gui/macOS/fileprovidermaterialiseditemsmodel_mac.mm
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2023 (c) Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileprovidermaterialiseditemsmodel.h"
+
+#include
+
+#import
+
+#include "fileproviderutils.h"
+
+#include "gui/systray.h"
+
+namespace OCC {
+
+namespace Mac {
+
+Q_LOGGING_CATEGORY(lcMacImplFileProviderMaterialisedItemsModelMac, "nextcloud.gui.macfileprovidermaterialiseditemsmodelmac", QtInfoMsg)
+
+void FileProviderMaterialisedItemsModel::evictItem(const QString &identifier, const QString &domainIdentifier)
+{
+ NSFileProviderManager * const manager = FileProviderUtils::managerForDomainIdentifier(domainIdentifier);
+ if (manager == nil) {
+ qCWarning(lcMacImplFileProviderMaterialisedItemsModelMac) << "Received null manager for domain"
+ << domainIdentifier
+ << "cannot evict item"
+ << identifier;
+ Systray::instance()->showMessage(tr("Error"),
+ tr("An internal error occurred. Please try again later."),
+ QSystemTrayIcon::Warning);
+ return;
+ }
+
+ __block BOOL successfullyDeleted = YES;
+
+ [manager evictItemWithIdentifier:identifier.toNSString() completionHandler:^(NSError *error) {
+ if (error != nil) {
+ const auto errorDesc = QString::fromNSString(error.localizedDescription);
+ qCWarning(lcMacImplFileProviderMaterialisedItemsModelMac) << "Error evicting item:" << errorDesc;
+ Systray::instance()->showMessage(tr("Error"),
+ tr("An error occurred while trying to delete the local copy of this item: %1").arg(errorDesc),
+ QSystemTrayIcon::Warning);
+ successfullyDeleted = NO;
+ }
+ }];
+
+ if (successfullyDeleted == NO) {
+ return;
+ }
+
+ const auto deletedItemIt = std::find_if(_items.cbegin(),
+ _items.cend(),
+ [identifier, domainIdentifier](const FileProviderItemMetadata &item) {
+ return item.identifier() == identifier && item.domainIdentifier() == domainIdentifier;
+ });
+
+ if (deletedItemIt == _items.cend()) {
+ qCWarning(lcMacImplFileProviderMaterialisedItemsModelMac) << "Could not find item"
+ << identifier
+ << "in model items.";
+ return;
+ }
+
+ const auto deletedItemRow = std::distance(_items.cbegin(), deletedItemIt);
+ beginRemoveRows({}, deletedItemRow, deletedItemRow);
+ _items.remove(deletedItemRow);
+ endRemoveRows();
+}
+
+
+} // namespace OCC
+
+} // namespace Mac
diff --git a/src/gui/macOS/fileprovidersettingscontroller.h b/src/gui/macOS/fileprovidersettingscontroller.h
new file mode 100644
index 0000000000000..bf980fdf770c7
--- /dev/null
+++ b/src/gui/macOS/fileprovidersettingscontroller.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "gui/macOS/fileproviderdomainsyncstatus.h"
+
+class QAbstractListModel;
+
+namespace OCC {
+
+class UserInfo;
+
+namespace Mac {
+
+class FileProviderSettingsController : public QObject
+{
+ Q_OBJECT
+
+public:
+ static FileProviderSettingsController *instance();
+
+ [[nodiscard]] QQuickWidget *settingsViewWidget(const QString &accountUserIdAtHost,
+ QWidget *const parent = nullptr,
+ const QQuickWidget::ResizeMode resizeMode = QQuickWidget::SizeRootObjectToView);
+
+ [[nodiscard]] QStringList vfsEnabledAccounts() const;
+ [[nodiscard]] Q_INVOKABLE bool vfsEnabledForAccount(const QString &userIdAtHost) const;
+ [[nodiscard]] unsigned long long localStorageUsageForAccount(const QString &userIdAtHost) const;
+ [[nodiscard]] Q_INVOKABLE float localStorageUsageGbForAccount(const QString &userIdAtHost) const;
+ [[nodiscard]] unsigned long long remoteStorageUsageForAccount(const QString &userIdAtHost) const;
+ [[nodiscard]] Q_INVOKABLE float remoteStorageUsageGbForAccount(const QString &userIdAtHost) const;
+
+ [[nodiscard]] Q_INVOKABLE QAbstractListModel *materialisedItemsModelForAccount(const QString &userIdAtHost);
+ [[nodiscard]] Q_INVOKABLE FileProviderDomainSyncStatus *domainSyncStatusForAccount(const QString &userIdAtHost) const;
+
+public slots:
+ void setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled);
+
+ void createEvictionWindowForAccount(const QString &userIdAtHost);
+ void signalFileProviderDomain(const QString &userIdAtHost);
+ void createDebugArchive(const QString &userIdAtHost);
+
+signals:
+ void vfsEnabledAccountsChanged();
+ void localStorageUsageForAccountChanged(const QString &userIdAtHost);
+ void remoteStorageUsageForAccountChanged(const QString &userIdAtHost);
+ void materialisedItemsForAccountChanged(const QString &userIdAtHost);
+
+private:
+ explicit FileProviderSettingsController(QObject *parent = nullptr);
+
+ class MacImplementation;
+ MacImplementation *d;
+
+ QHash _userInfos;
+};
+
+} // Mac
+
+} // OCC
diff --git a/src/gui/macOS/fileprovidersettingscontroller_mac.mm b/src/gui/macOS/fileprovidersettingscontroller_mac.mm
new file mode 100644
index 0000000000000..17d277e5ef1a6
--- /dev/null
+++ b/src/gui/macOS/fileprovidersettingscontroller_mac.mm
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileprovidersettingscontroller.h"
+
+#include
+#include
+
+#include "gui/systray.h"
+#include "gui/userinfo.h"
+#include "gui/macOS/fileprovider.h"
+#include "gui/macOS/fileprovideritemmetadata.h"
+#include "gui/macOS/fileprovidermaterialiseditemsmodel.h"
+#include "gui/macOS/fileproviderutils.h"
+
+// Objective-C imports
+#import
+
+#import "fileproviderstorageuseenumerationobserver.h"
+// End of Objective-C imports
+
+namespace {
+constexpr auto fpSettingsQmlPath = "qrc:/qml/src/gui/macOS/ui/FileProviderSettings.qml";
+constexpr auto fpEvictionDialogQmlPath = "qrc:/qml/src/gui/macOS/ui/FileProviderEvictionDialog.qml";
+
+// QML properties -- make sure they match up in QML file!
+constexpr auto fpAccountUserIdAtHostProp = "accountUserIdAtHost";
+constexpr auto fpMaterialisedItemsModelProp = "materialisedItemsModel";
+
+// NSUserDefaults entries
+constexpr auto enabledAccountsSettingsKey = "enabledAccounts";
+
+float gbFromBytesWithOneDecimal(const unsigned long long bytes)
+{
+ constexpr auto bytesIn100Mb = 1ULL * 1000ULL * 1000ULL * 100ULL;
+ return ((bytes * 1.0) / bytesIn100Mb) / 10.0;
+}
+} // namespace
+
+namespace OCC {
+
+namespace Mac {
+
+Q_LOGGING_CATEGORY(lcFileProviderSettingsController, "nextcloud.gui.mac.fileprovider.settingscontroller")
+
+class FileProviderSettingsController::MacImplementation : public QObject
+{
+public:
+ enum class VfsAccountsAction {
+ VfsAccountsNoAction,
+ VfsAccountsEnabledChanged,
+ };
+
+ explicit MacImplementation(FileProviderSettingsController *const parent)
+ {
+ q = parent;
+ initialCheck();
+ fetchMaterialisedFilesStorageUsage();
+ };
+
+ ~MacImplementation() override = default;
+
+ [[nodiscard]] QStringList enabledAccounts() const
+ {
+ QStringList qEnabledAccounts;
+ NSArray *const enabledAccounts = nsEnabledAccounts();
+ for (NSString *const userIdAtHostString in enabledAccounts) {
+ qEnabledAccounts.append(QString::fromNSString(userIdAtHostString));
+ }
+ return qEnabledAccounts;
+ }
+
+ [[nodiscard]] bool vfsEnabledForAccount(const QString &userIdAtHost) const
+ {
+ NSArray *const vfsEnabledAccounts = nsEnabledAccounts();
+ return [vfsEnabledAccounts containsObject:userIdAtHost.toNSString()];
+ }
+
+ [[nodiscard]] VfsAccountsAction setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled) const
+ {
+ NSArray *vfsEnabledAccounts = nsEnabledAccounts();
+
+ qCInfo(lcFileProviderSettingsController) << "Setting file provider-based vfs of account"
+ << userIdAtHost
+ << "to"
+ << setEnabled;
+
+ if (vfsEnabledAccounts == nil) {
+ qCDebug(lcFileProviderSettingsController) << "Received nil array for accounts, creating new array";
+ vfsEnabledAccounts = NSArray.array;
+ }
+
+ NSString *const nsUserIdAtHost = userIdAtHost.toNSString();
+ const BOOL accountEnabled = [vfsEnabledAccounts containsObject:nsUserIdAtHost];
+
+ if (accountEnabled == setEnabled) {
+ qCDebug(lcFileProviderSettingsController) << "VFS enablement status for"
+ << userIdAtHost
+ << "matches config.";
+ return VfsAccountsAction::VfsAccountsNoAction;
+ }
+
+ NSMutableArray *const mutableVfsAccounts = vfsEnabledAccounts.mutableCopy;
+
+ if (setEnabled) {
+ [mutableVfsAccounts addObject:nsUserIdAtHost];
+ } else {
+ [mutableVfsAccounts removeObject:nsUserIdAtHost];
+ }
+
+ NSArray *const modifiedVfsAccounts = mutableVfsAccounts.copy;
+ [_userDefaults setObject:modifiedVfsAccounts forKey:_accountsKey];
+
+ Q_ASSERT(vfsEnabledForAccount(userIdAtHost) == userIdAtHost);
+
+ return VfsAccountsAction::VfsAccountsEnabledChanged;
+ }
+
+ [[nodiscard]] VfsAccountsAction enableVfsForAllAccounts() const
+ {
+ const auto accManager = AccountManager::instance();
+ const auto accountsList = accManager->accounts();
+
+ if (accountsList.count() == 0) {
+ return VfsAccountsAction::VfsAccountsNoAction;
+ }
+
+ auto overallActResult = VfsAccountsAction::VfsAccountsNoAction;
+
+ for (const auto &account : accountsList) {
+ const auto qAccountUserIdAtHost = account->account()->userIdAtHostWithPort();
+ const auto accountActResult = setVfsEnabledForAccount(qAccountUserIdAtHost, true);
+
+ if (accountActResult == VfsAccountsAction::VfsAccountsEnabledChanged) {
+ overallActResult = accountActResult;
+ }
+ }
+
+ return overallActResult;
+ }
+
+ [[nodiscard]] unsigned long long localStorageUsageForAccount(const QString &userIdAtHost) const
+ {
+ // Return cached value as we fetch asynchronously on initialisation of this class.
+ // We will then emit a signal when the new value is found.
+ return _storageUsage.value(userIdAtHost);
+ }
+
+ [[nodiscard]] QVector materialisedItemsForAccount(const QString &userIdAtHost) const
+ {
+ return _materialisedFiles.value(userIdAtHost);
+ }
+
+ void signalFileProviderDomain(const QString &userIdAtHost) const
+ {
+ qCInfo(lcFileProviderSettingsController) << "Signalling file provider domain" << userIdAtHost;
+ NSFileProviderDomain * const domain = FileProviderUtils::domainForIdentifier(userIdAtHost);
+ NSFileProviderManager * const manager = [NSFileProviderManager managerForDomain:domain];
+ [manager signalEnumeratorForContainerItemIdentifier:NSFileProviderRootContainerItemIdentifier
+ completionHandler:^(NSError *const error) {
+ if (error != nil) {
+ qCWarning(lcFileProviderSettingsController) << "Could not signal file provider domain, error"
+ << error.localizedDescription;
+ return;
+ }
+
+ qCInfo(lcFileProviderSettingsController) << "Successfully signalled file provider domain";
+ // TODO: Provide some feedback in the UI
+ }];
+ }
+
+ [[nodiscard]] FileProviderDomainSyncStatus *domainSyncStatusForAccount(const QString &userIdAtHost) const
+ {
+ return _fileProviderDomainSyncStatuses.value(userIdAtHost);
+ }
+
+private slots:
+ void updateDomainSyncStatuses()
+ {
+ qCInfo(lcFileProviderSettingsController) << "Updating domain sync statuses";
+ _fileProviderDomainSyncStatuses.clear();
+ const auto enabledAccounts = nsEnabledAccounts();
+ for (NSString *const domainIdentifier in enabledAccounts) {
+ const auto qDomainIdentifier = QString::fromNSString(domainIdentifier);
+ const auto syncStatus = new FileProviderDomainSyncStatus(qDomainIdentifier, q);
+ _fileProviderDomainSyncStatuses.insert(qDomainIdentifier, syncStatus);
+ }
+ }
+
+private:
+ [[nodiscard]] NSArray *nsEnabledAccounts() const
+ {
+ return (NSArray *)[_userDefaults objectForKey:_accountsKey];
+ }
+
+ void fetchMaterialisedFilesStorageUsage()
+ {
+ qCInfo(lcFileProviderSettingsController) << "Fetching materialised files storage usage";
+
+ [NSFileProviderManager getDomainsWithCompletionHandler: ^(NSArray *const domains, NSError *const error) {
+ if (error != nil) {
+ qCWarning(lcFileProviderSettingsController) << "Could not get file provider domains:"
+ << error.localizedDescription
+ << "Will try again in 2 secs";
+
+ // HACK: Sometimes the system is not in a state where it wants to give us access to
+ // the file provider domains. We will try again in 2 seconds and hope it works
+ const auto thisQobject = (QObject*)this;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSTimer scheduledTimerWithTimeInterval:2 repeats:NO block:^(NSTimer *const timer) {
+ Q_UNUSED(timer)
+ QMetaObject::invokeMethod(thisQobject, [this] { fetchMaterialisedFilesStorageUsage(); });
+ }];
+ });
+ return;
+ }
+
+ for (NSFileProviderDomain *const domain in domains) {
+ qCInfo(lcFileProviderSettingsController) << "Checking storage use for domain:" << domain.identifier;
+
+ NSFileProviderManager *const managerForDomain = [NSFileProviderManager managerForDomain:domain];
+ if (managerForDomain == nil) {
+ qCWarning(lcFileProviderSettingsController) << "Got a nil file provider manager for domain"
+ << domain.identifier
+ << ", returning early.";
+ return;
+ }
+
+ const id enumerator = [managerForDomain enumeratorForMaterializedItems];
+ Q_ASSERT(enumerator != nil);
+ [enumerator retain];
+
+ FileProviderStorageUseEnumerationObserver *const storageUseObserver = [[FileProviderStorageUseEnumerationObserver alloc] init];
+ [storageUseObserver retain];
+ storageUseObserver.enumerationFinishedHandler = ^(NSError *const error) {
+ qCInfo(lcFileProviderSettingsController) << "Enumeration finished for" << domain.identifier;
+ if (error != nil) {
+ qCWarning(lcFileProviderSettingsController) << "Error while enumerating storage use" << error.localizedDescription;
+ [storageUseObserver release];
+ [enumerator release];
+ return;
+ }
+
+ const auto items = storageUseObserver.materialisedItems;
+ Q_ASSERT(items != nil);
+
+ // Remember that OCC::Account::userIdAtHost == domain.identifier for us
+ const auto qDomainIdentifier = QString::fromNSString(domain.identifier);
+ QVector qMaterialisedItems;
+ qMaterialisedItems.reserve(items.count);
+ for (const id item in items) {
+ const auto itemMetadata = FileProviderItemMetadata::fromNSFileProviderItem(item, qDomainIdentifier);
+ const auto storageUsage = _storageUsage.value(qDomainIdentifier) + itemMetadata.documentSize();
+ qCDebug(lcFileProviderSettingsController) << "Adding item" << itemMetadata.identifier()
+ << "with size" << itemMetadata.documentSize()
+ << "to storage usage for account" << qDomainIdentifier
+ << "with total size" << storageUsage;
+ qMaterialisedItems.append(itemMetadata);
+ _storageUsage.insert(qDomainIdentifier, storageUsage);
+ }
+ _materialisedFiles.insert(qDomainIdentifier, qMaterialisedItems);
+
+ emit q->localStorageUsageForAccountChanged(qDomainIdentifier);
+ emit q->materialisedItemsForAccountChanged(qDomainIdentifier);
+
+ [storageUseObserver release];
+ [enumerator release];
+ };
+ [enumerator enumerateItemsForObserver:storageUseObserver startingAtPage:NSFileProviderInitialPageSortedByName];
+ }
+ }];
+ }
+
+ void initialCheck()
+ {
+ qCInfo(lcFileProviderSettingsController) << "Running initial checks for file provider settings controller.";
+
+ NSArray *const vfsEnabledAccounts = nsEnabledAccounts();
+ if (vfsEnabledAccounts != nil) {
+ updateDomainSyncStatuses();
+ connect(q, &FileProviderSettingsController::vfsEnabledAccountsChanged, this, &MacImplementation::updateDomainSyncStatuses);
+ return;
+ }
+
+ qCInfo(lcFileProviderSettingsController) << "Initial check for file provider settings found nil enabled vfs accounts array."
+ << "Enabling all accounts on initial setup.";
+
+ [[maybe_unused]] const auto result = enableVfsForAllAccounts();
+ }
+
+ FileProviderSettingsController *q = nullptr;
+ NSUserDefaults *_userDefaults = NSUserDefaults.standardUserDefaults;
+ NSString *_accountsKey = [NSString stringWithUTF8String:enabledAccountsSettingsKey];
+ QHash> _materialisedFiles;
+ QHash _storageUsage;
+ QHash _fileProviderDomainSyncStatuses;
+};
+
+FileProviderSettingsController *FileProviderSettingsController::instance()
+{
+ static FileProviderSettingsController controller;
+ return &controller;
+}
+
+FileProviderSettingsController::FileProviderSettingsController(QObject *parent)
+ : QObject{parent}
+ , d(new FileProviderSettingsController::MacImplementation(this))
+{
+ const auto accManager = AccountManager::instance();
+ const auto accountsList = accManager->accounts();
+
+ for (const auto &accountState : accountsList) {
+ const auto userInfo = new UserInfo(accountState.data(), false, false, this);
+ const auto account = accountState->account();
+ const auto accountUserIdAtHost = account->userIdAtHostWithPort();
+
+ _userInfos.insert(accountUserIdAtHost, userInfo);
+ connect(userInfo, &UserInfo::fetchedLastInfo, this, [this, accountUserIdAtHost] {
+ emit remoteStorageUsageForAccountChanged(accountUserIdAtHost);
+ });
+ userInfo->setActive(true);
+ }
+}
+
+QQuickWidget *FileProviderSettingsController::settingsViewWidget(const QString &accountUserIdAtHost,
+ QWidget *const parent,
+ const QQuickWidget::ResizeMode resizeMode)
+{
+ const auto settingsViewWidget = new QQuickWidget(Systray::instance()->trayEngine(), parent);
+ settingsViewWidget->setResizeMode(resizeMode);
+ settingsViewWidget->setSource(QUrl(fpSettingsQmlPath));
+ settingsViewWidget->rootObject()->setProperty(fpAccountUserIdAtHostProp, accountUserIdAtHost);
+ return settingsViewWidget;
+}
+
+QStringList FileProviderSettingsController::vfsEnabledAccounts() const
+{
+ return d->enabledAccounts();
+}
+
+bool FileProviderSettingsController::vfsEnabledForAccount(const QString &userIdAtHost) const
+{
+ return d->vfsEnabledForAccount(userIdAtHost);
+}
+
+void FileProviderSettingsController::setVfsEnabledForAccount(const QString &userIdAtHost, const bool setEnabled)
+{
+ const auto enabledAccountsAction = d->setVfsEnabledForAccount(userIdAtHost, setEnabled);
+ if (enabledAccountsAction == MacImplementation::VfsAccountsAction::VfsAccountsEnabledChanged) {
+ emit vfsEnabledAccountsChanged();
+ }
+}
+
+unsigned long long FileProviderSettingsController::localStorageUsageForAccount(const QString &userIdAtHost) const
+{
+ return d->localStorageUsageForAccount(userIdAtHost);
+}
+
+float FileProviderSettingsController::localStorageUsageGbForAccount(const QString &userIdAtHost) const
+{
+ return gbFromBytesWithOneDecimal(localStorageUsageForAccount(userIdAtHost));
+}
+
+unsigned long long FileProviderSettingsController::remoteStorageUsageForAccount(const QString &userIdAtHost) const
+{
+ const auto userInfoForAccount = _userInfos.value(userIdAtHost);
+ if (!userInfoForAccount) {
+ return 0;
+ }
+
+ return userInfoForAccount->lastQuotaUsedBytes();
+}
+
+float FileProviderSettingsController::remoteStorageUsageGbForAccount(const QString &userIdAtHost) const
+{
+ return gbFromBytesWithOneDecimal(remoteStorageUsageForAccount(userIdAtHost));
+}
+
+QAbstractListModel *FileProviderSettingsController::materialisedItemsModelForAccount(const QString &userIdAtHost)
+{
+ const auto items = d->materialisedItemsForAccount(userIdAtHost);
+ if (items.isEmpty()) {
+ return nullptr;
+ }
+
+ const auto model = new FileProviderMaterialisedItemsModel(this);
+ model->setItems(items);
+
+ connect(this, &FileProviderSettingsController::materialisedItemsForAccountChanged, model, [this, model, userIdAtHost](const QString &accountUserIdAtHost) {
+ if (accountUserIdAtHost != userIdAtHost) {
+ return;
+ }
+
+ const auto items = d->materialisedItemsForAccount(userIdAtHost);
+ model->setItems(items);
+ });
+
+ return model;
+}
+
+void FileProviderSettingsController::createEvictionWindowForAccount(const QString &userIdAtHost)
+{
+ const auto engine = Systray::instance()->trayEngine();
+ QQmlComponent component(engine, QUrl(fpEvictionDialogQmlPath));
+ const auto model = materialisedItemsModelForAccount(userIdAtHost);
+ const auto genericDialog = component.createWithInitialProperties({
+ {fpAccountUserIdAtHostProp, userIdAtHost},
+ {fpMaterialisedItemsModelProp, QVariant::fromValue(model)},
+ });
+ const auto dialog = qobject_cast(genericDialog);
+ Q_ASSERT(dialog);
+ dialog->show();
+}
+
+void FileProviderSettingsController::signalFileProviderDomain(const QString &userIdAtHost)
+{
+ d->signalFileProviderDomain(userIdAtHost);
+}
+
+void FileProviderSettingsController::createDebugArchive(const QString &userIdAtHost)
+{
+ const auto filename = QFileDialog::getSaveFileName(nullptr,
+ tr("Create Debug Archive"),
+ QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DocumentsLocation),
+ tr("Text files") + " (*.txt)");
+ if (filename.isEmpty()) {
+ return;
+ }
+ FileProvider::instance()->createDebugArchiveForDomain(userIdAtHost, filename);
+}
+
+FileProviderDomainSyncStatus *FileProviderSettingsController::domainSyncStatusForAccount(const QString &userIdAtHost) const
+{
+ return d->domainSyncStatusForAccount(userIdAtHost);
+}
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileproviderstorageuseenumerationobserver.h b/src/gui/macOS/fileproviderstorageuseenumerationobserver.h
new file mode 100644
index 0000000000000..149d4249a6d46
--- /dev/null
+++ b/src/gui/macOS/fileproviderstorageuseenumerationobserver.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#import
+#import
+
+typedef void(^UsageEnumerationFinishedHandler)(NSError *const error);
+
+@interface FileProviderStorageUseEnumerationObserver : NSObject
+
+@property (readwrite, strong) UsageEnumerationFinishedHandler enumerationFinishedHandler;
+@property (readonly) NSSet> *materialisedItems;
+
+@end
diff --git a/src/gui/macOS/fileproviderstorageuseenumerationobserver.m b/src/gui/macOS/fileproviderstorageuseenumerationobserver.m
new file mode 100644
index 0000000000000..619f632cd015d
--- /dev/null
+++ b/src/gui/macOS/fileproviderstorageuseenumerationobserver.m
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+#import "fileproviderstorageuseenumerationobserver.h"
+
+@implementation FileProviderStorageUseEnumerationObserver
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self) {
+ _materialisedItems = [NSSet set];
+ }
+ return self;
+}
+
+// NSFileProviderEnumerationObserver protocol methods
+- (void)didEnumerateItems:(NSArray> *)updatedItems
+{
+ NSMutableSet> * const existingItems = self.materialisedItems.mutableCopy;
+
+ for (const id item in updatedItems) {
+ NSLog(@"StorageUseEnumerationObserver: Enumerating %@ with size %llu",
+ item.filename, item.documentSize.unsignedLongLongValue);
+ [existingItems addObject:item];
+ }
+
+ _materialisedItems = existingItems.copy;
+}
+
+- (void)finishEnumeratingWithError:(NSError *)error
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.enumerationFinishedHandler(error);
+ });
+}
+
+- (void)finishEnumeratingUpToPage:(NSFileProviderPage)nextPage
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.enumerationFinishedHandler(nil);
+ });
+}
+
+@end
diff --git a/src/gui/macOS/fileproviderutils.h b/src/gui/macOS/fileproviderutils.h
new file mode 100644
index 0000000000000..063be6d6d4d72
--- /dev/null
+++ b/src/gui/macOS/fileproviderutils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 (c) Claudio Cambra
+ *
+ * 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.
+ */
+
+#pragma once
+
+class QString;
+
+@class NSFileProviderDomain;
+@class NSFileProviderManager;
+
+/**
+ * This file contains the FileProviderUtils namespace, which contains
+ * utility functions for the FileProvider extension.
+ *
+ * Unlike other classes or namespaces in this module, this does not have
+ * a clear file separation between C++ and Objective C++ code.
+ * This is intended as a completely Objective-C++ namespace!
+ *
+ * You should threfore try to avoid using this in C++ code wherever possible
+ * and only use this in *_mac.mm implementation files.
+ */
+
+namespace OCC {
+
+namespace Mac {
+
+namespace FileProviderUtils {
+
+// Synchronous function to get the domain for a domain identifier
+NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier);
+
+// Synchronous function to get manager for a domain identifier
+NSFileProviderManager *managerForDomainIdentifier(const QString &domainIdentifier);
+
+} // namespace FileProviderUtils
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileproviderutils_mac.mm b/src/gui/macOS/fileproviderutils_mac.mm
new file mode 100644
index 0000000000000..1f3d046d9fcf4
--- /dev/null
+++ b/src/gui/macOS/fileproviderutils_mac.mm
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023 (c) Claudio Cambra
+ *
+ * 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.
+ */
+
+#include "fileproviderutils.h"
+
+#include
+#include
+
+#import
+
+namespace OCC {
+
+namespace Mac {
+
+namespace FileProviderUtils {
+
+Q_LOGGING_CATEGORY(lcMacFileProviderUtils, "nextcloud.gui.macfileproviderutils", QtInfoMsg)
+
+NSFileProviderDomain *domainForIdentifier(const QString &domainIdentifier)
+{
+ __block NSFileProviderDomain *foundDomain = nil;
+ NSString *const nsDomainIdentifier = domainIdentifier.toNSString();
+ dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+
+ // getDomainsWithCompletionHandler is asynchronous -- we create a dispatch semaphore in order
+ // to wait until it is done. This should tell you that we should not call this method very
+ // often!
+
+ [NSFileProviderManager getDomainsWithCompletionHandler:^(NSArray *const domains, NSError *const error) {
+ if (error != nil) {
+ qCWarning(lcMacFileProviderUtils) << "Error fetching domains:"
+ << error.localizedDescription;
+ dispatch_semaphore_signal(semaphore);
+ return;
+ }
+
+ for (NSFileProviderDomain *const domain in domains) {
+ if ([domain.identifier isEqualToString:nsDomainIdentifier]) {
+ [domain retain];
+ foundDomain = domain;
+ break;
+ }
+ }
+
+ dispatch_semaphore_signal(semaphore);
+ }];
+
+ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
+ dispatch_release(semaphore);
+
+ if (foundDomain == nil) {
+ qCWarning(lcMacFileProviderUtils) << "No matching item domain for identifier"
+ << domainIdentifier;
+ }
+
+ return foundDomain;
+}
+
+NSFileProviderManager *managerForDomainIdentifier(const QString &domainIdentifier)
+{
+ NSFileProviderDomain * const domain = domainForIdentifier(domainIdentifier);
+ if (domain == nil) {
+ qCWarning(lcMacFileProviderUtils) << "Received null domain for identifier"
+ << domainIdentifier
+ << "cannot acquire manager";
+ return nil;
+ }
+
+ NSFileProviderManager * const manager = [NSFileProviderManager managerForDomain:domain];
+ if (manager == nil) {
+ qCWarning(lcMacFileProviderUtils) << "Received null manager for domain"
+ << domainIdentifier;
+ }
+
+ [domain release];
+ return manager;
+}
+
+} // namespace FileProviderUtils
+
+} // namespace Mac
+
+} // namespace OCC
diff --git a/src/gui/macOS/fileproviderxpc.h b/src/gui/macOS/fileproviderxpc.h
index 25efb05ad2894..9e234ae2ee3dc 100644
--- a/src/gui/macOS/fileproviderxpc.h
+++ b/src/gui/macOS/fileproviderxpc.h
@@ -39,6 +39,7 @@ public slots:
void configureExtensions();
void authenticateExtension(const QString &extensionAccountId) const;
void unauthenticateExtension(const QString &extensionAccountId) const;
+ void createDebugArchiveForExtension(const QString &extensionAccountId, const QString &filename) const;
private slots:
void slotAccountStateChanged(AccountState::State state) const;
diff --git a/src/gui/macOS/fileproviderxpc_mac.mm b/src/gui/macOS/fileproviderxpc_mac.mm
index 399c1af6d4bf9..260804b71bc31 100644
--- a/src/gui/macOS/fileproviderxpc_mac.mm
+++ b/src/gui/macOS/fileproviderxpc_mac.mm
@@ -102,5 +102,39 @@
break;
}
}
+void FileProviderXPC::createDebugArchiveForExtension(const QString &extensionAccountId, const QString &filename) const
+{
+ qCInfo(lcFileProviderXPC) << "Creating debug archive for extension" << extensionAccountId << "at" << filename;
+ // You need to fetch the contents from the extension and then create the archive from the client side.
+ // The extension is not allowed to ask for permission to write into the file system as it is not a user facing process.
+ const auto clientCommService = (NSObject *)_clientCommServices.value(extensionAccountId);
+ const auto group = dispatch_group_create();
+ __block NSString *rcvdDebugLogString;
+ dispatch_group_enter(group);
+ [clientCommService createDebugLogStringWithCompletionHandler:^(NSString *const debugLogString, NSError *const error) {
+ if (error != nil) {
+ qCWarning(lcFileProviderXPC) << "Error getting debug log string" << error.localizedDescription;
+ dispatch_group_leave(group);
+ return;
+ } else if (debugLogString == nil) {
+ qCWarning(lcFileProviderXPC) << "Debug log string is nil";
+ dispatch_group_leave(group);
+ return;
+ }
+ rcvdDebugLogString = [NSString stringWithString:debugLogString];
+ [rcvdDebugLogString retain];
+ dispatch_group_leave(group);
+ }];
+ dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
+
+ QFile debugLogFile(filename);
+ if (debugLogFile.open(QIODevice::WriteOnly)) {
+ debugLogFile.write(rcvdDebugLogString.UTF8String);
+ debugLogFile.close();
+ qCInfo(lcFileProviderXPC) << "Debug log file written to" << filename;
+ } else {
+ qCWarning(lcFileProviderXPC) << "Could not open debug log file" << filename;
+ }
+}
} // namespace OCC::Mac
diff --git a/src/gui/macOS/progressobserver.h b/src/gui/macOS/progressobserver.h
new file mode 100644
index 0000000000000..51b23ed92fbcd
--- /dev/null
+++ b/src/gui/macOS/progressobserver.h
@@ -0,0 +1,30 @@
+/*
+* Copyright 2024 (c) Claudio Cambra
+*
+* 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.
+*/
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^ProgressKVOChangeHandler)(NSProgress *const progress);
+
+@interface ProgressObserver : NSObject
+
+@property (readonly) NSProgress *progress;
+@property (readwrite, copy) ProgressKVOChangeHandler progressKVOChangeHandler;
+
+- (instancetype)initWithProgress:(NSProgress *)progress;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/gui/macOS/progressobserver.m b/src/gui/macOS/progressobserver.m
new file mode 100644
index 0000000000000..b52e13b26161d
--- /dev/null
+++ b/src/gui/macOS/progressobserver.m
@@ -0,0 +1,42 @@
+/*
+* Copyright 2024 (c) Claudio Cambra
+*
+* 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.
+*/
+
+#import "progressobserver.h"
+
+@implementation ProgressObserver
+
+- (instancetype)initWithProgress:(NSProgress *)progress
+{
+ self = [super init];
+ if (self) {
+ _progress = progress;
+ [_progress addObserver:self forKeyPath:@"totalUnitCount" options:NSKeyValueObservingOptionNew context:nil];
+ [_progress addObserver:self forKeyPath:@"completedUnitCount" options:NSKeyValueObservingOptionNew context:nil];
+ [_progress addObserver:self forKeyPath:@"cancelled" options:NSKeyValueObservingOptionNew context:nil];
+ [_progress addObserver:self forKeyPath:@"paused" options:NSKeyValueObservingOptionNew context:nil];
+ [_progress addObserver:self forKeyPath:@"fileTotalCount" options:NSKeyValueObservingOptionNew context:nil];
+ [_progress addObserver:self forKeyPath:@"fileCompletedCount" options:NSKeyValueObservingOptionNew context:nil];
+ }
+ return self;
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ self.progressKVOChangeHandler(self.progress);
+}
+
+@end
diff --git a/src/gui/macOS/ui/FileProviderEvictionDialog.qml b/src/gui/macOS/ui/FileProviderEvictionDialog.qml
new file mode 100644
index 0000000000000..5884b63770a9b
--- /dev/null
+++ b/src/gui/macOS/ui/FileProviderEvictionDialog.qml
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import Style 1.0
+import "../../filedetails"
+import "../../tray"
+
+import com.nextcloud.desktopclient 1.0
+
+ApplicationWindow {
+ id: root
+
+ property var materialisedItemsModel: null
+ property string accountUserIdAtHost: ""
+
+ title: qsTr("Evict materialised files")
+ flags: Qt.Dialog | Qt.WindowStaysOnTopHint
+ width: 640
+ height: 480
+
+ ListView {
+ anchors.fill: parent
+ model: root.materialisedItemsModel
+ delegate: FileProviderFileDelegate {
+ width: parent.width
+ height: 60
+ onEvictItem: root.materialisedItemsModel.evictItem(identifier, domainIdentifier)
+ }
+ }
+}
diff --git a/src/gui/macOS/ui/FileProviderFileDelegate.qml b/src/gui/macOS/ui/FileProviderFileDelegate.qml
new file mode 100644
index 0000000000000..25d812f6623e0
--- /dev/null
+++ b/src/gui/macOS/ui/FileProviderFileDelegate.qml
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import Style 1.0
+import "../../filedetails"
+import "../../tray"
+
+import com.nextcloud.desktopclient 1.0
+
+Item {
+ id: root
+
+ signal evictItem(string identifier, string domainIdentifier)
+
+ // Match with model rolenames for automagic setting of properties
+ required property string identifier
+ required property string domainIdentifier
+ required property string fileName
+ required property string userVisiblePath
+ required property string fileTypeString
+
+ required property string fileSizeString
+
+ RowLayout {
+ id: internalLayout
+
+ anchors.fill: parent
+
+ Image {
+ id: fileIconImage
+ Layout.fillHeight: true
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ source: "image://tray-image-provider/:/fileicon/" + root.userVisiblePath
+ sourceSize.width: Style.trayListItemIconSize
+ sourceSize.height: Style.trayListItemIconSize
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Column {
+ Layout.fillWidth: true
+
+ EnforcedPlainTextLabel {
+ id: fileNameLabel
+ width: parent.width
+ text: root.fileName
+ }
+
+ EnforcedPlainTextLabel {
+ id: pathLabel
+ width: parent.width
+ text: root.userVisiblePath
+ elide: Text.ElideLeft
+ }
+
+ Row {
+ width: parent.width
+ spacing: Style.smallSpacing
+
+ EnforcedPlainTextLabel {
+ id: fileSizeLabel
+ text: root.fileSizeString
+ font.bold: true
+ }
+
+ EnforcedPlainTextLabel {
+ id: fileTypeLabel
+ text: root.fileTypeString
+ color: Style.ncSecondaryTextColor
+ }
+ }
+ }
+
+ CustomButton {
+ id: deleteButton
+
+ Layout.minimumWidth: implicitWidth
+ Layout.fillHeight: true
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+
+ text: qsTr("Delete")
+ bgColor: Style.errorBoxBackgroundColor
+ onClicked: root.evictItem(root.identifier, root.domainIdentifier)
+ }
+ }
+}
diff --git a/src/gui/macOS/ui/FileProviderSettings.qml b/src/gui/macOS/ui/FileProviderSettings.qml
new file mode 100644
index 0000000000000..f3844f2458c20
--- /dev/null
+++ b/src/gui/macOS/ui/FileProviderSettings.qml
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 by Claudio Cambra
+ *
+ * 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import Style 1.0
+import "../../filedetails"
+import "../../tray"
+
+import com.nextcloud.desktopclient 1.0
+
+Page {
+ id: root
+
+ property bool showBorder: true
+ property var controller: FileProviderSettingsController
+ property string accountUserIdAtHost: ""
+
+ title: qsTr("Virtual files settings")
+
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally.
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ brightText: Style.ncTextBrightColor
+ highlight: Style.lightHover
+ highlightedText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.ncSecondaryTextColor
+ mid: Style.darkerHover
+ dark: Style.menuBorder
+ button: Style.buttonBackgroundColor
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ toolTipBase: Style.backgroundColor
+ toolTipText: Style.ncTextColor
+ }
+
+ background: Rectangle {
+ color: palette.window
+ border.width: root.showBorder ? Style.normalBorderWidth : 0
+ border.color: root.palette.dark
+ }
+
+ padding: Style.standardSpacing
+
+ ColumnLayout {
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+
+ EnforcedPlainTextLabel {
+ Layout.fillWidth: true
+ text: qsTr("General settings")
+ font.bold: true
+ font.pointSize: Style.subheaderFontPtSize
+ elide: Text.ElideRight
+ }
+
+ CheckBox {
+ id: vfsEnabledCheckBox
+ Layout.fillWidth: true
+ text: qsTr("Enable virtual files")
+ checked: root.controller.vfsEnabledForAccount(root.accountUserIdAtHost)
+ onClicked: root.controller.setVfsEnabledForAccount(root.accountUserIdAtHost, checked)
+ }
+
+ Loader {
+ id: vfsSettingsLoader
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ active: vfsEnabledCheckBox.checked
+ sourceComponent: ColumnLayout {
+ Rectangle {
+ Layout.fillWidth: true
+ height: Style.normalBorderWidth
+ color: Style.ncSecondaryTextColor
+ }
+
+ FileProviderSyncStatus {
+ syncStatus: root.controller.domainSyncStatusForAccount(root.accountUserIdAtHost)
+ }
+
+ FileProviderStorageInfo {
+ id: storageInfo
+ localUsedStorage: root.controller.localStorageUsageGbForAccount(root.accountUserIdAtHost)
+ remoteUsedStorage: root.controller.remoteStorageUsageGbForAccount(root.accountUserIdAtHost)
+
+ onEvictDialogRequested: root.controller.createEvictionWindowForAccount(root.accountUserIdAtHost)
+
+ Connections {
+ target: root.controller
+
+ function onLocalStorageUsageForAccountChanged(accountUserIdAtHost) {
+ if (root.accountUserIdAtHost !== accountUserIdAtHost) {
+ return;
+ }
+ storageInfo.localUsedStorage = root.controller.localStorageUsageGbForAccount(root.accountUserIdAtHost);
+ }
+
+ function onRemoteStorageUsageForAccountChanged(accountUserIdAtHost) {
+ if (root.accountUserIdAtHost !== accountUserIdAtHost) {
+ return;
+ }
+ storageInfo.remoteUsedStorage = root.controller.remoteStorageUsageGbForAccount(root.accountUserIdAtHost);
+ }
+ }
+ }
+
+ EnforcedPlainTextLabel {
+ Layout.fillWidth: true
+ Layout.topMargin: Style.standardSpacing
+ text: qsTr("Advanced")
+ font.bold: true
+ font.pointSize: Style.subheaderFontPtSize
+ elide: Text.ElideRight
+ }
+
+ CustomButton {
+ text: qsTr("Signal file provider domain")
+ onClicked: root.controller.signalFileProviderDomain(root.accountUserIdAtHost)
+ }
+
+ CustomButton {
+ text: qsTr("Create debug archive")
+ onClicked: root.controller.createDebugArchive(root.accountUserIdAtHost)
+ }
+ }
+ }
+ }
+}
diff --git a/src/gui/macOS/ui/FileProviderStorageInfo.qml b/src/gui/macOS/ui/FileProviderStorageInfo.qml
new file mode 100644
index 0000000000000..9189507239273
--- /dev/null
+++ b/src/gui/macOS/ui/FileProviderStorageInfo.qml
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 by Claudio Cambra
+ *
+ * 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import Style 1.0
+import "../../filedetails"
+import "../../tray"
+
+import com.nextcloud.desktopclient 1.0
+
+GridLayout {
+ id: root
+
+ signal evictDialogRequested()
+
+ required property real localUsedStorage
+ required property real remoteUsedStorage
+
+ Layout.fillWidth: true
+ columns: 2
+
+ EnforcedPlainTextLabel {
+ Layout.row: 0
+ Layout.column: 0
+ Layout.alignment: Layout.AlignLeft | Layout.AlignVCenter
+ Layout.fillWidth: true
+ text: qsTr("Local storage use")
+ font.bold: true
+ }
+
+ EnforcedPlainTextLabel {
+ Layout.row: 0
+ Layout.column: 1
+ Layout.alignment: Layout.AlignRight | Layout.AlignVCenter
+ text: qsTr("%1 GB of %2 GB remote files synced").arg(root.localUsedStorage.toFixed(2)).arg(root.remoteUsedStorage.toFixed(2));
+ color: Style.ncSecondaryTextColor
+ horizontalAlignment: Text.AlignRight
+ }
+
+ ProgressBar {
+ Layout.row: 1
+ Layout.columnSpan: root.columns
+ Layout.fillWidth: true
+ value: root.localUsedStorage / root.remoteUsedStorage
+ }
+}
\ No newline at end of file
diff --git a/src/gui/macOS/ui/FileProviderSyncStatus.qml b/src/gui/macOS/ui/FileProviderSyncStatus.qml
new file mode 100644
index 0000000000000..11f093bbcd88b
--- /dev/null
+++ b/src/gui/macOS/ui/FileProviderSyncStatus.qml
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 by Claudio Cambra
+ *
+ * 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import Style 1.0
+import "../../filedetails"
+import "../../tray"
+
+import com.nextcloud.desktopclient 1.0
+
+GridLayout {
+ id: root
+
+ required property var syncStatus
+
+ rows: syncStatus.syncing ? 2 : 1
+
+ NCBusyIndicator {
+ id: syncIcon
+
+ property int size: Style.trayListItemIconSize * 0.8
+
+ Layout.row: 0
+ Layout.rowSpan: root.syncStatus.syncing ? 2 : 1
+ Layout.column: 0
+ Layout.preferredWidth: size
+ Layout.preferredHeight: size
+ Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
+
+ padding: 0
+ spacing: 0
+ imageSource: root.syncStatus.icon
+ running: root.syncStatus.syncing
+ }
+
+ EnforcedPlainTextLabel {
+ Layout.row: 0
+ Layout.column: 1
+ Layout.columnSpan: root.syncStatus.syncing ? 2 : 1
+ Layout.fillWidth: true
+ font.bold: true
+ font.pointSize: Style.headerFontPtSize
+ text: root.syncStatus.syncing ? qsTr("Syncing") : qsTr("All synced!")
+ }
+
+ NCProgressBar {
+ Layout.row: 1
+ Layout.column: 1
+ Layout.fillWidth: true
+ value: root.syncStatus.fractionCompleted
+ visible: root.syncStatus.syncing
+ }
+}
\ No newline at end of file
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index 6749df8a6e91b..c14b5125a0861 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -62,6 +62,10 @@
#include
#include
+#ifdef BUILD_FILE_PROVIDER_MODULE
+#include "macOS/fileprovidersettingscontroller.h"
+#endif
+
namespace OCC {
Q_LOGGING_CATEGORY(lcOwnCloudGui, "com.nextcloud.owncloudgui")
@@ -146,6 +150,10 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "UserAppsModel", UserAppsModel::instance());
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "Theme", Theme::instance());
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "Systray", Systray::instance());
+
+#ifdef BUILD_FILE_PROVIDER_MODULE
+ qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "FileProviderSettingsController", Mac::FileProviderSettingsController::instance());
+#endif
}
void ownCloudGui::createTray()
diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp
index 130e5deac1c72..8f8b1e2f8f76f 100644
--- a/src/gui/settingsdialog.cpp
+++ b/src/gui/settingsdialog.cpp
@@ -39,6 +39,7 @@
#include
#include
#include
+#include
namespace {
const QString TOOLBAR_CSS()
@@ -132,8 +133,6 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
auto *networkSettings = new NetworkSettings;
_ui->stack->addWidget(networkSettings);
- connect(_ui->stack, &QStackedWidget::currentChanged, this, &SettingsDialog::currentPageChanged);
-
_actionGroupWidgets.insert(generalAction, generalSettings);
_actionGroupWidgets.insert(networkAction, networkSettings);
diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h
index 6034e21bbf999..f992d50ea7b36 100644
--- a/src/gui/settingsdialog.h
+++ b/src/gui/settingsdialog.h
@@ -28,11 +28,11 @@ class QStandardItemModel;
namespace OCC {
-class AccountState;
-
namespace Ui {
class SettingsDialog;
}
+
+class AccountState;
class AccountSettings;
class Application;
class FolderMan;
diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp
index 510435ebf1b02..fd526682db6d6 100644
--- a/src/gui/systray.cpp
+++ b/src/gui/systray.cpp
@@ -59,9 +59,14 @@ Systray *Systray::instance()
return _instance;
}
+QQmlApplicationEngine *Systray::trayEngine() const
+{
+ return _trayEngine.get();
+}
+
void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine)
{
- _trayEngine = trayEngine;
+ _trayEngine = std::make_unique(trayEngine);
_trayEngine->setNetworkAccessManagerFactory(&_accessManagerFactory);
@@ -112,7 +117,7 @@ void Systray::create()
_trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
}
- QQmlComponent trayWindowComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"));
+ QQmlComponent trayWindowComponent(trayEngine(), QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"));
if(trayWindowComponent.isError()) {
qCWarning(lcSystray) << trayWindowComponent.errorString();
@@ -243,7 +248,7 @@ void Systray::createCallDialog(const Activity &callNotification, const AccountSt
{"link", callNotification._link},
};
- const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
+ const auto callDialog = new QQmlComponent(trayEngine(), QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
if(callDialog->isError()) {
qCWarning(lcSystray) << callDialog->errorString();
@@ -265,7 +270,7 @@ void Systray::createEditFileLocallyLoadingDialog(const QString &fileName)
qCDebug(lcSystray) << "Opening a file local editing dialog...";
- const auto editFileLocallyLoadingDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/EditFileLocallyLoadingDialog.qml"));
+ const auto editFileLocallyLoadingDialog = new QQmlComponent(trayEngine(), QStringLiteral("qrc:/qml/src/gui/tray/EditFileLocallyLoadingDialog.qml"));
if (editFileLocallyLoadingDialog->isError()) {
qCWarning(lcSystray) << editFileLocallyLoadingDialog->errorString();
@@ -287,7 +292,7 @@ void Systray::destroyEditFileLocallyLoadingDialog()
void Systray::createResolveConflictsDialog(const OCC::ActivityList &allConflicts)
{
- const auto conflictsDialog = std::make_unique(_trayEngine, QStringLiteral("qrc:/qml/src/gui/ResolveConflictsDialog.qml"));
+ const auto conflictsDialog = std::make_unique(trayEngine(), QStringLiteral("qrc:/qml/src/gui/ResolveConflictsDialog.qml"));
const QVariantMap initialProperties{
{"allConflicts", QVariant::fromValue(allConflicts)},
};
@@ -378,7 +383,7 @@ void Systray::createFileDetailsDialog(const QString &localPath)
{"localPath", localPath},
};
- QQmlComponent fileDetailsDialog(_trayEngine, QStringLiteral("qrc:/qml/src/gui/filedetails/FileDetailsWindow.qml"));
+ QQmlComponent fileDetailsDialog(trayEngine(), QStringLiteral("qrc:/qml/src/gui/filedetails/FileDetailsWindow.qml"));
if (!fileDetailsDialog.isError()) {
const auto createdDialog = fileDetailsDialog.createWithInitialProperties(initialProperties);
diff --git a/src/gui/systray.h b/src/gui/systray.h
index 87592e935dc4d..605286084089f 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -60,8 +60,7 @@ double menuBarThickness();
* @brief The Systray class
* @ingroup gui
*/
-class Systray
- : public QSystemTrayIcon
+class Systray : public QSystemTrayIcon
{
Q_OBJECT
@@ -97,6 +96,8 @@ class Systray
bool raiseDialogs();
+ [[nodiscard]] QQmlApplicationEngine* trayEngine() const;
+
signals:
void currentUserChanged();
void openAccountWizard();
@@ -176,7 +177,7 @@ private slots:
bool _isOpen = false;
bool _syncIsPaused = true;
- QPointer _trayEngine;
+ std::unique_ptr _trayEngine;
QPointer _contextMenu;
QSharedPointer _trayWindow;
diff --git a/src/libsync/configfile.cpp b/src/libsync/configfile.cpp
index 7583956d8f5dc..d46ad50f0e87c 100644
--- a/src/libsync/configfile.cpp
+++ b/src/libsync/configfile.cpp
@@ -111,8 +111,6 @@ static constexpr char certPath[] = "http_certificatePath";
static constexpr char certPasswd[] = "http_certificatePasswd";
static const QSet validUpdateChannels { QStringLiteral("stable"), QStringLiteral("beta") };
-
-static constexpr auto macFileProviderModuleEnabledC = "macFileProviderModuleEnabled";
}
namespace OCC {
@@ -1229,16 +1227,4 @@ void ConfigFile::setDiscoveredLegacyConfigPath(const QString &discoveredLegacyCo
_discoveredLegacyConfigPath = discoveredLegacyConfigPath;
}
-bool ConfigFile::macFileProviderModuleEnabled() const
-{
- QSettings settings(configFile(), QSettings::IniFormat);
- return settings.value(macFileProviderModuleEnabledC, false).toBool();
-}
-
-void ConfigFile::setMacFileProviderModuleEnabled(const bool moduleEnabled)
-{
- QSettings settings(configFile(), QSettings::IniFormat);
- settings.setValue(QLatin1String(macFileProviderModuleEnabledC), moduleEnabled);
-}
-
}
diff --git a/src/libsync/configfile.h b/src/libsync/configfile.h
index 7ae1f98df207b..97ec16e2f5ad7 100644
--- a/src/libsync/configfile.h
+++ b/src/libsync/configfile.h
@@ -236,9 +236,6 @@ class OWNCLOUDSYNC_EXPORT ConfigFile
[[nodiscard]] static QString discoveredLegacyConfigPath();
static void setDiscoveredLegacyConfigPath(const QString &discoveredLegacyConfigPath);
- [[nodiscard]] bool macFileProviderModuleEnabled() const;
- void setMacFileProviderModuleEnabled(const bool moduleEnabled);
-
protected:
[[nodiscard]] QVariant getPolicySetting(const QString &policy, const QVariant &defaultValue = QVariant()) const;
void storeData(const QString &group, const QString &key, const QVariant &value);
diff --git a/theme/Style/Style.qml b/theme/Style/Style.qml
index 4d1360c31e0a1..9dd321c4b3a84 100644
--- a/theme/Style/Style.qml
+++ b/theme/Style/Style.qml
@@ -33,6 +33,10 @@ QtObject {
// We are using pixel size because this is cross platform comparable, point size isn't
readonly property int topLinePixelSize: pixelSize
readonly property int subLinePixelSize: topLinePixelSize - 2
+ readonly property int defaultFontPtSize: fontMetrics.font.pointSize
+ readonly property int subheaderFontPtSize: defaultFontPtSize + 2
+ readonly property int headerFontPtSize: defaultFontPtSize + 4
+ readonly property int titleFontPtSize: defaultFontPtSize + 8
// Dimensions and sizes
property int trayWindowWidth: variableSize(400)