Compare commits

...

986 Commits

Author SHA1 Message Date
allexzander f1c37d7671
Merge pull request #6738 from nextcloud/bugfix/incorrect-date-parsing
Fix incorrect date parsing.
2024-05-19 22:57:35 +02:00
Matthieu Gallien f15810ae0d
Merge pull request #6758 from nextcloud/ci/githubActionsForAppimage
produce Appimage packages from github actions
2024-05-16 15:39:54 +02:00
Matthieu Gallien df7527c310 produce Appimage packages from github actions
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-16 11:56:22 +02:00
Nextcloud bot 064e134569
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-16 02:34:05 +00:00
Claudio Cambra 22f2fe1218
Merge pull request #6706 from nextcloud/bugfix/account-details 2024-05-15 21:47:54 +02:00
Claudio Cambra 57ced98f98 Censor account details string in file provider logging
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-05-15 21:47:05 +02:00
Matthieu Gallien a06fe10f65
Merge pull request #6713 from nextcloud/ci/fixWindowsTests
some files just cannot sync on windows: get automated tests to work
2024-05-15 18:28:22 +02:00
Matthieu Gallien 9ed87bf2ad some files just cannot sync on windows: get automated tests to work
fix one automated test knowing the platform limitations of windows

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 17:48:58 +02:00
Matthieu Gallien d228ec445f
Merge pull request #6756 from nextcloud/ci/fixMacCi
skip tests currently broken on macOS: enable mandatory tests for macOS
2024-05-15 17:39:35 +02:00
Matthieu Gallien 3626e4c0c0 skip tests currently broken on macOS: enable mandatory tests for macOS
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 17:05:46 +02:00
Matthieu Gallien 36c2c618ec
Merge pull request #6715 from nextcloud/updateInstallRequirementsInDoc
update doc with install requirements fom Qt6 supported platforms
2024-05-15 17:01:59 +02:00
Matthieu Gallien 3342a13f44
update doc with install requirements for Qt6 supported platforms
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:49:53 +02:00
Camila Ayres 6a6138c6b4
Merge pull request #6725 from nextcloud/update/documentation
Extend 'How the "Edit locally" functionality works' text.
2024-05-15 15:42:33 +02:00
Camila Ayres 011dd6c4cb Extend 'How the "Edit locally" functionality works' text.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-15 15:42:23 +02:00
Matthieu Gallien 42ed8b454c
Merge pull request #6705 from nextcloud/ci/fixWindowsCi
use windows-2022 image to run our windows CI on github actions
2024-05-15 15:38:33 +02:00
Matthieu Gallien a3083f8be4 skip FolderWatcher on windows: really not a reliable test
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien e2ed718030 disable some unreliable tests for CfApi CI
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien 7c9f652c3e fix regression with VFS update metadata instruction
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien 91bea6f305 only compute checksum of a local file that is not a virtual one
should avoid triggering implicit hydration from within the desktop
client

triggering an implicit hydration on our own is strictly forbidden as
that is causing issues like deadlock and failed hydration attempts

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien a1efabc3b6 remove usage of memory sanitizers in drone tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien 098f4ef164 better automated tests log
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:11 +02:00
Matthieu Gallien 7cde0b16e1 catch std::filesystem exceptions in automated tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:10 +02:00
Matthieu Gallien 96d1fc0720 temporarily ignore failed tests on windows
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:10 +02:00
Claudio Cambra df93608477 Update macos-build-and-test.yml with latest Xcode
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-05-15 15:06:10 +02:00
Matthieu Gallien d4986e15f8 simplify macOS targets and try arm64 craft target
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:10 +02:00
Matthieu Gallien aa38a0180d use windows-2022 image to run our windows CI on github actions
also switch craft to target windows-msvc2022_64-cl

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 15:06:10 +02:00
Matthieu Gallien 1b9fbb03bb
Merge pull request #6751 from nextcloud/bugfix/fixWrongMemoryAccessInExcludeFiles
avoid accessing a temp QString via QStringView after it is deleted
2024-05-15 14:34:57 +02:00
Matthieu Gallien ba00c50022 avoid accessing a temp QString via QStringView after it is deleted
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-15 14:34:46 +02:00
Claudio Cambra 62661b7a0b
Merge pull request #6753 from nextcloud/bugfix/mdm-postinstall-mac
Do not open client on install as this breaks MDM deployments
2024-05-15 14:29:35 +02:00
Claudio Cambra 9eb8645b95 Do not open client on install as this breaks MDM deployments
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-05-15 14:28:49 +02:00
Nextcloud bot 5e70aa3634
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-15 02:31:38 +00:00
Claudio Cambra c8d756ee65
Merge pull request #6736 from nextcloud/update/docs-qt6
Update the documentation on how to build the client
2024-05-14 12:42:05 +02:00
Camila Ayres 71098127fc Improve text about %PATH% and KDE Craft.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Camila Ayres 88ad7e3b02 Remove duplicated instructions.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Camila Ayres 4fe7cdd5b0 Fix documentation style.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Camila Ayres 63fdce3fbe Update instructions to build the client on mac OS.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Camila Ayres 6d3335bd60 Update instructions to build on Windows.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Camila Ayres 7d575d9bf0 Add documentation about how to compile the client on Windows with Qt6.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-05-14 10:28:17 +02:00
Claudio Cambra 3dd3082a66
Merge pull request #6733 from nextcloud/dependabot/github_actions/skjnldsv/block-fixup-merge-action-2
Bump skjnldsv/block-fixup-merge-action from 1 to 2
2024-05-14 10:27:56 +02:00
dependabot[bot] a6d48ef5db Bump skjnldsv/block-fixup-merge-action from 1 to 2
Bumps [skjnldsv/block-fixup-merge-action](https://github.com/skjnldsv/block-fixup-merge-action) from 1 to 2.
- [Release notes](https://github.com/skjnldsv/block-fixup-merge-action/releases)
- [Commits](42d26e1b53...c138ea99e4)

---
updated-dependencies:
- dependency-name: skjnldsv/block-fixup-merge-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-14 09:56:54 +02:00
Nextcloud bot 52e6b8216f
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-14 02:34:12 +00:00
Claudio Cambra 390d6a58a4
Merge pull request #6743 from nextcloud/bugfix/mac-build-qtsix
Fix macOS build on master
2024-05-13 11:10:05 +02:00
Claudio Cambra 4bd0974696 Remove use of setMargin on QVBoxLayout, not present in Qt6
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-05-13 11:07:33 +02:00
Claudio Cambra afbd2ebc02 Remove all use of MacExtras
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-05-13 11:07:33 +02:00
allexzander 7a49312ddd
Merge pull request #6742 from nextcloud/bugfix/lockownertype-setreadonly-basedoncaps
Bugfix/lockownertype setreadonly basedoncaps
2024-05-12 12:53:56 +02:00
Nextcloud bot 7bd2b8aeff
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-11 02:34:15 +00:00
alex-z 04c0125bd1 Fix incorrect date parsing.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-05-09 16:21:46 +02:00
alex-z c7591f6332 Bugfix. Files lock. Check lock owner type for setting readonly based on server capabilities (NC27/28 compatibility issue).
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-05-09 16:21:46 +02:00
Nextcloud bot cf888027df
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-08 02:47:33 +00:00
alex-z 00022a3f9c Fix incorrect date parsing.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-05-08 00:45:08 +02:00
Nextcloud bot 0d7ab96330
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-03 02:40:59 +00:00
Matthieu Gallien 73c2f17c59
Merge pull request #6718 from nextcloud/ci/improveDevModeOnWindows
improve logs when build with NEXTCLOUD_DEV enabled
2024-05-02 18:17:06 +02:00
Matthieu Gallien d0b4af6ccc improve logs when build with NEXTCLOUD_DEV enabled
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-05-02 16:34:51 +02:00
Nextcloud bot 75d0e9aebe
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-02 02:40:54 +00:00
Nextcloud bot 593d133a20
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-05-01 02:42:24 +00:00
Nextcloud bot 8cbb7c3cd3
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-30 02:41:22 +00:00
Matthieu Gallien 5ee1afe3dd
Merge pull request #6710 from nextcloud/ci/improveBuildAppimageScript
Ci/improve build appimage script
2024-04-29 11:14:42 +02:00
Matthieu Gallien 64d54a17b6
as far as I can tell icon name is always Nextcloud.png
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-29 11:13:56 +02:00
Matthieu Gallien b58723fe6f
buildAppimage script gets configurable paths via env variables
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-29 10:02:50 +02:00
Matthieu Gallien 2ba242527c
makes Qt path and openssl path depend on environment variable
provide default value that may work outside our docker build images

will get sensible value when run inside our official docker build images

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-29 09:52:54 +02:00
Nextcloud bot f4d9fbc2e7
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-29 02:40:52 +00:00
Claudio Cambra 6a6d92d89b
Merge pull request #6602 from nextcloud/bugfix/clarifyInvalidFileNamesWarning
invalid item name warning: use file or folder when appropriate
2024-04-28 17:01:38 +08:00
Matthieu Gallien 7262d11f47 invalid item name warning: use file or folder when appropriate
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-28 16:57:22 +08:00
Claudio Cambra 95c7debfb5
Merge pull request #6703 from nextcloud/rakekniven-patch-1
chore(i18n): Improve grammar
2024-04-28 16:50:41 +08:00
rakekniven 9ec5712b21 chore(i18n): Improve grammar
Fixes #6408 

Reported at Transifex.

Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com>
2024-04-28 16:48:08 +08:00
Nextcloud bot 06c4ea8e25
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-27 02:50:59 +00:00
Matthieu Gallien 810d8f15ee
Merge pull request #4584 from nextcloud/feature/qt6
Migrate to Qt 6
2024-04-26 09:05:38 +02:00
Matthieu Gallien 3770eec050 windows needs an explicit QML import path to be set
on windows qml modules are not default loaded from the install folder of the app

set it such that qml modules are imported from teh installed qml folder

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 07c2554eb3 AppImage build tool for Qt needs to know our qml files
gives the path to qml files such that needed Qt qml modules are deployed
correctly inside the AppImage package

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 347285b5b3 fix build issue after rebase
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 5bed41a670 use the correct Qt6 build in newer CI images
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 794db304f9 fix compilation of AppImage packages
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 2d5753c17d make it easy to find out that this branch is Qt6 based
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien a357570633 fix compilation after rebase
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien bebb8e1954 fix compilation issues on windows
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 20db6b6d86 fully qualify types for use with Qt metaobject system
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 1533670e77 add NextcloudSslCertificate to wrap QSslCertificate in QHash
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien a05ac621bf do some header includes clean-up as recommended by compiler
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 9b9ff4f471 add needed namespace for declarations read by Qt metaobject system
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 3099628770 header changes needed for clang/vs2022 compilers
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 82a0c1d054 NetrcParser tests are known to be broken
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 7d132029a2 use new qt 6.6.3 ci images
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 060181f83d fix some of the qml issues
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien aae9e84438 fix compilation issues after rebase
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
tobiasKaminsky 8ad2a82ea9 Signed drone.yml
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 4f178fa9be fix order of init in a constructor to avoid warnings
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 3ad7ac922a avoid warning by ignoring the return value of QtConcurrent::run
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 910d7f03e8 when loading translation catalog, do not ignore the return value
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 7432fb4980 adapt ci checks to Qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 430d56e72e build image for Qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien ced6d3274c build appimage
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 332f069491 use the new CI images with Qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien b7bba50672 let appimage build script work with Qt6 based version
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 44f6d514ff only try to include qt6-keychain header and not the qt5 version
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 032af80d4d fix computation of timeout for jobs to have proper bounds (min < max)
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 09e60744c2 e proper data type in data for activity model automated test data
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien fa766c02ac properly use QStringView to avoid copies
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 670b2ce42f fix automated tests missing toString() convert from QVariant
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 274d866c19 fix failing automated test that erases invalid iterator
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien ced85ac287 fix automated tests with network requests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien e3456847d8 last step
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 70931fb0af Remove commented out broken quick compiler check in Qt6
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 5d765dd017 Fix user status selector
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 3765df627b Fix test compilation
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 3597766fb0 Add separators to SyncStatus and UnifiedSearchInputContainer to stop the scrollviews looking broken
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 52758a00b8 Fix QML coloring issues
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 5087d5142a Fix all broken QML imports
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 7e62368eb2 Fix QDateTime string formatting
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra c31e65c111 Remove crashing QRandomGenerator seed call
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra a0e90cf56b Remove use of QCoreApplication AA attributes
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra a8e7e340aa Replace use of staticQtMetaObject with staticMetaObject
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra aaea45110f Replace removed progress bar option orientation with state flag
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 7a17a51a25 Use QEnterEvent for new enterEvent parameters
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 7e1448bcf2 Remove use of qRegisterMetatypeStreamOperators
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 84414ce5dc Replace deleted '+' operator for flags with '|' operator
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 6210490109 Replace now invalid '+' operator with '|' operator for QKeySequence
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 1a2db488ab Add missing QActionGroup include
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 7954695783 Add missing QStandardPaths include
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra ac1206a0c1 Remove conflicting alias to QStringList
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 366f5f0303 Fix bad conversion to bool of shared pointer
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 6a497cf21c Fix QTextCodec related build issues
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 4cad9ebdac Fix type of decpoint
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 9932956686 Remove qtokenizer in favour of Qt6 QStringTokenizer
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 29b0d2b8ad Remove use of QNetworkConfiguration in Qt6
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra 79a150baf4 Fix qtkeychain imports with Qt6
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Claudio Cambra fe7c00a7bf Fix macOS-specific CMake things with Qt6
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 4566400ee6 streamline find_package calls to really find Qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien aa76de9b68 allow detection of qt5 or qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien d3442d137a disable qt apis deprecated before qt 5.12, enable warnings
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 1f0279e1c1 remove usage of QStringRef due to it being missing in Qt6
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien b63c88e492 add missing QStringLiteral
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien ee245f26c8 replace deprecated QWebEngineProfile::setRequestInterceptor
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 249e0eb87d port away from QWidget related margines APIs
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 20ee506b71 port away from deprecated API from QFontMetricsF
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien d035c26be5 replace qrand/qsrand
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien b712108229 add missing Qt:: namespace when using Qt::endl
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 80b25d36fa port away from QStringList::toSet
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Matthieu Gallien 21464063b6 port away from QDesktopServices::storageLocation
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-26 09:05:12 +02:00
Nextcloud bot aa175036e9
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-26 02:44:27 +00:00
allexzander 7e801d6c9a
Merge pull request #6691 from nextcloud/bugfix/slow-sync-with-tray-open
Bugfix/slow sync with tray open
2024-04-25 10:18:13 +02:00
Matthieu Gallien 59fc619c35
Merge pull request #6696 from nextcloud/bugfix/conflictdialog-multiple-darkmode
Bugfix. Conflict dialog for multiple files. Fix checkbox border colors for dark mode on Windows.
2024-04-25 10:14:21 +02:00
alex-z 2ea6d3fd38 Bugfix. Conflict dialog for multiple files. Fix checkbox border colors for dark mode on Windows.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-25 10:14:11 +02:00
Nextcloud bot 98e1d71f70
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-25 02:42:00 +00:00
alex-z 9fac497e6e Hotfix. Slow download speed while tray is open. Do not run rotation animation in syncstatus in tray.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-24 20:28:24 +02:00
alex-z fc31ac4a1f Just for test. Disable status update to avoid havin a sync status animation in tray.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-24 20:28:24 +02:00
Matthieu Gallien caa4d8943c
Merge pull request #6660 from nextcloud/bugfix/fileslock_incorrect_readonly
Bugfix. Files lock. Fix incorrect readonly state.
2024-04-24 15:17:39 +02:00
alex-z 47a605c654 Bugfix. Files lock. Fix incorrect readonly state.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-24 13:28:22 +02:00
Nextcloud bot e18ab96882
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-24 02:41:27 +00:00
Camila Ayres d415b08101
Revert "Bump version to 3.13.0."
This reverts commit 9f968f6821.
2024-04-23 20:37:38 +02:00
Camila Ayres 9f968f6821
Bump version to 3.13.0.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-04-23 20:26:09 +02:00
Claudio Cambra ef08c5eb4e Only show successful debug archive creation dialog if it has indeed been successful
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:16:53 +02:00
Claudio Cambra ef2423da53 Prevent crash on creating debug archive in non-writeable location
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:16:53 +02:00
Claudio Cambra 2323b843f0 Give debug archive save location dialog a default location
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:16:53 +02:00
Claudio Cambra 64b7282bd4 Re-run update sync paused state slot when folder list has changed
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:15:13 +02:00
Claudio Cambra d3aa7f8f51 Make sure to emit syncIsPausedChanged in syncIsPaused setter
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:15:13 +02:00
Claudio Cambra ccf6b5abe1 Make sure to emit relevant signals and set sync is paused to true if relevant
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:15:13 +02:00
Claudio Cambra d497e265df Extract syncIsPaused initialisation into new updater slot
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:15:13 +02:00
Claudio Cambra 92f6de9ca9 Always correctly set values for all fields in File Provider sharing UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:12:56 +02:00
Claudio Cambra 1c84b832fe Fetch macOS VFS package in autoupdater if the client is using the file provider module
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:11:41 +02:00
Claudio Cambra 591d5eebd2 Prevent use of invalid characters for file provider domain names
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:11:11 +02:00
Claudio Cambra 8f6c19e029 Fix "false" error about bad applying of nodiscard to value type
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 19:10:40 +02:00
Matthieu Gallien 510b3edc3c
Merge pull request #6606 from nextcloud/bugfix/doNotImplicitlyHydrateFilesDuringSync
do not cause implicit hydration of virtual files during sync
2024-04-23 18:28:34 +02:00
Matthieu Gallien 2141ccdb7d do not cause implicit hydration of virtual files during sync
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-23 18:28:25 +02:00
Matthieu Gallien 5450552339
Merge pull request #6621 from nextcloud/bugfix/fixMsgVfsState
if a virtual file change but nothing changed: set it as in sync
2024-04-23 18:04:48 +02:00
Matthieu Gallien 1522d01d5b if a virtual file change but bothing changed: set it as in sync
some software (at least outlook native software) may fiddle with CfApi
placeholder metadta and set a .msg file to be out of sync state when
opening it

in that case, we will let the sync engine go over it and decide what to
do

it is then possible in that case that we would just put it back in "in
sync" state

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-23 18:04:35 +02:00
Claudio Cambra 03fe6494e4
Merge pull request #6670 from nextcloud/bugfix/dav-user-fileprovider
Use davUser instead of direct credentials user in file provider
2024-04-23 23:55:33 +08:00
Claudio Cambra 80afd8e737
Use davUser instead of direct credentials user in file provider
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-23 22:24:20 +08:00
Matthieu Gallien c0af76ca5f
Merge pull request #6663 from nextcloud/bugfix/editLocallyShouldWorkForAllMachineUsers
create registry keys needed for edit locally in local machine category
2024-04-23 14:49:03 +02:00
Matthieu Gallien 06c2fecbe1 create registry keys needed for edit locally in local machine category
creating the registry keys via installer for the current user is not
enough to have the feature enabled for all user accounts on a given
machine

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-23 13:48:14 +02:00
Matthieu Gallien 23772f18e0
Merge pull request #6622 from nextcloud/ci/endToEndTests
improving end-to-end tests
2024-04-23 13:44:43 +02:00
Matthieu Gallien cd924de9d3
improving end-to-end tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-23 12:21:10 +02:00
Matthieu Gallien dd8a16f9d6
improving end-to-end tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-23 12:21:09 +02:00
allexzander f40b8ae198
Merge pull request #6655 from nextcloud/bugfix/folder-conflict-disappear
Bugfix/folder conflict disappear
2024-04-23 12:18:25 +02:00
alex-z b52906a8a6 Fix tests failure. Refactoring.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
alex-z f490989a1a Fix CI errors.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
alex-z 9ae60258e1 Unit tests for diverse conflicts in one folder.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
alex-z 57f6c7cda2 Also support nested folder scenarios.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
alex-z d2bfb59d6a Bugfix. Folder invalid char conflict. Do not update parent folder record if it contains conflicted subfolders.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
alex-z d240ed9d50 Bugfix. Folder case clash conflict. Do not update parent folder record if it contains conflicted subfolers. Also fix crash with local rename.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-23 12:18:12 +02:00
Nextcloud bot f4acb5099f
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-23 02:43:21 +00:00
Matthieu Gallien b7dd6ff748
Merge pull request #6596 from pyromaniac2k/master
Fix tests
2024-04-22 16:08:38 +02:00
Thomas Witt a5a31321f8 UserStatusDialog.cpp: Fix timezone
This test fails on the day before a timezone switch:
   Actual   (model.clearAtDisplayString()): "23 hours"
   Expected (tr("1 day"))                 : "1 day"

Setting the timezone to UTC remedies this problem.

Signed-off-by: Thomas Witt <pyromaniac@exherbo.org>
2024-04-22 09:47:49 +02:00
Thomas Witt d0097ce25c SyncConfilctsModel test: use FakeAccountState
Using the real account state needs an internet connection.

Additionally, `example.de` is a valid existing domain, which should
probably not be used in testing. Switching to `example.com` as this is
recommended here.

Signed-off-by: Thomas Witt <pyromaniac@exherbo.org>
2024-04-22 09:47:49 +02:00
Thomas Witt 8a9de185a9 pushnotification testutils: Bind only to LocalHost
LocalHost is enough to complete the tests, and it enables the test to be
run under networksandboxing.

Signed-off-by: Thomas Witt <pyromaniac@exherbo.org>
2024-04-22 09:47:49 +02:00
Nextcloud bot 1cb798b6c7
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-22 02:42:25 +00:00
Nextcloud bot 0aea31d877
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-20 02:48:12 +00:00
Nextcloud bot 0203b5423f
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-19 02:43:07 +00:00
Claudio Cambra fcb5380437
Merge pull request #6648 from nextcloud/bugfix/fp-sharing
Fix possible issues with item metadata acquisition required for macOS VFS file sharing
2024-04-18 13:13:03 +08:00
Claudio Cambra b80afca177
Wrap access of itemUrl in security scoping
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-18 04:56:31 +08:00
Claudio Cambra 59928a6c33
Explicitly set bundle name and identifiers from env vars in FileProviderUIExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-18 04:56:31 +08:00
Claudio Cambra 96f1ba656f
Unify FileProviderUIExt entitlements
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-18 02:26:49 +08:00
Claudio Cambra 19cf69ccd3
Make sure network error is shown in UI instead of generic error
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-18 02:07:48 +08:00
Claudio Cambra ac1b11708f
Improve logging across file provider sharing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-18 01:58:38 +08:00
Claudio Cambra a899fe19fa
Merge pull request #6632 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.11.0
Build(deps): Bump cpp-linter/cpp-linter-action from 2.10.2 to 2.11.0
2024-04-17 17:22:26 +08:00
dependabot[bot] 75020c03ce Build(deps): Bump cpp-linter/cpp-linter-action from 2.10.2 to 2.11.0
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.10.2 to 2.11.0.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.10.2...v2.11.0)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-17 17:06:43 +08:00
Claudio Cambra aeca31af97
Merge pull request #6642 from nextcloud/bugfix/file-provider-testing
Make use of NextcloudFileProviderKit in File Provider Module
2024-04-17 16:21:16 +08:00
Claudio Cambra f0f995c260
Pin NextcloudFileProviderKit version to 0.9.0 (up to next major version)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:18:17 +08:00
Claudio Cambra dff6428a75
Remove now-unneeded Realm dependency from FileProviderExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:17:42 +08:00
Claudio Cambra 77f9096538
Set changeobserver as delegate for nkcommon
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:05 +08:00
Claudio Cambra 5977a7c92d
Remove client-side push notification handling for file provider extension in favour of simply using NCFPK remote change observer
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:05 +08:00
Claudio Cambra 68370ade88
Add change observer from NCFPK
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:05 +08:00
Claudio Cambra 6a64248ff6
Always signal enumerator after errors to try and recover from what the error might have been
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra 33e2c084a5
Provide NCFPK enumerator with extension domain
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra 7664509e22
Correctly set up NCKit instance with account string
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra 9e7ce1640d
Remove unused components in FileProviderExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra c082c446c1
Fix passing of wrong item into item.modify
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra dab28f20f4
Improve logging in delete item procedure of FPExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:04 +08:00
Claudio Cambra 485b07a805
Use NCFPK item modify method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra c9a131736a
Use NCFPK Item.create in createItem
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra 2373cd4dde
Use fetchContents from NCFPK item
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra 3ec18ba1a6
Simplify FileProviderExtension's deleteItem method by leveraging Item's delete method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra 788fd7f363
Greatly simplify item method of FIleProviderExtension by using item storedItem method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra 6200cab957
Use new convenience method to get Item for root container from NCFPK
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:03 +08:00
Claudio Cambra d74d23cedb
Use thumbnail fetching procedure from NCFPK
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra 1f78b9f685
Fix build of ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra e8d1afa3df
Adapt to new NextcloudFileProviderKit item nomenclature
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra 913d724254
Make use of NextcloudFileProviderKit materialisedenumerationobserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra 2c0688f82b
Use FileProviderItem from NextcloudFileProviderKit
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra 5a774756b6
Use Enumerator in NextcloudFileProviderKit
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:02 +08:00
Claudio Cambra b9483f0c55
Remove use of all code now available in NextcloudFileProviderKit
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:01 +08:00
Claudio Cambra e7616e0e54
Add NextcloudFileProviderKit dependency
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:01 +08:00
Claudio Cambra 3c3e3aa353
Remove FileProviderExtTests
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:01 +08:00
Claudio Cambra 3066f58673
Allow NextcloudFilesDatabaseManager to take a specific realmconfig in constructor
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:01 +08:00
Claudio Cambra 2caa43a76d
Database manager does not need to be an NSObject
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:01 +08:00
Claudio Cambra af9a271662
Add test target for file provider testing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:15:00 +08:00
Claudio Cambra 3afa861f91
Merge pull request #6614 from nextcloud/feature/file-provider-sharing
File sharing for macOS VFS (File Provider Module)
2024-04-17 16:12:56 +08:00
Claudio Cambra d066536de0 Set file provider extension target as a dependency of file provider ui extension target in CMake
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 206d7cf3f4 Allow SuggestionsTextFieldKit to use up to next major version
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5e80827c1f Upgrade NextcloudCapabilitiesKit version
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1beb04371c Add a "no shares" label if there are no shares available in share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3bdb1ca1cb Update description label in share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5070c370a6 Display shareWith in nkshare displaystring extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra beedbbb471 Pin NextcloudCapabilitiesKit version
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra cfda22c107 Pin NextcloudKit version
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1285b02770 Set suggestionstextfieldkit to 1.0.0
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra af2b2e7aa1 Clean up TODO comments in file provider ui ext
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 395cf9649c Fix SuggestionsTextFieldKit import
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4464f7e460 Simplify configuration of text field delegate
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 66f77233a8 Fix suggestion labels for sharees
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra db20e44850 Use suggestions window controller in share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 07b6391688 Improve logging in ShareeSuggestionsDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 798e060032 Post suggestions changed notification in ShareeSuggestionsDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra c91d5827ab Update suggestions on inputString change in sharee suggestions data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ad43c13882 Add converter method fro nksharee to suggestion
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3012976d9b Add sharee fetcher method to shareesuggestionsdatasource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 74cea5e57a Add basic shareesuggestionsdatasource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d6e67a1882 Add SuggestionsTextFieldKit dependency
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ca94b452f1 Add package dependency NextcloudCapabilitiesKit to NextcloudIntegration
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e54652b690 Retry getting information from FileProviderExt if doing so has failed (usually due to opening share view before auth details are present)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1f3d636a92 Deduplicate error presentation in ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra bab3b4181c Fix options view disappearing when clicking create button in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d2242ea9a4 Fix letter used to identify shareability in sharetableviewdatasource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 807371e1b7 Fix scan path to retrieve item metadata in share table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 9621dede1f Ensure interpolated logging string in share table view data source is public
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 65db197a62 Check if the given item is shareable before fetching shares
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4d3e63009c Add method to ShareTableViewDataSource to fetch the given item's metadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f7dba3e4c6 Add more debug logging to sharecapabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3e7cde632e Fix default states around passwords for share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 49303045b7 Fix ShareCapabilities parsing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 7be5541cde Fix password capabilities for public link capabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3bb6f43bdf Update form layout according to picked type when picked type changed
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ce1bf89a99 Setup the fields in share options view when creating a new form according to capabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra b7541fc783 Update reset to make sure it also resets min and max dates in date picker, don't affect share type
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra b074d19e6e Extract conversion of picked menu item in share type picker into NKShare ShareType into new method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3bfbb38e0f Do not bother fetching shares if sharing is disabled on server
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra cfd8c00e94 Fetch sharing capabilities in ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8039fcd951 Add init for ShareCapabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3ffcd6de42 Add init for public link capabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1e811fead5 Add initialiser from dictionary for EmailCapabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 15651f6a0c Restructure ShareCapabilities
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 2d0cdb3716 Add ShareCapabilities struct
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra b23706633e Adapt visibility of note recipient text field upon toggling checkbox
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f1adfcf8b7 Adapt visibility of expiration date field upon toggling checkbox
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4be8bace48 Adapt visibility of password field upon toggling password checkbox
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d12cdebf48 Reset newly added fields in ShareOptionsView correctly
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d0195e67c3 Properly handle failure state when creating new share in share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 31a9db9e25 Correctly treat share recipient text field in the share options view flow
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra c5d57cde1f Add a text field for relevant shareWith in ShareOptionsView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 54f3822b3c Have specific cancel behaviour in shareoptionsview on delete if in create mode
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 83ed6ea35f Change deleteButton to a cancel button when in creation mode
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra a7637257d5 Adjust share options view title depending on create mode
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 70cb2bd4c1 Make share options view capable of creating or updating view upon clicking "save" depending on create status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4029458eff Connect different share type popup button menu items to outlets in share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 131973b935 Make create button in share view controller show options view and toggle create mode
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e1997bd1fd Add "createMode" toggle to share options view, start building for use to create new shares
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 0ceb6a9481 Add additional options to share controller creation method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 000fe02bb0 Nilify itemServerRelativePath upon loading item in table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8d3b101569 Make item's server relative path public in sharetableviewdatasource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e25bebcd69 Add static method to create shares in ShareController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3a58fbeef6 Add a popupbutton to share options vie to select type of share
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 15f03d6417 Add button to create new share in share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 2085a1214e Remove unused outlets in tableitemview
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ff305ec9fc Implement deletion functionality for share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra a4551b25c6 Improve design of save and delete buttons in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8b285a7ea9 Add deletion capability to sharecontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e52d6dfb23 Properly handle permissions in shareoptionsview
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 49450d52f6 Add convenience property to nkshare extension to see if sharees can edit share file/folder
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 41f229c7c3 Add PermissionValues to NKShare extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 7e3769cc45 Blend in-window contents in share view loading overlay
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4aaaf5b15b Show error in share table view data source if received one in fetch
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 69dfe596e2 Present error for updating share in file provider UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f1d3798396 Add method to dismiss error in share view controller via dismiss button
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 24ce9a1e1e Add method to show error in data source ui delegate
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 9d6db4fa45 Add views to present an error in the share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 30b4509457 Deselect table view or shares after reload to reset UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra a7de8edd16 Fix crash on reloading share table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5702b163c8 Implement more logging in sharecontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 249dd02e75 Reload data source on share saving
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f1363040fa Use a sharecontroller in shareviewcontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 08917a9559 Make save button functional in share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e2b62e492f Add fields as parameters to save in sharecontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 3ef88137ad Correctly handle NKShare's "canEdit" in share options view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 19316f4d3a Add convenience function to enable or disable all share option fields
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 422a6c7962 Respond to changes in share of sharecontroller in shareoptionsview
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 755897be55 Add convenience function to format date to valid server string in nkshare
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f654a8ca83 Use ShareController in ShareOptionsView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 29262345cd Add functionality to share controller to save changes
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 318661748d Add convenience property to nkshare extension to get valid expiration date string
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra aa57ba10f3 Add starter sharecontroller for fileprovider
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5e0d990308 Handle optionsview as ShareOptionsView in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ec145d8ca2 Handle state of share options view via setting of NKShare
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 52dbfc3109 Improve placeholder text for share view password field
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 322ae529b7 Add a ShareOptionsView class
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 7a40f6f728 Ensure calls are made via main actor to delegate in share table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 26d635b6fa Tell delegate when fetch ongoing in share table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e113a09ecf Add fetch related handling functions to ui delegate
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 55cdbe860b Add a loading overlay to table view to indicate fetch in progress
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 13fa2bca1d Implement hide and show of share options in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra c74575cd32 Re-integrate share options view into main share view controller XIB
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8956fcebe9 Implement ui delegate in ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 9b1ad4d6d5 Treat bottom view in share view as a target view to inject other views
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d23f30ae4e Add a shareview ui delegate protocol for datasources
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1b64e29050 Extract options view to different XIB
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 70a565d60d Redesign shares to incorporate share options into main share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d3d75e85f6 Add starter ShareOptionsWindow class
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ecf4efd481 Replace ShareOptionsView with a ShareOptionsWindow
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra cad56f1e2a Implement ShareOptionsView design
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra da8ae80544 Add starter ShareOptionsView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 18862bed35 Add popover and corresponding view controller to sharetableitemview
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 30673976be Temporarily change copy share link button image after clicking
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 540eb11bf2 Implement share link copying in share table item view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4f0e25a4e6 Improve share table style
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 18de0d0b3f Make share property central to display update of share table item view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 46e2ca5887 Add displayString property to NKShare extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 41f93371a2 Add typeImage property to NKShare extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 930106e7ac Add ShareType enum to NKShare extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 4ba2bc8a3b Add starter NKShare extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra dda5ec295e Customise prepareForReuse in ShareTableItemView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra b6c247ba8d Ensure usable rowHeight for shares table view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ee21715860 Set basic property of sharetableitemview in delegate method of data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra c2ec72c132 Add corresponding class for ShareTableItemView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 731a5e1f8f Set delegate for shares table view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f11807815a Remove default item view in ShareViewController table XIB
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ff9e344810 Move all share fetching logic to FileProviderUIExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 331a76195b Register shareItemView nib in shareTableView
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8add57a048 Implement NSTableViewDelegate viewFor method in sharetableviewdatasource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 765f33b45c Add essential table view data source method to ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d53680e583 Add release entitlements fro FileProviderUIExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 145d92e5cb Add method to get an item's server path through FPUIExtensionService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e6f20e9498 Remove logging from NextcloudAccount
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 81dc8ce708 Add method to FPUIExtensionServiceSource to get extension account details
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra a6992cf38b Add NextcloudAccount to FileProviderUIExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 259a28e33e Add method to convert dictionary to NextcloudAccount
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 02b5a31eae Add method to export NextcloudAccount details to dictionary
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra adb4028a6b Fix linking of FileProviderUIExt which was missing NextcloudKit symbols
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5b1b87b788 Add FPUIExtensionServiceSource to services published by FileProviderExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 9ce3af3de7 Fix visibility of logging in ItemSharesController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra e15db9b938 Fix protocol used for FPUIExtensionServiceSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 46ad6f0bfb Instantiate share data source in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1288448336 Add item shares loading routine to ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 6dad778498 Add share fetching routine to FPUIExtensionService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra af93122445 Move serviceConnection method to table view data source
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8dc9807eb2 Add base properties to ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 820c7e4bd1 Add outlet for share view table view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra b30f8c30d7 Add starter ShareTableViewDataSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 90f7e3a2e3 Add more complete ShareTableItemView design
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 023e1453ba Add starter ShareTableItemView XIB
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra a586767f00 Add table to ShareViewControlelr XIB
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 07473ac5d9 Add fetch method to itemsharescontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 0feb5da08f Add starter ItemSharesController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8982857833 Add starter FPUIExtensionServiceSource
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 36a829849b Implement convenience method to acquire FPUIExtensionService in share view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra dbe8a5f8f2 Add starter FPUIExtensionService protocol
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 5fbcec1400 Extract display updating upon url acquisition to different method in ShareViewController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 09042d701d Set the right filename and icon on share view controller view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1e34affc01 Add domain property to document action view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 9bb67ee68d Make close button work properly in share view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 7a9ca59734 Store file provider item identifiers in ShareViewController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra ce026dfd94 Add proper design for share view in file provider UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 13aced88ce Properly add share custom action
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 05e7f1b992 Remove unused IBActions in DocumentActionViewController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 45b4fee7ba Add convenience method to prepare child view controllers in DocumentActionViewController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 76045fd989 Create ShareViewController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f44fec2ff1 Add logging for prepare methods of document action view controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra f35148b1bf Add logger extension to FileProviderUIExt
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra d0baa23b5d Add FileProviderUIExt entitlements file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 8128697a70 Build and install FileProviderUIExt via CMake
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra 1522c22576 Add new FileProviderUIExt target
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 16:11:50 +08:00
Claudio Cambra fe6f47e3b9
Merge pull request #6635 from nextcloud/bugfix/crash-mac-vfs-toggle
Fix crash when in debug mode when toggling enabled status of an account's virtual files (macOS)
2024-04-17 04:44:27 +08:00
Claudio Cambra 3dcef75c57
Fix crash when in debug mode when toggling enabled status of an account's virtual files (macOS)
Caused by comparing to the wrong thing in the Q_ASSERT *facepalm*

Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-04-17 03:05:46 +08:00
Nextcloud bot 3ab692c26e
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-15 02:37:33 +00:00
allexzander adc7a22491
Merge pull request #6613 from nextcloud/feature/office-files-lock-newly-created
Feature/office files lock newly created. Plus refactoring.
2024-04-13 15:58:03 +02:00
alex-z dbde9e3a2b Detect office files for locking on new upload. Notify FolderWatcher.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-13 15:46:37 +02:00
Matthieu Gallien d6ed67806e
Merge pull request #6472 from nextcloud/nextcloud-dev
Add cmake NEXTCLOUD_DEV so debug client can run in parallel to release client.
2024-04-11 18:43:10 +02:00
Camila Ayres 63b0a9f94d Add cmake NEXTCLOUD_DEV so debug client can run in parallel to release client.
- Defaults to OFF.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-04-11 15:42:54 +02:00
Camila Ayres d9d4101ca7
Bump version to 3.13.50.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-04-11 15:05:32 +02:00
Matthieu Gallien 3a7ae094d3
Merge pull request #6519 from nextcloud/ci/fixWindowsCi
fix windows and macOS ci checks
2024-04-10 23:53:37 +02:00
Matthieu Gallien c588fdc049
just use the default value for "Packager/RepositoryUrl"
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:27:43 +02:00
Matthieu Gallien cf97b439d0 disable coverage on windows due to a crash
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Matthieu Gallien ee28ef818f update python for macos and update deployment target
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Matthieu Gallien 49650f2f7a install inkscape using same cache as OpenCppCoverage
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Matthieu Gallien 36aadaaa18 install inkscape via chocolatey after other dependencies
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Matthieu Gallien a84c72248f switch Craft cache to proper location
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Matthieu Gallien df8115d1be switch CI to python 3.12
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-04-10 17:05:56 +02:00
Claudio Cambra fc9e69e884
Merge pull request #6524 from Zocker1999NET/patch-1
Add typical Syncthing files to default exclusion
2024-04-10 14:34:59 +08:00
Felix Stupp 04461c758b Add typical Syncthing files to default exclusion
For increasing default interoperability between Syncthing & Nextcloud if users may want to sync a (sub-)directory with both tools without thinking about that those hidden files synced by Nextcloud might break Syncthing

Signed-off-by: Felix Stupp <felix.stupp@banananet.work>
2024-04-10 14:34:45 +08:00
Claudio Cambra 406ff17a29
Merge pull request #6579 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.10.2
Bump cpp-linter/cpp-linter-action from 2.10.0 to 2.10.2
2024-04-10 14:34:14 +08:00
dependabot[bot] f725851792 Bump cpp-linter/cpp-linter-action from 2.10.0 to 2.10.2
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.10.0 to 2.10.2.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.10.0...v2.10.2)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-10 14:32:15 +08:00
Nextcloud bot 7fb2e975cd
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-10 02:43:38 +00:00
Nextcloud bot 4a5526879a
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-08 02:39:53 +00:00
Nextcloud bot 5d5a92b8d5
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-06 02:50:18 +00:00
Nextcloud bot e3dc48dcad
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-04 02:39:27 +00:00
allexzander 6cc4c026a5
Merge pull request #6563 from nextcloud/bugfix/outdated-call-notifications
Bugfix. Remove seen call notifications from the list.
2024-04-02 23:51:22 +02:00
alex-z 1145d0613b Bugfix. Remove seen call notifications from the list.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-04-02 13:19:34 +02:00
Nextcloud bot 45bb243c38
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-02 02:38:16 +00:00
Nextcloud bot f8477069ff
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-04-01 02:38:52 +00:00
Nextcloud bot 139ac3f44d
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-31 02:40:50 +00:00
Nextcloud bot 47a414e1e9
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-30 02:48:03 +00:00
Nextcloud bot f66d3a3396
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-29 02:44:39 +00:00
Claudio Cambra f13a0fc732 Also prevent use of std::filesystem in macOS 10.14
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-28 19:11:57 +08:00
Claudio Cambra 42504d0a0f Wrap all use of std::filesystem in ifdefs to fix legacy build for <macOS 10.15
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-28 19:11:57 +08:00
Nextcloud bot 9c5b41abda
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-28 02:39:19 +00:00
Matthieu Gallien 6c03f7600d
Merge pull request #6578 from nextcloud/bugfix/allowRenamingFoldersInsideGroupFolders
properly compute if a folder is top level or child extern mounted
2024-03-27 14:29:44 +01:00
Matthieu Gallien 55034f7e43
properly compute if a folder is top level or child extern mounted
asks new permission to server to be able to know if a folder is a top
level mounted folder

should allow detecting the top level folders from external storages or
group folders

should also make the client reliably detect that it is handling a child
folder inside a group folder and be allowed to rename such folders

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-27 14:29:01 +01:00
Matthieu Gallien b6ff0c5abb
Merge pull request #6573 from nextcloud/bugfix/fasterContextMenuWindowsVirtualFiles
context menu: do not recursively check pin and availability states
2024-03-27 12:02:27 +01:00
Matthieu Gallien 608a435d38
context menu: do not recursively check pin and availability states
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-27 11:59:30 +01:00
Matthieu Gallien 9715cb01a0
Merge pull request #6580 from nextcloud/ci/someUnityBuildFixes
Ci/some unity build fixes
2024-03-27 10:34:30 +01:00
Matthieu Gallien 4e643166af ensure we ignore deprecated warnings from openssl in unity builds
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-27 10:34:22 +01:00
Matthieu Gallien d81c20b116 partial fix for compilation with QT_NO_KEYWORDS
need to replace signals by Q_SIGNALS and so on

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-27 10:34:22 +01:00
Matthieu Gallien 9585d994e0
Merge pull request #6582 from nextcloud/bugfix/fixExplorerCrashWindows
Bugfix/fix explorer crash windows
2024-03-27 09:56:53 +01:00
Matthieu Gallien ba78942209 Revert "Allow installation to close shell extension DLLs via the custom action. Disable reboot prompt in case of the version with this change was previously already installed."
Close #6566

This reverts commit c1719bc817.
2024-03-27 09:56:45 +01:00
Nextcloud bot 709deb794c
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-27 02:42:13 +00:00
Nextcloud bot 5c2bd3c193
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-26 02:39:59 +00:00
Nextcloud bot 206b3179d3
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-24 02:42:22 +00:00
Nextcloud bot 170896fdda
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-23 02:49:57 +00:00
Matthieu Gallien 38812ce6b7
Merge pull request #6568 from nextcloud/bugfix/defaultIgnorePatternsShouldBeCorrect
use the proper name when reading system exclude config file
2024-03-22 17:26:07 +01:00
Matthieu Gallien 634f007074
use the proper name when reading system exclude config file
Close #6539

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-22 10:40:45 +01:00
Nextcloud bot 0312a1f398
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-22 02:38:01 +00:00
Nextcloud bot 54fdf85879
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-21 02:40:14 +00:00
Matthieu Gallien 23c812e224
Merge pull request #6137 from tintou/tintou/pkgconfig-define-variables
libcloudproviders: Use DEFINE_VARIABLES to install in the correct directory
2024-03-20 12:28:53 +01:00
Corentin Noël 6e1e8a8bdc libcloudproviders: Use DEFINE_VARIABLES to install in the correct directory
Conditionnaly require CMake 3.28 to install in the right directory.

Signed-off-by: Corentin Noël <corentin.noel@collabora.com>
2024-03-20 12:28:38 +01:00
Matthieu Gallien baa19e485c
Merge pull request #6532 from nextcloud/fix/docs/promptDeleteAllFiles
fix(docs): parameter promptDeleteAllFiles defaults to false not true
2024-03-20 10:09:48 +01:00
Josh f66fb97b30 fix(docs): parameter promptDeleteAllFiles defaults to false not true
The default was changed to false in #1201 

Signed-off-by: Josh <josh.t.richards@gmail.com>
2024-03-20 10:09:39 +01:00
Matthieu Gallien e707d685fd
Merge pull request #6514 from nextcloud/bugfix/minSupportDesktopVerMsg
User 'Connection issue' instead of 'Network error' in systray notification
2024-03-20 10:03:21 +01:00
Camila Ayres 84c08419ff User 'Connection issue' instead of 'Network error' in systray notification.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-03-20 09:51:11 +01:00
Matthieu Gallien 0da5e4a089
Merge pull request #6553 from nextcloud/bugfix/preventMissingContextMenuOnWindowsFileExplorer
if desktop client sent a first reply: wait for the menu data
2024-03-20 09:49:36 +01:00
Matthieu Gallien 4bf1b8604c
if desktop client sent a first reply: wait for the menu data
may need the user to wait a bit longer but should prevent missing the
context menu

should still be robust since we only wait indefinitely if desktop client
sent a first reply

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-20 09:44:33 +01:00
Matthieu Gallien 9a8392c1a3
Merge pull request #6537 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.10.0
Bump cpp-linter/cpp-linter-action from 2.9.1 to 2.10.0
2024-03-20 09:14:59 +01:00
dependabot[bot] 1af85564ac Bump cpp-linter/cpp-linter-action from 2.9.1 to 2.10.0
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.9.1 to 2.10.0.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.9.1...v2.10.0)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-20 09:14:47 +01:00
Nextcloud bot 39b145f3c8
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-20 02:39:44 +00:00
allexzander 53eef98033
Merge pull request #6528 from nextcloud/bugfix/e2ee-vfs-disallow-move
E2EE with VFS. Disallow MOVE as it is not supported. Prevent data loss.
2024-03-20 01:32:20 +01:00
alex-z b749cf9748 E2EE with VFS. Disallow MOVE as it is not supported. Prevent data loss.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-20 01:08:34 +01:00
allexzander ff65c3c1f8
Merge pull request #6529 from nextcloud/bugfix/foldermetadata-fetch-root
E2EE. Fix root metadata fetching path for non-root remote sync folder. Refactoring. Stabilizing paths.
2024-03-20 01:07:52 +01:00
alex-z c5dd2e89a1 E2EE. Fix root metadata fetching path for non-root remote sync folder. Refactoring. Stabilizing paths.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-20 00:54:27 +01:00
Matthieu Gallien 747efd4711
Merge pull request #6521 from nextcloud/bugfix/allowWipeData
allow wipe feature to delete data of a wiped user account
2024-03-19 15:58:09 +01:00
Matthieu Gallien 2c95775edf allow wipe feature to delete data of a wiped user account
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-19 15:58:00 +01:00
allexzander a5b65d1cb3
Merge pull request #6525 from nextcloud/feature/msi-customaction-close-shellextensions
Allow installation to close shell extension DLLs via the custom action. Disable reboot prompt in case of the version with this change was previously already installed.
2024-03-19 15:16:54 +01:00
alex-z c1719bc817 Allow installation to close shell extension DLLs via the custom action. Disable reboot prompt in case of the version with this change was previously already installed.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-19 13:56:07 +01:00
Nextcloud bot 7262696846
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-19 02:39:43 +00:00
allexzander 82da32e3d7
Merge pull request #6547 from nextcloud/bugfix/files-lock-incorrect-readonly-set
Bugfix. Files-lock. Incorrect readonly state for TokenLock.
2024-03-18 13:53:40 +01:00
Matthieu Gallien 3ea60ee8a9
Merge pull request #6542 from nextcloud/bugfix/avoidRepeatingTheSameNetworkError
only display changed network errors during validation of connection
2024-03-18 09:46:52 +01:00
Matthieu Gallien 2346ec226d update parts of CI to get tests to be OK
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-18 09:21:30 +01:00
Matthieu Gallien 8422496bec sonarcloud CI check with address sanitizer
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-18 09:21:30 +01:00
Matthieu Gallien e33832fa1d switch to ubuntu-latest image for CI github actions
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-18 09:21:30 +01:00
Matthieu Gallien 6d1f028ed8 only display changed network errors during validation of connection
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-18 09:21:30 +01:00
alex-z 4a21b290d2 Bugfix. Files-lock. Inorrect readonly state for TokenLock.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-17 17:02:54 +01:00
Nextcloud bot b45376d579
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-16 02:48:33 +00:00
Nextcloud bot 462c35aa02
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-15 02:47:11 +00:00
Nextcloud bot 54a25d8abe
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-14 02:39:59 +00:00
allexzander a399f4dbf6
Merge pull request #6540 from nextcloud/bugfix/e2ee-v1.2-version-double
Bugfix. E2EE. Use 'double' for legacy metadata.
2024-03-14 01:24:10 +01:00
alex-z 7376616375 Bugfix. E2EE. Use 'double' for legacy metadata.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-13 15:18:16 +01:00
Nextcloud bot 7a9cafd589
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-13 04:29:55 +00:00
Matthieu Gallien 61251cde5d
Merge pull request #6343 from nextcloud/feature/applyPermissionsFromServerToFolders
newly created folders will be read-only when needed
2024-03-12 23:30:14 +01:00
Matthieu Gallien ff9953b36b make folder read-write before deleting it
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien 4844f326c1 newly created folders will be read-only when needed
Close #6296

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien bbc976c920 gather more information on exceptions that happen when running tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien bf7f87492a update automated test to work with read only folders
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien 706d9697d4 ensure we get proper logs for permissions automated tests
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien 5dfeb55e0f we require c++-17
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien b0a2d5ff81 adjust AppImage build script to the new build image
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien 969a873a65 update drone signature
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien e98c25af8a switch to newer CI images
should solve the AppImage build issue

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien 9d6d28971e correct category for some log output
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 23:30:02 +01:00
Matthieu Gallien ea9c19e7b4
Merge pull request #6535 from nextcloud/bugfix/avoidDuplicatingNewFolderOnWindows
always store newly created folders in DB even if it is missing
2024-03-12 08:45:42 +01:00
Matthieu Gallien e607e9d1fb always store newly created folders in DB even if it is missing
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-12 08:45:06 +01:00
Nextcloud bot 899b147d59
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-12 02:39:19 +00:00
Camila Ayres 7fb8eb3b79 Set min OSX version via cmake.
It should now only use the CMAKE_OSX_DEPLOYMENT_TARGET set when building.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-03-11 15:20:51 +01:00
Claudio Cambra f805596418 Remove unneeded fallback for undefined cmake_osx_deployment_target
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-11 15:20:51 +01:00
Claudio Cambra bfdbecc416 Only set BUILD_FILE_PROVIDER_MODULE if deployment target high enough
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-11 15:20:51 +01:00
Claudio Cambra bbf280e12e Do not try to link UserNotifications framework when building below 10.14
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-11 15:20:51 +01:00
Claudio Cambra 8e39d8ef97 macosx: Selectively compile UserNotifications component of systray depending on deployment version
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-11 15:20:51 +01:00
Claudio Cambra 2f6ae0c4dd Create systray_mac_common file for common systray functions
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-11 15:20:51 +01:00
Camila dd2ca78884 Change minimun mac OS version to 10.13.
Signed-off-by: Camila <hello@camila.codes>
2024-03-11 15:20:51 +01:00
Nextcloud bot 0071daad61
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-11 02:53:50 +00:00
Nextcloud bot c6d4771e26
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-10 02:39:01 +00:00
Nextcloud bot 799cfc3252
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-09 03:06:48 +00:00
Nextcloud bot 2584fdc2da
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-08 02:39:37 +00:00
Matthieu Gallien ffffc89414
Merge pull request #6515 from nextcloud/work/ervin/socketapi-use-ksystemclipboard-when-available
[socketapi] Replace QClipboard with KSystemClipboard when available
2024-03-07 08:51:22 +01:00
Kevin Ottens 526ab056d6 [socketapi] Replace QClipboard with KSystemClipboard when available
This is necessary in Wayland sessions for the clipboard related actions
of the socketapi to work. Indeed, QClipboard does its job only if we got
a window with the focus in such session.

In the case of the socketapi, it is generally the file manager asking
the desktop client to put something in the clipboard. At this point in
time no window of the client have the focus, so nothing gets added to
the clipboard.

KSystemClipboard on the other hand, uses the wlr data control protocol
under wayland sessions ensuring something ends up in the clipboard as
expected. When not in a wayland session it falls back to QClipboard so
no behavior is lost.

Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-07 08:18:52 +01:00
Kevin Ottens 312da848bc [gui] Add KGuiAddons as an optional dependency
This will be needed to replace QClipboard in some places.

Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-07 08:18:52 +01:00
Claudio Cambra 0e301e75d9
Merge pull request #6467 from nextcloud/feature/file-provide-file-eviction 2024-03-07 11:52:42 +08:00
Claudio Cambra fe53933b04 Improve design of materialised file delegates
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 5f243a86d4 Use palette background color for file provider eviction dialog
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 19d1971612 Enable reloading of materialised items model from within file provider eviction dialog
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 1e96c0f1f9 Add title and reload button to file provider eviction dialog
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 69a3f286c0 Add slot to file provider settings controller to refresh materialised items for account
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra bf4fc01b39 Extract enumeration of materialised files for each domain manager into a separate slot
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 9f4e87dc1f Fix header text color property
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 60f116cdd4 Implement eviction capabilities in FileProviderItem
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Claudio Cambra 3e5766be47 Reintegrate eviction dialog button
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-07 11:51:55 +08:00
Nextcloud bot 2e5aa59757
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-07 02:49:13 +00:00
Matthieu Gallien 7ed0b9afb3
Merge pull request #6320 from nextcloud/work/ervin/support-kf6-based-dolphin
Support the KF6 based version of Dolphin
2024-03-06 15:56:17 +01:00
Kevin Ottens 38b59a82bf [dolphin] Adjust CMake to handle the presence of KF6
Now if KF6 KIO is present, we build the Dolphin plugin against the Qt6
version of everything. Otherwise we keep assuming the Qt5 version of the
platform.

Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Kevin Ottens c03837ee0d [dolphin] Remove the desktop file and go straight to json instead
The desktop to json conversion step will disappear with KF6

Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Kevin Ottens 6dbe8cc075 [dolphin] Don't rely on emit and signals, use the Q_* macros instead
Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Kevin Ottens d98d2cead5 [dolphin] Don't rely on const chat * -> QString implicit conversion
Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Kevin Ottens 17b913c3a7 [dolphin] Don't rely on QByteArray -> const char * implicit conversion
Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Kevin Ottens ea101adbf4 [dolphin] Clean up includes
We remove one unused include which will disappear in KF6. Also we switch
to camel case includes without the module prefix.

Signed-off-by: Kevin Ottens <ervin@kde.org>
2024-03-06 14:52:36 +01:00
Claudio Cambra 6845e24e91
Merge pull request #6461 from nextcloud/feature/file-provider-fast-init-sync
Add option to perform fast synchronisation runs in File Provider sync engine
2024-03-06 18:47:42 +08:00
Claudio Cambra d64d959b51 Add explainer about fast sync to file provider settings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 1a576a15d6 Log setting change for fast enumeration in ClientCommunicationService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 6e55c84d18 Instantiate and hook up FileProviderFastEnumerationSettings within FileProviderSettings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 23ef72c472 Add starter FileProviderFastEnumerationSettings QML component
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 1441a5e3a2 Make default of fast enumeration enabled method in file provider settings controller to be true
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 23c1e554d0 Add method to check if fast enumeration setting has been set in settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 5eb333cb8d Add method to set fast enumeration enabled in file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 6db990183e Add method to acquire fast enumeration enabled status in file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra a02efd65f8 Add method in file provider xpc to set fast enumeration enabled state
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra db9b9a64b4 Add method to get fast enumeration state (if possible) in FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 913dcd70ae Remove wrapper method for creating debug archive, directly use FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 90c26c3eff Make XPC pointer publicly available
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra ae524c0346 Mark fileProviderAvailable as nodiscard
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra bbe48954be Add method to set fast enumeration in client communication service
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 8d3b676558 Add method to get state of fast enumeration setting
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 9a8af68ddc Add lazy var for whether the fastEnumeration value has actually been set in settings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra a3e1d66707 Use value from config for fastEnumeration when creating enumerator
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 534b3a60d7 Add fastEnumerationEnabled property to config
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra f987bcd97a Add computed property to FileProviderConfig to get and set internal config
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 1fc6014230 Add a starter FileProviderConfig
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra cac263f174 Clean up properties and init/invalidate in FileProviderExtension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Claudio Cambra 179a368f9f Add option to do a fast enumeration of changes which ignores new, unexplored folders
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-06 18:46:41 +08:00
Nextcloud bot eb86b9141b
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-06 06:54:44 +00:00
Matthieu Gallien 903e313707
Merge pull request #6463 from nextcloud/bugfix/multipleMovesOfASingleFileid
when moving a file, checks that it exists at origin or destination
2024-03-05 17:19:50 +01:00
Matthieu Gallien b7c1a95d1c
fix missing tracking for some item rename operations
will fix mishandling of rename of a single file to multiple places
during discovery

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-05 16:43:05 +01:00
Matthieu Gallien b35a26091b add new test to simulate the data loss scenario
this new test trigger the assert that a file is either in the old place
or the new place when we execute a MOVE instruction for a local file

in the test one file is discovered as in need of a local MOVE but will
be missing from the old and new places when running the propagation due
to a bug

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-05 14:43:42 +01:00
Matthieu Gallien 976dbd6df6 when moving a file, checks that it exists at origin or destination
current automated tests are never breaking the rule that a file that
should be moved on client side (after a move was done server side)
exists either at teh original path or the destination path.

in practice it may happen (and I manually tested it) thet the sync
engine decides to move a single file in two distinct places

that decision will violate this rule and a debug build would then run
into the assert

Close #6462

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-03-05 14:43:42 +01:00
Matthieu Gallien 7a0b03aabc
Merge pull request #6491 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.9.1
Bump cpp-linter/cpp-linter-action from 2.8.0 to 2.9.1
2024-03-05 14:43:17 +01:00
dependabot[bot] 07d415f348 Bump cpp-linter/cpp-linter-action from 2.8.0 to 2.9.1
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-05 14:43:06 +01:00
Matthieu Gallien 6bd4539e56
Merge pull request #6503 from nextcloud/bugfix/ignorelist-exclude-status-stuck
Bugfix. Exclude list. Fix stuck 'excluded' status in Windows Explorer after removing the exclude pattern.
2024-03-05 12:17:12 +01:00
alex-z d29e5bee7a Unit tests for ignorelist exclude status stuck.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-05 10:40:09 +01:00
alex-z 837f9a4913 Bugfix. Exclude list. Fix stuck 'excluded' status in Windows Explorer after removing the exclude pattern.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-05 10:40:09 +01:00
Matthieu Gallien b88dfb1dbd
Merge pull request #6502 from nextcloud/bugfix/federated-share-activity-actions
Bugfix. Federated share activity show 'Decline' action button.
2024-03-05 10:29:25 +01:00
alex-z 17b0dda300 Unit tests for federated share activity actions
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-05 09:13:12 +01:00
alex-z 0a4452ef3d Bugfix. Federated share activity show 'Decline' action button.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-03-05 09:13:12 +01:00
Claudio Cambra 0826df7916
Merge pull request #6490 from nextcloud/bugfix/expandable-folder-status-model
Bugfix/expandable folder status model
2024-03-05 11:50:17 +08:00
Claudio Cambra e15252a88c Ensure there is a return in case FolderPathRole of FolderStatusModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra c7c34b9f14 Check parent variable nullness in folderstatusmodel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra 0daec2071e Remove unneeded error variable
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra 1a2afd4576 Clean up and add braces for if statements in folderstatusmodel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra 2556681997 Clarify role of idx in slotUpdateDirectories
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra 93a6da6c19 Automatically fetch first level of subfolders for root nodes in folder status model
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra e7a99a8e9a Use braced initialiser where possible for folderstatusmodel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra c070ed2b6d Const autofy where possible in folderstatusmodel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra d66827c91f Avoid excessive single letter variables in FolderStatusModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra c8c7bdbf40 Fix undecided lists in fodlerstatusmodel comment
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Claudio Cambra 4b1f502be7 Modernise and replace foreach loops with for in loops in folderstatusmodel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-03-05 11:48:05 +08:00
Nextcloud bot 4edb8a254e
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-05 02:39:45 +00:00
Nextcloud bot a80ff33fbb
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-04 02:39:35 +00:00
Nextcloud bot ee3734cbb9
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-03 02:37:18 +00:00
Nextcloud bot 6ff6638171
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-02 02:42:21 +00:00
Nextcloud bot 7e1f56bb6d
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-03-01 11:05:47 +00:00
allexzander 53dd8ea5b7
Merge pull request #6484 from nextcloud/bugfix/fix-misleading-log
Bugfix. E2ee misleading log fix.
2024-02-29 12:39:44 +01:00
Nextcloud bot 249fbbc8f7
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-29 02:38:45 +00:00
allexzander 12e9ebadef
Merge pull request #6486 from nextcloud/bugfix/e2ee-v2-nonroot-sync
Bugfix/e2ee v2 non-root sync
2024-02-28 21:52:23 +01:00
alex-z c0e0b53ee5 Bugfix. E2EE V2. Fix incorrect root e2ee folder path search in local db.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-28 15:37:50 +01:00
allexzander 38f23827ec
Merge pull request #6479 from nextcloud/bugfix/client-status-reporting-remove-desktop-specific-entries
Client Status Reporting. Only report statuses listed on the server.
2024-02-28 14:56:29 +01:00
alex-z ccf99125b5 Client Status Reporting. Only report statuses listed on the server.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-28 14:47:37 +01:00
Nextcloud bot 553be4287e
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-28 02:48:32 +00:00
Nextcloud bot 0cd31941c0
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-27 02:38:50 +00:00
Nextcloud bot 0d9ebc1184
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-26 02:35:29 +00:00
Nextcloud bot 224b6cc249
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-25 02:40:03 +00:00
alex-z 694a5f4242 Bugfix. E2ee misleading log fix.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-24 14:56:06 +01:00
Nextcloud bot 11900b7080
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-24 02:59:39 +00:00
allexzander e5ac6d2935
Merge pull request #6454 from nextcloud/bugfix/fix-crash-syncfolder-remove
Fix crash when deleting a local sync folder during sync.
2024-02-23 13:00:17 +01:00
alex-z 7160c05033 Fix crash when deleting a local sync folder during sync.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-23 12:46:17 +01:00
Nextcloud bot 67c5479793
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-23 02:36:37 +00:00
allexzander 36fc73eab2
Merge pull request #6471 from nextcloud/bugfix/e2ee-v1-deryption
E2EE. Allow decryption v1.0 and v1.1 folders.
2024-02-22 20:08:24 +01:00
alex-z 484426bae2 E2EE. Allow decryption v1.0 and v1.1 folders.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-22 18:50:18 +01:00
allexzander 094947bfb1
Merge pull request #6456 from nextcloud/feature/vfs-cfapi-detailed-error-message-for-leading-hash
VFS. CfAPI. Provide detailed error message for leading '#' placeholder update failure.
2024-02-22 18:37:19 +01:00
alex-z 74bef928a3 VFS. CfAPI. Provide detailed error message for leading '#' placeholder update failure.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-22 16:56:15 +01:00
allexzander 0364cd29c7
Merge pull request #6458 from nextcloud/bugfix/caseclashconflict-dialog-vfsmode-freeze
Use 'FindMimeFromData' from Win API instead of QMimeDatabase() functions to get the mimetype. Prevents freeze from VFS placeholders.
2024-02-22 12:37:05 +01:00
alex-z 8aaa0d4c13 Only match file extension not its content for mimetype. Prevents freeze from VFS placeholders.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-22 10:18:12 +01:00
Nextcloud bot 47d1800404
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-22 02:40:30 +00:00
Nextcloud bot 373d6d088d
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-21 02:36:42 +00:00
Nextcloud bot 8893569d7f
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-20 02:35:56 +00:00
Claudio Cambra 478d65c17b
Merge pull request #6351 from nextcloud/feature/file-provider-config-ui
Add a configuration interface for macOS File Provider virtual files
2024-02-19 22:46:45 +08:00
Claudio Cambra 92a1d808ef
Temporarily remove eviction dialog button until eviction works on extension side
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra 28cfd8fe70
Mark MacImplementation for FileProviderSettings destructor as override
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra b4e1338bc8
Ensure [[nodiscard]] in new file provider classes
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra a428256e4f
Limit gigabyte sizing string to two decimal figures in file provider storage info
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra 81d4aa7446
Use NCProgressBar for file provider sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra 1f70c85e22
Adjust alignment and sizing of file provider sync status icon
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra 918e411b10
Use font sizes in Style.qml in FileProviderSettings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:23 +08:00
Claudio Cambra 472bba8149
Add useful font sizes to Style.qml
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra a469d44123
Provide correct sync state icon in file provider domain sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra 7d8778d12a
Bolden sync state text for fp
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra 7b99a06eb9
Add separator to dynamically loaded fp settings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra 034822fcc6
Fix grid layout for sync status of file provider
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra ed12541e15
Extract File Provider storage settings to separate file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra b9ae82ce3e
Extract sync status layout to separate file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra 2f752a6c5c
Only load file provider settings items if domain is enabled
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:22 +08:00
Claudio Cambra 3060ff6a71
Add current sync status UI to file provider settings view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 9e235f99fa
Enable use of domain sync status class in QML
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra f8ebbe8c71
Fix crash on state change in file provider domain sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 36001aeff7
Add method to acquire domain sync status in settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 9ca418c922
Setup domain sync statuses in settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 9774ac6dde
Make mac implementation of file provider settings controller a QObject
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 21a317cff3
Fix unique ptr compile issue in domain sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:21 +08:00
Claudio Cambra 78732bfeb0
Fix parameter name shadowing in fraction completed methods of domain sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra 6d9c44526d
Update properties via ProgressObserver on FileProviderDomainSyncStatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra f881c1d483
Add main properties to FileProviderDomainSyncStatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra d387eb96ab
Add observing for important properties of nsprogress in progresshandler
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra 13b0244ce6
Add ProgressKVOChangeHandler property to ProgressObserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra e314fd05e7
Acquire upload/download progress in domain sync status
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra 6fa969d8f2
Add a starter ProgressObserver Objective-C class
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra fe6d03a16b
Add q pointer to MacImplementation for FileProviderDomainSyncStatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:20 +08:00
Claudio Cambra 62c85de35b
Define basic FileProviderDomainSyncStatus private implementation
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 7a9a17a2f3
Add starter FileProviderDomainSyncStatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 369c1d737a
Add UI button to signal file provider domain
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra f1c7811962
Add method to signal file provider domain via file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 1d01f67790
Always perform fallback document size discovery in fileprovideritemmetadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 3b179cc1a1
Remove unused usage property from enumeration observer
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra fd95ab7171
Directly generate file provider item metadatas and cache
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 6a0f76de7e
Mark MacImplementation constructor for FileProviderSettingsController as explicit
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:19 +08:00
Claudio Cambra 62bbf6a2ad
Create debug archive on client side
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 9946495edb
Replace handling of debug archive creation in clientcommunicationservice with simple retrieval of debug string
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 571b1ca238
Directly provide text file path to logger for debug archive creation
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra b2e5659c43
Replace old and now broken socket based debug archive command with command over XPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 70521e95bc
Implement creating debug archive in client communication service
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 9733c11f7f
Add button to create debug archive
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 24dda9b437
Add createFileProvider slot to FileProviderSettingsController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:18 +08:00
Claudio Cambra 292ff9b3bc
Add convenience method in FileProvider to send a message to a given file provider domain
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra 0a5df3ba66
Add public convenience static method to get domainIdentifier from a given accountState
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra 1dcfee4087
Add function to File Provider Logger extension to create debug logs file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra 0ddd22ddbb
Add function to retrieve log entries in File provider Logger extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra b9cf135080
Update the materialised items model post-file eviction
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra cfbdf3a773
Provide feedback when materialised item eviction fails
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra a204f114d2
Have backup for 0 value in itemmetadata documentsize in materialised items model
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra 5c3cd69252
Improve visuals of file delegate details UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:17 +08:00
Claudio Cambra f22bf9e527
Display file sizes as nicely formatted string in file provider file delegate
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra 643d30a3d5
Fix retain crash when calling managerForDomain
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra 84d8561a8e
Use FileProviderUtils function to get manager from domain id in FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra b23e6bc4ee
Use FileProviderUtils function to get manager for domain id in FileProviderItemMetadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra 44a6f4673e
Separate domain finding into separate function in FileProviderUtils
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra b47665efba
Add convenience function to FileProviderUtils to get manager for a given domain identifier
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra ba2bef2495
Add fileproviderutils namespace
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:16 +08:00
Claudio Cambra 64e1166066
Fix layout for FileProviderFileDelegate
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra d905f13d83
Enable use of "Delete" button in FileProviderFileDelegate to evict item
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra c763a9a227
Expose items' domain identifier in FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra 0b961e9e17
Add method to evict an item in FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra da20e7e179
Add basic FileProviderFileDelegate UI component
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra b3c75750ab
Add file type role to FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra 371257a6de
Add a user-understandable file type string to itemmetadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra f060f0c271
Add UserVisiblePathRole to FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra beb889c2f6
Get and set userVisiblePath at item metadata construction time
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:15 +08:00
Claudio Cambra e03edc131d
Add method to get user visible path for an item metadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra ad2baeaba0
Keep track of item metadata's parent domain identifier
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra f2547140c4
Ensure correct index is set on hidden tab widget
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra bfaba671f3
Add rolenames for FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra 89bb008bd7
Add additional roles to FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra d51c2a486b
Add a general actions grid to the settings qml component to enable users to manage storage usage
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra af98e48805
Add starter FileProviderEvictionDialog component
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra 2ce4e91383
Add method to generate eviction of materialised item windows in settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:14 +08:00
Claudio Cambra c1a5e788f8
Add method to generate materialised item models in settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra 85b3a135b6
Emit signal when materialised items for an account change
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra f90f86d696
Set FileProviderItemMetadata vector in materialised items model
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra 726503f3c7
Add custom equality check to FileProviderItemMetadata
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra 269fb03351
Add starter FileProviderMaterialisedItemsModel
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra 389e663219
Add method to get qtified file provider item metadata for account in FileProviderSettingsController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra 134eae63dd
Add static convenience method to FileProviderItemMetadata to convert from NSFileProviderItem
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra e3a0dabb07
Add convenience function to convert extendedAttributes to QHash
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:13 +08:00
Claudio Cambra a34a390790
Add a convenience function to convert from nsnamecomponents to qstring
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra 57d1dc84aa
Add a Qt-based data class for file provider items
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra fec9902a25
Store materialised file metadata in file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra c3490db271
Keep track of materialised files in storageuseenumerationobserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra 0b8a2315a0
Leave usage as a property of materialised files enumerator
Do this instead of passing as variable in completion handler

Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra 02b47b47f8
Fix positioning fo elements in FileProviderSettings UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra 679177eae2
Add property to file provider settings controller displaying remote file storage usage
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra f68965a241
Fetch and store user info for accounts in file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:12 +08:00
Claudio Cambra 7d229b569f
Move GB with decimal number calculation to function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 4e8eab6cd9
Handle edge cases with materialised files enumeration and improve logging
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 22a521d2a2
Hold a strong reference to UsageEnumerationFinishedHandler
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 9105a4584e
Use NSUInteger directly when calculationg usage in FileProviderStorageUseEnumerationObserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 18ed46ef77
Expose local storage usage for account in gigabytes to QML
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 2105596643
Fetch materialised files storage usage per account on creation of FileProviderSettingsController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 8fdc69b3cc
Calculate total storage use from enumerator for materialised files in fileproviderstorageuseenumerationobserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra 435e25d75c
Define ompletion block for fileproviderstorageuseenumerationobserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:11 +08:00
Claudio Cambra b83f0a51a9
Add starter fileproviderstorageuseenumerationobserver
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 97c963dcab
Remove fp domain id from registered domains when removal is confirmed
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 5aa7138643
Do not bother trying to add a fp domain if we know it is active
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra e4aeb5b481
Update file provider domain enablement status upon settings change
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 15525bca62
Separate file provider domain updating into separate slot
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra ee6c081603
Enable file provider domains per the settings in the file provider domain manager
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 1db59fecc1
Remove ConfigFile().macFileProviderModuleEnabled() config flag
Since we are going to distribute the file provider client as separate
from the normal client, this flag doesn't make any practical sense.
The user using the FileProvider desktop client will not want to not
use the file provider module

Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 0c40ceff9c
Init widely-used variables in MacImplementation at declaration, fix nullability warnings
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:10 +08:00
Claudio Cambra 58262a8d94
Ensure account is file provider vfs enablement is correctly set in NSUserDefaults
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra f5b80e9ec1
Run initial check on creation of FileProviderSettingsController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra 5f059a23ce
Add method to enable file provider vfs for all accounts
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra 42b72f9d9a
Use [[nodiscard]] for MacImplementation methods
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra 556bce2672
Move fp settings controller out of ui folder
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra e9451e9281
Set account as property of settings view root object rather than context property
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra a57cb3df9d
Register file provider settings controller as singleton in qml engine
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra ba3baa406b
Expose account user id with host to QML
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:09 +08:00
Claudio Cambra 3c26e25a3c
Notify when enabled file provider vfs accounts change
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra ac608a661d
Use default constructor for FileProviderSettingsController::MacImplementation
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra 9a880f4199
Implement modification of file provider enabled for account
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra c69bb4375c
Make it possible to check whether an account has vfs enabled or not
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra 94ce3da9c5
Treat FileProviderSettingsController::settingsViewWidget as a factory method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra 20f89c6999
Make the fp settings controller static
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra 0e02a64c5e
Lazily load settings view widget
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra d80962b634
Instantiate internal MacImplementation in file provider settings controller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:08 +08:00
Claudio Cambra 27c803a3d8
Add MacImplementation private class to interface with obj-c settings API
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra 7e4d643ade
Add starter FileProviderSettings objective c class
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra 2863415428
Add access to FileProviderSettingsController in FileProviderSettings page
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra f607bfaca1
Make file provider settings controller implementation an Objective-C++ file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra 8070dbd9f6
Implement basic layout for file provider configuration UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra 174d3ec9e0
Remove Virtual files section of settings, move this instead to individual tab in each account page
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra d4fb1e7dad
Move disguising of tab widget into separate method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra c57a5820d0
Make sure qtabwidget in account settings is unnoticeable when file provider module is disabled
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:07 +08:00
Claudio Cambra 5af363ad9c
Wrap normal folder settings in tab widget
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra f158e275fc
Resize settings object to root view
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra 58e6385093
Make settings page use other internal QML components, correct palette
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra 346a07643c
Simplify widget creation of file provider settings UI
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra 4b8338ce91
Expose QML engine in systray
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra dae1643542
Add basic fitting styling for FileProviderSettings component
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra 40389a2197
Use file provider settings controller in settings dialog
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra a6f15ea700
Set up QML file provider settings within the FileProviderSettingsController
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:06 +08:00
Claudio Cambra 8643acfdaa
Add a start fileprovidersettingscontroller
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:05 +08:00
Claudio Cambra e547ae22b7
Add basic, start FileProviderSettings page
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:05 +08:00
Claudio Cambra 35659bbda2
Add mac-specific virtual files settings section
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:45:04 +08:00
Claudio Cambra 8ab52b8683
Merge pull request #6324 from nextcloud/work/fileprovider-xpc
Rewrite communication between client and File Provider extensions using XPC
2024-02-19 22:40:05 +08:00
Claudio Cambra 3334b4e49c Do not reconfigure file provider extension account if we are receiving the same details again
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 71119fe65a Improve socket controller logging
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 9611e47a3b Simplify xpc service acquisition completion handler
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra b22f463ad5 Add explainer to client interface
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 41133e4cd8 Clarify utility of xpc system for file provider
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra a44454daf8 Simplify and clarify utility of socket system for file provider
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra a01e55272a Only start XPC after file provider domains have been configured
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 45b123130f Separate XPC init from file provider constructor
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 423891230e Separate starting of domain manager tasks from constructor
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 58dc42a521 Get services using better non-url based method if available (macOS 13.0+)
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 798f77fa2b Improve domain discovery logging
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 43b7ae55df Use accountStateFromFileProviderDomainIdentifier method in authenticateExtension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 6d4e785ebb Remove unused extension ID NSString
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra be3bd7bb3b Do not shadow sender()
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 784cd129fb Remove all obj-c classes from fileproviderxpc.h
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra fca6b37804 Fix clang-tidy namespace concatenation warning
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 0856138b2e Implement recognition of account state changing in FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra c1fa6621ae Move extension authentication into separate method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 94bfa035c3 Define NSDictionary types in _clientCommServices
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra f728ec1b75 Add method to unauthenticate file provider extension in FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 502d73814e Cast clientCommServices to NSObject with correct protocol
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 23a3a3e554 Extract account id acquisition from clientCommService into separate function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 8a8d3b3ef5 Remove redundant static in anonymous fileproviderxpc utils namespace
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra c9997ce5f9 Extract remote service object acquisistion from connection into separate function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 595c23cf76 Extract file provider connection configuration into separate util function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 5ed456f30c Implement NSFileProviderServicing in FileProviderExtension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 6f5b07c1ae Fix clientcommservices datatype
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 95bebc5214 Clean up FileProviderXPC code, separate everything into single-responsibility methods
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 5ee2cfa749 Moved XPC Utils into separate file
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra a0376a2dab Move acquisition of file provider services to different function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 4a8d50144a Move domain url acquisition to separate function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 8eaf4e6324 Move domain manager acquisition into separate function
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 59b8b8ef21 Upon acquisition of client communication services, send account configuration
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 7716860bc9 Retrieve extension account ids over XPC, store client comm service per account
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 6cb71694fd Log extension id sent over XPC publicly
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra eb774adc95 Check for protocol type in fileproviderxpc start
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 2d1e5ba197 Ensure the listener is resumed once the endpoint is created in the extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra ee82968c69 Cache NSXPCConnections when starting FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 5f6f7f302a Add extensionAccountId property to ClientCommunicationProtocol
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra a6e3f18168 Ensure FileProvider components instantiated after account setup
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra d4d0cf550e Fix retain issues with domains in FileProviderXPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 0b505a9c2c Flatten FileProviderXPC::start()
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 25201116a0 Implement connection begin in XPC
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 9dddaf4f9f Fix logging for fileproviderdomainmanager
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra e8c1bbb953 Add relative symlink to ClientCommunicationProtocol in srd/gui/macOS
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 5cc8b6e7c2 Add starter FileProviderXPC class on client side
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra ac43369bed Properly implement listener should accept connection in ClientCommunicationService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 026f082253 Reimplement ClientCommunicationService in Swift
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra dd39991f1c Implement ClientCommunicationProtocol in ClientCommunicationService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra e763a9d29b Add ClientCommunicationProtocol
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra a2d69fcf7c Keep pointer to FileProviderExtension around in ClientCommunicationService
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Claudio Cambra 22c176af8a Add starter client communication service
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-19 22:39:37 +08:00
Matthieu Gallien 9e75ca85bf
Merge pull request #6451 from nextcloud/bugfix/lockTypeMismatch
add extra logs to investigate lock type issues
2024-02-19 15:38:07 +01:00
Matthieu Gallien c254bb01a3
do not check lock type if server API is missing it
will allow automated unlock if the server API is missing the ability to
handle the lock types sent by the client

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-19 14:53:47 +01:00
Matthieu Gallien 17256d3902
add extra logs to investigate lock type issues
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-19 14:53:47 +01:00
allexzander 334e032d5b
Merge pull request #6459 from nextcloud/bugfix/settingsdialog-version-hidden
Settings dialog. Nextcloud version label should be visible even if auto-updates are turned off in config.
2024-02-19 10:22:14 +01:00
alex-z aab9066654 Settings dialog. Nextcloud version label should be visible even if auto-updates are turned off in config.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-19 09:58:08 +01:00
Nextcloud bot 68a5fc64ec
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-19 02:38:46 +00:00
Nextcloud bot dd0baaa0d0
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-18 02:38:33 +00:00
Nextcloud bot 1129cb9595
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-17 02:47:37 +00:00
Nextcloud bot cfbcebe216
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-16 02:38:40 +00:00
Nextcloud bot 5511d692ed
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-15 02:46:21 +00:00
allexzander e2410ff4c5
Merge pull request #6434 from nextcloud/feature/detect-open-files
Feature/detect open files
2024-02-14 09:40:38 +01:00
alex-z b2aca219fc Do not upload modified PDF file while it is open in Kofax PowerPDF on Windows. Prevents signature verification failure.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-02-14 08:25:21 +01:00
Nextcloud bot 39611b9728
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-14 02:38:37 +00:00
Matthieu Gallien 606b1ef137
Merge pull request #6425 from nextcloud/bugfix/setExpirationDateInternalShareLink
clean up some qml code for share dialog
2024-02-13 14:46:07 +01:00
Matthieu Gallien 4f2988a7d5 fix more issues from qml in share dialog
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-13 14:45:58 +01:00
Matthieu Gallien 997ff6535f fix QML warnings about accessing undefined parent property
we are defining a component, by definition there will be no parent

setting the size if the responsibility of the stack view, not of the
component (or even its definition)

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-13 14:45:58 +01:00
Matthieu Gallien e24da81f49 ensure role hideDownload from ShareModel has a boolean default value
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-13 14:45:58 +01:00
Matthieu Gallien 2a5e273963 fix binding loop with palette assignments
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-13 14:45:58 +01:00
Matthieu Gallien 6797a5736a
Merge pull request #5565 from nextcloud/bugfix/tray-sync-status-fix
Fix undefined sync status reporting in tray icon
2024-02-13 14:06:11 +01:00
Claudio Cambra d52ad2d27b Do an early break of folder state check loop when errors seen
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-13 14:05:31 +01:00
Claudio Cambra 28874ec25c Use bools rather than unnecessary ints in folderman trayoverallstatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>

f
2024-02-13 14:05:31 +01:00
Claudio Cambra 165bf97b48 Use auto and const auto where possible in trayOverallStatus
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-13 14:05:31 +01:00
Claudio Cambra 76ff41f3e0 Properly report Undefined sync status when multiple folders syncing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-13 14:05:31 +01:00
Matthieu Gallien 909147bd6c
Merge pull request #6231 from nextcloud/v2-chunking-doc-sizes
docs(conffile) Update chunk sizes to match v2 chunking PR defaults
2024-02-13 13:12:21 +01:00
Josh Richards 90cd5b6d30
docs(conffile) Update chunk sizes to match v2 chunking PR
Signed-off-by: Josh Richards <josh.t.richards@gmail.com>
2024-02-13 13:11:49 +01:00
Nextcloud bot 37ac0f21ab
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-13 02:38:02 +00:00
Nextcloud bot ec4adb2469
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-12 02:36:05 +00:00
Nextcloud bot 6d9c601acb
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-11 02:39:01 +00:00
Nextcloud bot 784e794ec3
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-09 02:37:02 +00:00
Matthieu Gallien 9d78e97988
Merge pull request #6421 from nextcloud/bugfix/systray-error
Do not create systray notification if there are no errors.
2024-02-08 16:40:10 +01:00
Camila Ayres 533ef0b260 Do not create systray notification if there are no errors.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-02-08 16:40:02 +01:00
Nextcloud bot 182d5bc8e6
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-08 02:37:23 +00:00
Camila Ayres 1b305e4c6a Show systray error message when there is a network error.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-02-07 14:24:30 -03:00
Camila Ayres 9dbf43c6fd Update 'no connection' error text to a more user friendly message.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-02-07 14:24:30 -03:00
Camila Ayres b3b48dc163 Modernize extractErrorMessage and AbstractNetworkJob::errorStringParsingBody.
- Add const auto.
- Remove extra error message information to make it more user friendly.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-02-07 14:24:30 -03:00
Camila Ayres 6d50b87587 Modernize networkReplyErrorString by using const auto.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-02-07 14:24:30 -03:00
Nextcloud bot 252b84cc96
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-07 02:36:31 +00:00
Matthieu Gallien ead399895d
Merge pull request #6368 from nextcloud/bugfix/swift-format
Swift-format FileProviderExt
2024-02-06 11:45:52 +01:00
Claudio Cambra 7ac5f38178 Cllean up function and method calls in File Provider Extension
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-06 11:45:44 +01:00
Claudio Cambra d707ccc5b3 Clean up file provider extension property declarations and init
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-06 11:45:44 +01:00
Claudio Cambra 639c6f9120 Add .swift-format.json
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-06 11:45:44 +01:00
Claudio Cambra 3928573ff4 Run swift-format on swift code
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-02-06 11:45:44 +01:00
Matthieu Gallien f1ed494b31
Merge pull request #6402 from nextcloud/ci/suppressOpensslDeprecatedWarnings
suppress deprecated warnings from openssl
2024-02-06 08:45:42 +01:00
Matthieu Gallien 87522bf24c suppress deprecated warnings from openssl
is needed to avoid failing builds due to warnings

unclear when we will tackle the work of removing the use of deprectaed
APIs

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-06 08:45:33 +01:00
Matthieu Gallien 1133e3112b
Merge pull request #6414 from nextcloud/dependabot/github_actions/codecov/codecov-action-4
Build(deps): Bump codecov/codecov-action from 3 to 4
2024-02-06 08:45:10 +01:00
dependabot[bot] 57b60486c4 Build(deps): Bump codecov/codecov-action from 3 to 4
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 08:45:02 +01:00
Matthieu Gallien a55ff84d62
Merge pull request #6412 from nextcloud/automated/update-workflows/default
chore: update workflows from templates
2024-02-06 08:44:38 +01:00
John Molakvoæ fcc2d4e62a chore: update workflows from templates
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2024-02-06 08:44:30 +01:00
Matthieu Gallien b11648e8a6
Merge pull request #6407 from nextcloud/rakekniven-patch-1
fix(i18n): uppercase ID
2024-02-06 08:43:49 +01:00
rakekniven f6137200fe fix(i18n): Changed spelling of "public key"
Reported at Transifex

Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com>
2024-02-06 08:43:40 +01:00
rakekniven 0799af8ed3 fix(i18n): uppercase ID
Signed-off-by: rakekniven <2069590+rakekniven@users.noreply.github.com>
2024-02-06 08:43:40 +01:00
Matthieu Gallien 76f19ee0d8
Merge pull request #6406 from nextcloud/bugfix/waitLongerForContextMenuOnWindows
wait longer to get the contextual menu entries: may be necessary
2024-02-05 17:47:07 +01:00
Matthieu Gallien 51654bcc90 wait longer to get the contextual menu entries: may be necessary
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-02-02 16:52:30 +01:00
Nextcloud bot b0183cef6b
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-02-01 02:39:36 +00:00
Nextcloud bot 25d13a3372
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-31 02:36:27 +00:00
Matthieu Gallien 6ad34fa942
Merge pull request #6394 from nextcloud/dependabot/github_actions/peter-evans/create-or-update-comment-4.0.0
Build(deps): Bump peter-evans/create-or-update-comment from 3.1.0 to 4.0.0
2024-01-30 09:49:59 +01:00
dependabot[bot] dd899d1978 Build(deps): Bump peter-evans/create-or-update-comment
Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 3.1.0 to 4.0.0.
- [Release notes](https://github.com/peter-evans/create-or-update-comment/releases)
- [Commits](23ff15729e...71345be026)

---
updated-dependencies:
- dependency-name: peter-evans/create-or-update-comment
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-30 09:49:49 +01:00
Matthieu Gallien a6e683f7ed
Merge pull request #6393 from nextcloud/bugfix/fix-random-crash-lscoljob
Bugfix. Random crash in LsColJob after recent changes.
2024-01-30 09:48:25 +01:00
alex-z 5cdeba8061 Bugfix. Random crash in LsColJob after recent changes.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-01-30 09:34:29 +01:00
Matthieu Gallien 068979a74d
Merge pull request #6363 from nextcloud/ci/moveSomeChecksToGithubActions
partial move of some checks done with drone to github actions
2024-01-30 09:27:10 +01:00
Matthieu Gallien d8a52266b6 partial move of some checks done with drone to github actions
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-30 08:49:57 +01:00
Nextcloud bot 1fef6e7074
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-30 02:35:11 +00:00
allexzander 89d2ec4f51
Merge pull request #6336 from nextcloud/feature/improve-speed-of-discovery
Allow event processing between each XML parser run, to improve GUI performance.
2024-01-29 20:29:46 +01:00
alex-z e9a0dbd16c Allow event processing between each XML parser run, to improve GUI performance.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-01-29 18:32:01 +01:00
Matthieu Gallien 654b5fe38d
starts the development of version 3.13
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-29 17:44:15 +01:00
Matthieu Gallien b977472aea
Merge pull request #6353 from nextcloud/bugfix/window-pos-compute
Simplify window positioning compute, more flexibly handle different available geometries
2024-01-29 17:27:55 +01:00
Claudio Cambra b1a50f42be Do not recreate geometry more than is necessary
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-01-29 17:27:45 +01:00
Claudio Cambra 3dc5a6cc9e Remove now unused method for calculating taskbar geometry
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-01-29 17:27:45 +01:00
Claudio Cambra 6c71c38ca8 Use available gemoetry measurement rather than whole screen geometry measurement unit to define non-taskbar invading window positions, removing need for calculating taskbar geometry
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-01-29 17:27:45 +01:00
Claudio Cambra bcc1cea9b4 Add currentAvailableScreenRect convenience method
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2024-01-29 17:27:45 +01:00
Matthieu Gallien 120d67cc3e
Merge pull request #6370 from nextcloud/ci/partialBuildFixForTokenAuthOnlyBuild
partial build fix when TOKEN_AUTH_ONLY is enabled at configure time
2024-01-29 16:56:28 +01:00
Matthieu Gallien b8ee9db377 partial build fix when TOKEN_AUTH_ONLY is enabled at configure time
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-29 16:56:20 +01:00
Matthieu Gallien e02223108a
Merge pull request #6377 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.8.0
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.6 to 2.8.0
2024-01-29 16:55:50 +01:00
dependabot[bot] e84616cb34
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.6 to 2.8.0
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.7.6 to 2.8.0.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.7.6...v2.8.0)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 15:46:44 +00:00
Matthieu Gallien 1ad86aad83
Merge pull request #6376 from nextcloud/dependabot/github_actions/actions/cache-4
Build(deps): Bump actions/cache from 3 to 4
2024-01-29 16:45:58 +01:00
dependabot[bot] 0ece126cad Build(deps): Bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 16:45:50 +01:00
Matthieu Gallien 604ad3871f
Merge pull request #6387 from nextcloud/lunar_noble
Remove Ubuntu Lunar, add Noble
2024-01-29 16:44:35 +01:00
István Váradi 0433bd7f25 Remove Ubuntu Lunar, add Noble
Signed-off-by: István Váradi <ivaradi@varadiistvan.hu>
2024-01-29 16:44:26 +01:00
Matthieu Gallien 8cf4dee510
Merge pull request #6350 from nextcloud/feature/e2ee-v2-foldersharing
Feature/e2ee v2 foldersharing
2024-01-29 16:36:13 +01:00
alex-z af612525c4 End-to-End Encryption V2. Implemented sharing between users. Automatic migration from 1.0 to 2.0(only for flat folders). Improved secure filedrop.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-01-29 16:13:25 +01:00
Nextcloud bot 615d592c3e
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-27 03:06:41 +00:00
Nextcloud bot a113fa7828
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-26 02:38:39 +00:00
Matthieu Gallien 1e7654be14
Merge pull request #6381 from nextcloud/bugfix/issue/6380
Fix: remove failure to import legacy account notification.
2024-01-25 12:25:38 +01:00
Camila Ayres 0ae7695d5c
Fix: remove failure to import legacy account notification.
- It was being displayed even on a fresh install.
- We are already displaying a notification when there are accounts to be
imported and we ask the user on what to do.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-25 12:23:37 +01:00
Matthieu Gallien 81847f9020
Merge pull request #6378 from nextcloud/feature/enableXCodeHardenedRuntime
globally enable use of the xcode hardened runtime
2024-01-24 18:26:57 +01:00
Matthieu Gallien ad14e10fbb globally enable use of the xcode hardened runtime
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-24 12:12:43 +01:00
Nextcloud bot de878649c5
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-24 02:36:53 +00:00
Nextcloud bot a60b339e22
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-23 02:45:45 +00:00
Matthieu Gallien 9f9c9fc0f3
Merge pull request #6342 from nextcloud/bugfix/autostart
Bugfix/autostart
2024-01-22 11:52:41 +01:00
Camila Ayres 2089fb168b
Remove if TOKEN_AUTH_ONLY because the build will fail without it in all cases.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:35:58 +01:00
Camila Ayres 30883785e4
Save the value of 'Launch on system startup' in the config files.
Make sure to migrate older configs to have the value set to true.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:35:55 +01:00
Camila Ayres d677664f9d
Clean up logic when setting up auto start for user.
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:28:00 +01:00
Erik Verbruggen 07063c9c9d
macOS: Fix restart-after-quit
Instead of using a dictionary for the value of KeepAlive, setting it to
@NO will keep launchd from relaunching the client.

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:28:00 +01:00
Erik Verbruggen 31559faa35
Creaate `LaunchAgents` directory if it doesn't exist
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:28:00 +01:00
Erik Verbruggen 74c7487314
Mac: do not restart after crash
When the crash is due to a library load failure while starting, having
this turned on would result in an endless loop of crash-restarts.

Fixes: #9800

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Erik Verbruggen 1f046ccd5e
macOS: Do case-insensitive compare in auto-launch code
Case-sensitive compares will give problems when a launchd plist file
exists, but the executable gets upated, and the name only has a
different case (e.g.: owncloud -> ownCloud).

Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Erik Verbruggen 76cf707934
macOS: Modernise Utility::setupFavLink
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Erik Verbruggen fb13eb7203
macOS: Simplify Utility::hasDarkSystray()
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Erik Verbruggen a616fbf189
Mac: Move launch-on-login to use launchd
Previously, deprecated API was used to tell macOS to launch the GUI when
the user logs in. This has now been moved to launchd, which uses the
contents of the file
~/Library/LaunchAgents/com.owncloud.desktopclient.plist to determine
what to do. This comes with the added benefit that wel now tell launchd
to relaunch the client when it crashes.

Fixes: #9037
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Erik Verbruggen 8da94b6509
Turn utility_* into stand-alone files
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Hannah von Reth 3762e410f8
Log if setting up launch on start fails on mac
Signed-off-by: Camila Ayres <hello@camilasan.com>
2024-01-22 11:27:59 +01:00
Camila c4095d698a
Autostart does not depend on how many accounts are configured.
Set autostart default to true.

Signed-off-by: Camila <hello@camila.codes>
2024-01-22 11:27:58 +01:00
allexzander 3bcc17b942
Merge pull request #6354 from nextcloud/bugfix/e2ee-errors-spell-correction
Spell-correction for e2e_errors
2024-01-22 10:59:45 +01:00
Nextcloud bot 8a02625ea4
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-22 02:37:00 +00:00
Nextcloud bot 7269efe235
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-21 02:56:07 +00:00
Nextcloud bot e94db5d6ed
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-20 02:44:37 +00:00
Matthieu Gallien 15a771e9bb
Merge pull request #6275 from tintou/master
Avoid duplicate declarations with latest libcloudproviders
2024-01-19 13:01:38 +01:00
Corentin Noël 2238dab3f9 Avoid duplicate declarations with latest libcloudproviders
Only install the .ini if the target version doen't support the .desktop file
declaration.

Signed-off-by: Corentin Noël <corentin.noel@collabora.com>
2024-01-19 12:31:03 +01:00
Matthieu Gallien 97ac36e614
Merge pull request #6261 from SUNET/master
GUI/LIBSYNC: force login flow V2 with config setting
2024-01-19 11:37:33 +01:00
Micke Nordin 6ca4ace09f OwncloudWizard: Move implementation to cpp-file
Signed-off-by: Micke Nordin <kano@sunet.se>
2024-01-19 11:05:18 +01:00
Micke Nordin 42012a0efb Apply suggestions from code review
Formatting fixes

Co-authored-by: Claudio Cambra <developer@claudiocambra.com>
Signed-off-by: Micke Nordin <mickenordin@users.noreply.github.com>
2024-01-19 11:05:07 +01:00
Micke Nordin 772d44c66d GUI/LIBSYNC: force login flow V2 with config setting
This patch allows a user to set the config variable:

forceLoginV2=true

This will make the client use the browser login flow instead of the
webview. This is useful for making the user experience equal on
Windows, Linux and Mac. Currently only Macs use the v2 flow and it
was only possible to get this behaviour in the Windows and Linux
clients at build time using a CMAKE flag.

The default behaviour is kept the same, and nothing changes for the
user unless the flag is manually set in the config file. A setter is
included in this patch, although it is not yet used in the GUI.

Signed-off-by: Micke Nordin <kano@sunet.se>
2024-01-19 11:03:36 +01:00
Matthieu Gallien c4f2537418
Merge pull request #6286 from nextcloud/add/terabyte
Display terabyte again
2024-01-19 10:17:05 +01:00
Camila 8f628f808c Modernize Utility::octetsToString function.
- Add {} to single line if.
- Use const and auto.
- Use constexpr.
- Rename variables for readability.

Signed-off-by: Camila <hello@camila.codes>
2024-01-19 10:15:38 +01:00
Camila a06b88c1b4 Utility: Fix the storage string to display terabytes.
Signed-off-by: Camila <hello@camila.codes>
2024-01-19 10:15:38 +01:00
Matthieu Gallien 4b10038dcc
Merge pull request #6352 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.7.6
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.5 to 2.7.6
2024-01-19 10:14:59 +01:00
dependabot[bot] cdac0cf90f Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.5 to 2.7.6
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.7.5 to 2.7.6.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.7.5...v2.7.6)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 10:14:51 +01:00
Matthieu Gallien 3523875159
Merge pull request #6360 from nextcloud/bugfix/nextcloudkit
full git path
2024-01-19 10:14:01 +01:00
Tobias Kaminsky 71c5327c98 full git path
Signed-off-by: Tobias Kaminsky <tobias@kaminsky.me>
2024-01-19 10:13:53 +01:00
Nextcloud bot 7da66f0aee
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-19 02:37:02 +00:00
allexzander 8bb36566c7
Merge pull request #6347 from nextcloud/feature/filelocking-keep-user-lock
Feature/filelocking keep user lock
2024-01-18 20:30:42 +01:00
alex-z 2eaecb6400 File locking. Manual lock should never be modified without user intervention. Set token lock instead of user lock when locking office files automatically.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-01-18 10:21:09 +01:00
alex-z 6791cc6365 Spell-correction for e2e_errors
Signed-off-by: alex-z <blackslayer4@gmail.com>
2024-01-17 15:27:55 +01:00
Nextcloud bot 049d3e9df3
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-17 02:37:15 +00:00
Nextcloud bot 416bf8a109
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-14 02:37:40 +00:00
Nextcloud bot 419b015279
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-13 02:41:22 +00:00
Nextcloud bot df5094d299
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-12 02:34:46 +00:00
Nextcloud bot f797d22f04
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-10 02:38:46 +00:00
Nextcloud bot d45ad02430
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-07 02:41:07 +00:00
Nextcloud bot c7dd7dc841
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-06 02:42:15 +00:00
Matthieu Gallien 8e37f434f7
Merge pull request #6314 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.7.5
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.2 to 2.7.5
2024-01-05 16:47:19 +01:00
dependabot[bot] 761bdb09ee Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.2 to 2.7.5
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.7.2 to 2.7.5.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.7.2...v2.7.5)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-05 16:46:55 +01:00
Matthieu Gallien 008afa8061
Merge pull request #6316 from nextcloud/automated/update-workflows/default
chore: update workflows from templates
2024-01-05 16:46:21 +01:00
John Molakvoæ ae8eb229ec chore: update workflows from templates
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
2024-01-05 16:46:12 +01:00
Matthieu Gallien 053202e881
Merge pull request #6332 from nextcloud/bugfix/missingTranslations
Bugfix/missing translations in AppImage
2024-01-05 15:58:28 +01:00
Matthieu Gallien 7bff44daa1
get the appimage path to translations instead of looking at the host
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-05 12:16:15 +01:00
Matthieu Gallien 452cf435fc
add more info before we try to load translations and/or fail
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2024-01-05 12:16:15 +01:00
Nextcloud bot 4714e01477
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-05 02:41:26 +00:00
Nextcloud bot 983f960be0
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2024-01-04 02:39:05 +00:00
Nextcloud bot 4f418e807d
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-27 02:35:58 +00:00
Nextcloud bot ee982a10b9
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-24 02:43:52 +00:00
Nextcloud bot a51719777e
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-18 02:34:05 +00:00
Nextcloud bot 1e4d8891fb
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-17 02:33:41 +00:00
Nextcloud bot 79afa4d856
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-15 02:33:23 +00:00
Matthieu Gallien 9b269e9f5e
Merge pull request #6297 from nextcloud/bugfix/reallyDoNotModifyUploadedVirtualFilesDuringSync
ensure we do not modify a file that has been just uploaded
2023-12-14 16:12:11 +01:00
Matthieu Gallien 0b869247ef
immediately flush logs in automated test of sync engine
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-14 14:39:47 +01:00
Matthieu Gallien 911120caa0
ensure we do not modify a file that has been just uploaded
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-14 14:37:33 +01:00
Nextcloud bot 9f17381652
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-14 02:32:59 +00:00
allexzander b4fcc94c36
Merge pull request #6285 from nextcloud/bugfix/vfs-download-typechange
Bugfix. Do not treat item type change as metadata update.
2023-12-13 12:53:28 +01:00
alex-z 4672acb5a6 Unit tests for the type change.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-13 11:43:25 +01:00
alex-z ed2ad42502 Bugfix. Do not treat item type change as metadata update.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-13 11:43:24 +01:00
Nextcloud bot 7f37cf866d
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-13 02:35:26 +00:00
allexzander 64ce74ebae
Merge pull request #6291 from nextcloud/bugfix/fix-tray-folderbutton-size
Bugfix. Fix incorrect size of the folderbutton size and scaling.
2023-12-12 22:32:26 +01:00
alex-z 17fbfe4e06 Bugfix. Fix incorrect size of the folderbutton size and scaling.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-12 16:33:58 +01:00
Matthieu Gallien 2a58a0a034
Merge pull request #6289 from nextcloud/dependabot/github_actions/actions/stale-9
Build(deps): Bump actions/stale from 8 to 9
2023-12-12 10:53:36 +01:00
dependabot[bot] 3ef3c9a751 Build(deps): Bump actions/stale from 8 to 9
Bumps [actions/stale](https://github.com/actions/stale) from 8 to 9.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-12 10:53:04 +01:00
Matthieu Gallien e9c144785b
Merge pull request #6288 from nextcloud/dependabot/github_actions/actions/setup-python-5
Build(deps): Bump actions/setup-python from 4 to 5
2023-12-12 10:52:13 +01:00
dependabot[bot] 67edae2621 Build(deps): Bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-12 10:51:40 +01:00
Matthieu Gallien 6415ef3687
Merge pull request #6287 from nextcloud/dependabot/github_actions/cpp-linter/cpp-linter-action-2.7.2
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.1 to 2.7.2
2023-12-12 10:51:12 +01:00
dependabot[bot] 416002cdb0
Build(deps): Bump cpp-linter/cpp-linter-action from 2.7.1 to 2.7.2
Bumps [cpp-linter/cpp-linter-action](https://github.com/cpp-linter/cpp-linter-action) from 2.7.1 to 2.7.2.
- [Release notes](https://github.com/cpp-linter/cpp-linter-action/releases)
- [Commits](https://github.com/cpp-linter/cpp-linter-action/compare/v2.7.1...v2.7.2)

---
updated-dependencies:
- dependency-name: cpp-linter/cpp-linter-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 23:27:55 +00:00
alex-z 50d168759f Added error reporting for E2EE issues.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z c30e526bc8 Report error Virus Detected. Updated tests.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 66b0383fee Allow sending E2EE and Virus_Detected statuses. Also updated tests.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 4b0e1b57eb Prevent issues when changing the enum.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 8e38739d94 Generate client status report records when errors happen.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 5b54b13a97 Iteration. Properly process server reply.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 4b7ccf8647 Iteration. Use Capabilities to control Client Status Reporting availibility.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z 5e78b83ca7 Send status report. Improved logic and database columns.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
alex-z ff8db2674a Client status reporting feature. First iteration.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-11 18:03:57 +01:00
Matthieu Gallien d3562e6205
Merge pull request #6207 from nextcloud/bugfix/macos-legacy
Bug fixes for mac OS
2023-12-11 15:32:47 +01:00
Camila 02e4f05358
Use a new FindSQLite3 cmake module.
Add find sqlite to the root CMakeLists.txt.

Signed-off-by: Camila <hello@camila.codes>
2023-12-11 11:50:33 +01:00
Claudio Cambra c9b4419070
Only set linker flags needed for macOS on Apple
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2023-12-11 11:50:33 +01:00
Camila 3c51c9e1f8
Fix crash on mac OS < 13.
It's caused by the new optimized Xcode 15 linker when linking static libraries.
source: https://crystalidea.com/blog/qt-apps-crash-when-using-xcode-15

Signed-off-by: Camila <hello@camila.codes>
2023-12-11 11:50:33 +01:00
Nextcloud bot 74a9717e3f
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-10 02:32:28 +00:00
Nextcloud bot baa1e55db4
Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2023-12-09 02:38:17 +00:00
alex-z 9298305093 Feature. Do not allow move of external storage mounted folders.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2023-12-07 14:10:35 +01:00
Claudio Cambra 6b7b655649
Merge pull request #6258 from nextcloud/bugfix/build-latest-nckit-test
Fix build with latest NextcloudKit, caused by search for XCTest and friends
2023-12-06 20:37:37 +08:00
Claudio Cambra edaf49cc9a Fix build with latest NextcloudKit, caused by search for XCTest and friends
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
2023-12-06 11:51:55 +01:00
Matthieu Gallien 2c5281cea9
Merge pull request #6212 from nextcloud/bugfix/doNotModifyUploadedVirtualFiles
avoid modifying a placeholder (virtual files) when not needed
2023-12-06 10:53:38 +01:00
Matthieu Gallien bf78f008bd
extend log to get information about mtime modifications by the client
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-06 10:42:33 +01:00
Matthieu Gallien b77fc9d4ff
avoid modifying a placeholder (virtual files) when not needed
acoid modifying some metadata of the placeholder when this placeholder
has just been uploaded to the server (will avoid truncating the
timestamps)

Close #6190

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-06 10:13:49 +01:00
Matthieu Gallien 369296d6ed
fix include order from most specific to less specific
shoudl ensure that included headers are self sufficient

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-06 10:08:16 +01:00
Matthieu Gallien 14c03bf76f
prepare for 3.12 release
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2023-12-05 09:16:37 +01:00
459 changed files with 43256 additions and 13128 deletions

View File

@ -1,19 +1,19 @@
---
kind: pipeline
name: qt-5.15
name: drone desktop client
steps:
- name: cmake
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
commands:
- cd /drone/build
- cmake -G Ninja -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DADD_E2E_TESTS=ON -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 ../src
- cmake /drone/src -G Ninja -DCMAKE_PREFIX_PATH=/opt/qt6.6.3 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Debug -DQT_MAJOR_VERSION=6 -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 -DADD_E2E_TESTS=ON
- name: compile
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
@ -22,25 +22,27 @@ steps:
- ninja
- name: test
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
commands:
- cd /drone/build
- ../src/admin/test/wait_for_server.sh "server"
- useradd -m -s /bin/bash test
- chown -R test:test .
- su -c 'ASAN_OPTIONS=detect_odr_violation=0,detect_leaks=0 xvfb-run ctest --output-on-failure' test
- su -c 'xvfb-run ctest --output-on-failure' test
services:
- name: server
image: ghcr.io/nextcloud/continuous-integration-server:latest # also change in updateScreenshots.sh
image: ghcr.io/nextcloud/continuous-integration-shallow-server:latest # also change in updateScreenshots.sh
environment:
EVAL: true
SERVER_VERSION: 'stable24'
SERVER_VERSION: 'stable28'
commands:
- BRANCH="$SERVER_VERSION" /usr/local/bin/initnc.sh
- echo 127.0.0.1 server >> /etc/hosts
- apt-get update && apt-get install -y composer
- su www-data -c "OC_PASS=user1 php /var/www/html/occ user:add --password-from-env --display-name='User One' user1"
- su www-data -c "OC_PASS=user2 php /var/www/html/occ user:add --password-from-env --display-name='User Two' user2"
- su www-data -c "OC_PASS=user3 php /var/www/html/occ user:add --password-from-env --display-name='User Three' user3"
@ -48,12 +50,15 @@ services:
- su www-data -c "php /var/www/html/occ group:add users"
- su www-data -c "php /var/www/html/occ group:adduser users user1"
- su www-data -c "php /var/www/html/occ group:adduser users user2"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/activity.git /var/www/html/apps/activity/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/activity.git /var/www/html/apps/activity/"
- su www-data -c "php /var/www/html/occ app:enable activity"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/text.git /var/www/html/apps/text/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/text.git /var/www/html/apps/text/"
- su www-data -c "php /var/www/html/occ app:enable text"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/end_to_end_encryption.git /var/www/html/apps/end_to_end_encryption/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/end_to_end_encryption.git /var/www/html/apps/end_to_end_encryption/"
- su www-data -c "php /var/www/html/occ app:enable end_to_end_encryption"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/photos.git /var/www/html/apps/photos/"
- su www-data -c "cd /var/www/html/apps/photos; composer install"
- su www-data -c "php /var/www/html/occ app:enable -f photos"
- /usr/local/bin/run.sh
volumes:
@ -74,15 +79,15 @@ name: qt-5.15-clang
steps:
- name: cmake
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
commands:
- cd /drone/build
- cmake -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DADD_E2E_TESTS=ON -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 ../src
- cmake /drone/src -G Ninja -DCMAKE_PREFIX_PATH=/opt/qt6.6.3 -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_BUILD_TYPE=Debug -DQT_MAJOR_VERSION=6 -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 -DADD_E2E_TESTS=ON
- name: compile
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
@ -90,25 +95,27 @@ steps:
- cd /drone/build
- ninja
- name: test
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
image: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
volumes:
- name: build
path: /drone/build
commands:
- cd /drone/build
- ../src/admin/test/wait_for_server.sh "server"
- useradd -m -s /bin/bash test
- chown -R test:test .
- su -c 'ASAN_OPTIONS=detect_odr_violation=0,detect_leaks=0 xvfb-run ctest --output-on-failure' test
- su -c 'xvfb-run ctest --output-on-failure' test
services:
- name: server
image: ghcr.io/nextcloud/continuous-integration-server:latest # also change in updateScreenshots.sh
image: ghcr.io/nextcloud/continuous-integration-shallow-server:latest # also change in updateScreenshots.sh
environment:
EVAL: true
SERVER_VERSION: 'stable24'
SERVER_VERSION: 'stable28'
commands:
- BRANCH="$SERVER_VERSION" /usr/local/bin/initnc.sh
- echo 127.0.0.1 server >> /etc/hosts
- apt-get update && apt-get install -y composer
- su www-data -c "OC_PASS=user1 php /var/www/html/occ user:add --password-from-env --display-name='User One' user1"
- su www-data -c "OC_PASS=user2 php /var/www/html/occ user:add --password-from-env --display-name='User Two' user2"
- su www-data -c "OC_PASS=user3 php /var/www/html/occ user:add --password-from-env --display-name='User Three' user3"
@ -116,12 +123,15 @@ services:
- su www-data -c "php /var/www/html/occ group:add users"
- su www-data -c "php /var/www/html/occ group:adduser users user1"
- su www-data -c "php /var/www/html/occ group:adduser users user2"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/activity.git /var/www/html/apps/activity/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/activity.git /var/www/html/apps/activity/"
- su www-data -c "php /var/www/html/occ app:enable activity"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/text.git /var/www/html/apps/text/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/text.git /var/www/html/apps/text/"
- su www-data -c "php /var/www/html/occ app:enable text"
- su www-data -c "git clone --depth=1 -b $SERVER_VERSION https://github.com/nextcloud/end_to_end_encryption.git /var/www/html/apps/end_to_end_encryption/"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/end_to_end_encryption.git /var/www/html/apps/end_to_end_encryption/"
- su www-data -c "php /var/www/html/occ app:enable end_to_end_encryption"
- su www-data -c "git clone -b $SERVER_VERSION https://github.com/nextcloud/photos.git /var/www/html/apps/photos/"
- su www-data -c "cd /var/www/html/apps/photos; composer install"
- su www-data -c "php /var/www/html/occ app:enable -f photos"
- /usr/local/bin/run.sh
volumes:
@ -142,14 +152,14 @@ name: AppImage
steps:
- name: build
image: ghcr.io/nextcloud/continuous-integration-client-appimage:client-appimage-9
image: ghcr.io/nextcloud/continuous-integration-client-appimage-qt6:client-appimage-6.6.3-2
environment:
CI_UPLOAD_GIT_TOKEN:
from_secret: CI_UPLOAD_GIT_TOKEN
CI_UPLOAD_GIT_USERNAME:
from_secret: CI_UPLOAD_GIT_USERNAME
commands:
- BUILDNR=$DRONE_BUILD_NUMBER VERSION_SUFFIX=$DRONE_PULL_REQUEST BUILD_UPDATER=ON DESKTOP_CLIENT_ROOT=$DRONE_WORKSPACE /bin/bash -c "./admin/linux/build-appimage.sh"
- BUILDNR=$DRONE_BUILD_NUMBER VERSION_SUFFIX=$DRONE_PULL_REQUEST BUILD_UPDATER=ON DESKTOP_CLIENT_ROOT=$DRONE_WORKSPACE EXECUTABLE_NAME=nextcloud QT_BASE_DIR=/opt/qt6.6.3 OPENSSL_ROOT_DIR=/usr/local/lib64 /bin/bash -c "./admin/linux/build-appimage.sh"
- BUILDNR=$DRONE_BUILD_NUMBER VERSION_SUFFIX=$DRONE_PULL_REQUEST DESKTOP_CLIENT_ROOT=$DRONE_WORKSPACE /bin/bash -c "./admin/linux/upload-appimage.sh" || echo "Upload failed, however this is an optional step."
trigger:
branch:
@ -196,6 +206,6 @@ trigger:
- push
---
kind: signature
hmac: d72110d7f9cba086ca21f9f4f4032ae87f3d9555ab4c5f55d3aeb3df99cab540
hmac: fbdc01c6461fcc32d9ebff4be97340cbb6da5566643b60289504ed86b2a67583
...

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: cpp-linter/cpp-linter-action@v2.7.1
- uses: cpp-linter/cpp-linter-action@v2.11.0
id: linter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -23,7 +23,7 @@ jobs:
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@ -42,7 +42,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}

View File

@ -7,7 +7,7 @@ name: Block fixup and squash commits
on:
pull_request:
types: [opened, synchronize, reopened]
types: [opened, ready_for_review, reopened, synchronize]
permissions:
contents: read
@ -24,10 +24,10 @@ jobs:
pull-requests: write
name: Block fixup and squash commits
runs-on: ubuntu-latest
runs-on: ubuntu-latest-low
steps:
- name: Run check
uses: skjnldsv/block-fixup-merge-action@42d26e1b536ce61e5cf467d65fb76caf4aa85acf # v1
uses: skjnldsv/block-fixup-merge-action@c138ea99e45e186567b64cf065ce90f7158c236a # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

17
.github/workflows/linux-appimage.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Linux Appimage Package
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Linux Appimage Package
runs-on: ubuntu-latest
container: ghcr.io/nextcloud/continuous-integration-client-appimage-qt6:client-appimage-6.6.3-2
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Configure, compile and package
run: |
BUILDNR=${GITHUB_RUN_ID} VERSION_SUFFIX=${GITHUB_HEAD_REF} BUILD_UPDATER=ON DESKTOP_CLIENT_ROOT=`pwd` EXECUTABLE_NAME=nextcloud QT_BASE_DIR=/opt/qt6.6.3 OPENSSL_ROOT_DIR=/usr/local/lib64 /bin/bash -c "./admin/linux/build-appimage.sh"
BUILDNR=${GITHUB_RUN_ID} VERSION_SUFFIX=${GITHUB_HEAD_REF} DESKTOP_CLIENT_ROOT=`pwd` /bin/bash -c "./admin/linux/upload-appimage.sh" || echo "Upload failed, however this is an optional step."

View File

@ -0,0 +1,25 @@
name: Linux Clang compilation and tests
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Linux Clang compilation and tests
runs-on: ubuntu-latest
container: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Configure and compile
run: |
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_PREFIX_PATH=/opt/qt6.6.3 -DCMAKE_C_COMPILER=clang-14 -DCMAKE_CXX_COMPILER=clang++-14 -DCMAKE_BUILD_TYPE=Debug -DQT_MAJOR_VERSION=6 -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64
ninja
- name: Run tests
run: |
cd build
useradd -m -s /bin/bash test
chown -R test:test .
su -c 'xvfb-run ctest --output-on-failure' test

View File

@ -0,0 +1,25 @@
name: Linux GCC compilation and tests
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Linux GCC compilation and tests
runs-on: ubuntu-latest
container: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Configure and compile
run: |
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_PREFIX_PATH=/opt/qt6.6.3 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Debug -DQT_MAJOR_VERSION=6 -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64
ninja
- name: Run tests
run: |
cd build
useradd -m -s /bin/bash test
chown -R test:test .
su -c 'xvfb-run ctest --output-on-failure' test

View File

@ -5,9 +5,9 @@ on:
jobs:
build:
name: macOS Build and Test
runs-on: macos-latest
runs-on: macos-14
env:
CRAFT_TARGET: macos-64-clang
CRAFT_TARGET: macos-clang-arm64
CRAFT_MASTER_LOCATION: ${{ github.workspace }}/CraftMaster
CRAFT_MASTER_CONFIG: ${{ github.workspace }}/craftmaster.ini
steps:
@ -15,16 +15,22 @@ jobs:
with:
fetch-depth: 1
- name: List Xcode installations
run: sudo ls -1 /Applications | grep "Xcode"
- name: Select Xcode 15.3
run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer
- name: Restore cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/cache
key: macos-latest-${{ env.CRAFT_TARGET }}
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.9
python-version: 3.12
- name: Install Homebrew dependencies
run: |

View File

@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
operations-per-run: 1500
days-before-stale: 28

View File

@ -5,8 +5,8 @@ on:
jobs:
build:
name: SonarCloud analysis
runs-on: ubuntu-22.04
container: ghcr.io/nextcloud/continuous-integration-client:client-5.15-14
runs-on: ubuntu-latest
container: ghcr.io/nextcloud/continuous-integration-client-qt6:client-6.6.3-2
env:
SONAR_SERVER_URL: "https://sonarcloud.io"
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
@ -16,7 +16,7 @@ jobs:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Restore cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: /cache
key: ${{ runner.os }}
@ -25,7 +25,7 @@ jobs:
run: |
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DBUILD_COVERAGE=ON -DOPENSSL_ROOT_DIR=/usr/local/lib64
cmake .. -G Ninja -DCMAKE_PREFIX_PATH=/opt/qt6.6.3 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DQT_MAJOR_VERSION=6 -DCMAKE_CXX_FLAGS=-Werror -DOPENSSL_ROOT_DIR=/usr/local/lib64 -DBUILD_COVERAGE=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} ninja
- name: Run tests
run: |

View File

@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
operations-per-run: 1500
days-before-stale: 28

View File

@ -5,9 +5,9 @@ on:
jobs:
build:
name: Windows Build and Test
runs-on: windows-2019
runs-on: windows-2022
env:
CRAFT_TARGET: windows-msvc2019_64-cl
CRAFT_TARGET: windows-msvc2022_64-cl
COBERTURA_COVERAGE_FILE: ${{ github.workspace }}\cobertura_coverage\coverage.xml
CRAFT_MASTER_LOCATION: ${{ github.workspace }}\CraftMaster
CRAFT_MASTER_CONFIG: ${{ github.workspace }}\craftmaster.ini
@ -16,9 +16,9 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
python-version: '3.12'
- name: Install Craft Master with Nextcloud Client Deps
shell: pwsh
run: |
@ -35,7 +35,7 @@ jobs:
- name: Cache Install OpenCppCoverage
id: cache-install-opencppcoverage
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: C:\Program Files\OpenCppCoverage
key: ${{ runner.os }}-cache-install-opencppcoverage
@ -46,6 +46,19 @@ jobs:
run: |
choco install opencppcoverage
- name: Cache Install inkscape
id: cache-install-inkscape
uses: actions/cache@v4
with:
path: C:\Program Files\inkscape
key: ${{ runner.os }}-cache-install-inkscape
- name: Install inkscape
if: steps.cache-install-inkscape.outputs.cache-hit != 'true'
shell: pwsh
run: |
choco install inkscape
- name: Setup PATH
shell: pwsh
run: |
@ -62,24 +75,17 @@ jobs:
craft --src-dir ${{ github.workspace }} nextcloud-client
- name: Run tests
- name: Run tests with coverage
shell: pwsh
run: |
function runTestsAndCreateCoverage() {
function runTests() {
$buildFolder = "${{ github.workspace }}\${{ env.CRAFT_TARGET }}\build\nextcloud-client\work\build"
cd $buildFolder
$binFolder = "$buildFolder\bin"
& OpenCppCoverage.exe --optimized_build --quiet --sources ${{ github.workspace }} --modules $binFolder\*.dll* --export_type cobertura:${{ env.COBERTURA_COVERAGE_FILE }} --cover_children -- ctest --output-on-failure --timeout 300
& ctest --output-on-failure --timeout 300
}
runTestsAndCreateCoverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ${{ github.workspace }}\cobertura_coverage
fail_ci_if_error: true
runTests

69
.swift-format.json Normal file
View File

@ -0,0 +1,69 @@
{
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"indentation" : {
"spaces" : 4
},
"indentConditionalCompilationBlocks" : true,
"indentSwitchCaseLabels" : false,
"lineBreakAroundMultilineExpressionChainComponents" : false,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : false,
"lineBreakBeforeEachGenericRequirement" : false,
"lineLength" : 100,
"maximumBlankLines" : 1,
"multiElementCollectionTrailingCommas" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether" : false,
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : false,
"AlwaysUseLowerCamelCase" : true,
"AmbiguousTrailingClosureOverload" : true,
"BeginDocumentationCommentWithOneLineSummary" : false,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : false,
"NeverUseImplicitlyUnwrappedOptionals" : false,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : false,
"NoParensAroundConditions" : true,
"NoPlaygroundLiterals" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : false,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"TypeNamesShouldBeCapitalized" : true,
"UseEarlyExits" : false,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : false,
"ValidateDocumentationComments" : false
},
"spacesAroundRangeFormationOperators" : false,
"tabWidth" : 8,
"version" : 1
}

View File

@ -1,20 +1,31 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 17)
cmake_policy(SET CMP0071 NEW) # Enable use of QtQuick compiler/generated code
project(client)
if(APPLE)
set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum OSX deployment version")
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED 17)
include(FeatureSummary)
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
if (CLANG_TIDY_EXE)
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXE} -checks=-*,modernize-use-auto,modernize-use-using,modernize-use-nodiscard,modernize-use-nullptr,modernize-use-override,cppcoreguidelines-pro-type-static-cast-downcast,modernize-use-default-member-init,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-init-variables)
endif()
project(client)
include(FeatureSummary)
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES)
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
include(${CMAKE_SOURCE_DIR}/NEXTCLOUD.cmake)
set(QT_VERSION_MAJOR "6")
set(REQUIRED_QT_VERSION "6.0.0")
# CfAPI Shell Extensions
set( CFAPI_SHELL_EXTENSIONS_LIB_NAME CfApiShellExtensions )
@ -100,7 +111,8 @@ include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
add_definitions(
-DQT_DISABLE_DEPRECATED_BEFORE=0x000000
-DQT_DISABLE_DEPRECATED_BEFORE=0x051200
-DQT_DEPRECATED_WARNINGS
-DQT_USE_QSTRINGBUILDER
-DQT_MESSAGELOGCONTEXT #enable function name and line number in debug output
)
@ -130,7 +142,7 @@ if(APPLE AND BUILD_OWNCLOUD_OSX_BUNDLE)
add_definitions(-DBUILD_OWNCLOUD_OSX_BUNDLE)
endif()
find_package(Qt${QT_MAJOR_VERSION} COMPONENTS Core)
option(QUICK_COMPILER "Use QtQuick compiler to improve performance" OFF)
# this option removes Http authentication, keychain, shibboleth etc and is intended for
@ -176,6 +188,9 @@ option(BUILD_GUI "BUILD_GUI" ON)
# build the tests
option(BUILD_TESTING "BUILD_TESTING" ON)
# allows to run nextclouddev in parallel to nextcloud + logs
option(NEXTCLOUD_DEV "NEXTCLOUD_DEV" OFF)
option(ENABLE_CLANG_TIDY "ENABLE_CLANG_TIDY" OFF)
if(ENABLE_CLANG_TIDY)
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
@ -214,6 +229,7 @@ if(BUILD_CLIENT)
find_package(OpenSSL 1.1 REQUIRED )
find_package(ZLIB REQUIRED)
find_package(SQLite3 3.9.0 REQUIRED)
if(NOT WIN32 AND NOT APPLE)
find_package(PkgConfig REQUIRED)

View File

@ -1,6 +1,15 @@
set( APPLICATION_NAME "Nextcloud" )
set( APPLICATION_SHORTNAME "Nextcloud" )
set( APPLICATION_EXECUTABLE "nextcloud" )
if(NEXTCLOUD_DEV)
set( APPLICATION_NAME "NextcloudDev" )
set( APPLICATION_SHORTNAME "NextDev" )
set( APPLICATION_EXECUTABLE "nextclouddev" )
set( APPLICATION_ICON_NAME "Nextcloud" )
else()
set( APPLICATION_NAME "Nextcloud" )
set( APPLICATION_SHORTNAME "Nextcloud" )
set( APPLICATION_EXECUTABLE "nextcloud" )
set( APPLICATION_ICON_NAME "${APPLICATION_SHORTNAME}" )
endif()
set( APPLICATION_CONFIG_NAME "${APPLICATION_EXECUTABLE}" )
set( APPLICATION_DOMAIN "nextcloud.com" )
set( APPLICATION_VENDOR "Nextcloud GmbH" )
@ -10,8 +19,6 @@ set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
if(APPLE AND APPLICATION_NAME STREQUAL "Nextcloud" AND EXISTS "${CMAKE_SOURCE_DIR}/theme/colored/Nextcloud-macOS-icon.svg")
set( APPLICATION_ICON_NAME "Nextcloud-macOS" )
message("Using macOS-specific application icon: ${APPLICATION_ICON_NAME}")
else()
set( APPLICATION_ICON_NAME "${APPLICATION_SHORTNAME}" )
endif()
set( APPLICATION_ICON_SET "SVG" )
@ -77,6 +84,6 @@ if(WIN32)
option( BUILD_WIN_TOOLS "Build Win32 migration tools" OFF )
endif()
if (APPLE)
if (APPLE AND CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER_EQUAL 11.0)
option( BUILD_FILE_PROVIDER_MODULE "Build the macOS virtual files File Provider module" OFF )
endif()

View File

@ -1,7 +1,7 @@
set( MIRALL_VERSION_MAJOR 3 )
set( MIRALL_VERSION_MINOR 10 )
set( MIRALL_VERSION_MINOR 13 )
set( MIRALL_VERSION_PATCH 50 )
set( MIRALL_VERSION_YEAR 2023 )
set( MIRALL_VERSION_YEAR 2024 )
set( MIRALL_SOVERSION 0 )
# Minimum supported server version according to https://docs.nextcloud.com/server/latest/admin_manual/release_schedule.html
@ -13,6 +13,10 @@ set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_MAJOR 26)
set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_MINOR 0)
set(NEXTCLOUD_SERVER_VERSION_SECURE_FILEDROP_MIN_SUPPORTED_PATCH 0)
set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MAJOR 28)
set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_MINOR 0)
set(NEXTCLOUD_SERVER_VERSION_MOUNT_ROOT_PROPERTY_SUPPORTED_PATCH 3)
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
set( MIRALL_VERSION_SUFFIX "git") #e.g. beta1, beta2, rc1
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
@ -33,3 +37,5 @@ set( MIRALL_VERSION_STRING "${MIRALL_VERSION}${MIRALL_VERSION_SUFFIX}" )
if( MIRALL_VERSION_BUILD )
set( MIRALL_VERSION_STRING "${MIRALL_VERSION_STRING} (build ${MIRALL_VERSION_BUILD})" )
endif( MIRALL_VERSION_BUILD )
set(QT_MAJOR_VERSION 6)

View File

@ -2,16 +2,13 @@
set -xe
export APPNAME=${APPNAME:-nextcloud}
export APPNAME=${APPNAME:-Nextcloud}
export EXECUTABLE_NAME=${EXECUTABLE_NAME:-nextcloud}
export BUILD_UPDATER=${BUILD_UPDATER:-OFF}
export BUILDNR=${BUILDNR:-0000}
export DESKTOP_CLIENT_ROOT=${DESKTOP_CLIENT_ROOT:-/home/user}
#Set Qt-5.15
export QT_BASE_DIR=/opt/kdeqt5.15
export QTDIR=$QT_BASE_DIR
export PATH=$QT_BASE_DIR/bin:$PATH
export QT_BASE_DIR=${QT_BASE_DIR:-/usr}
export OPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR:-/usr/lib/x86_64-linux-gnu}
# Set defaults
export SUFFIX=${DRONE_PULL_REQUEST:=master}
@ -29,12 +26,14 @@ mkdir build-client
cd build-client
cmake \
-G Ninja \
-D CMAKE_INSTALL_PREFIX=/usr \
-D BUILD_TESTING=OFF \
-D BUILD_UPDATER=$BUILD_UPDATER \
-D MIRALL_VERSION_BUILD=$BUILDNR \
-D MIRALL_VERSION_SUFFIX="$VERSION_SUFFIX" \
-D CMAKE_UNITY_BUILD=ON \
-DCMAKE_PREFIX_PATH=${QT_BASE_DIR} \
-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} \
-DCMAKE_INSTALL_PREFIX=/usr \
-DBUILD_TESTING=OFF \
-DBUILD_UPDATER=$BUILD_UPDATER \
-DMIRALL_VERSION_BUILD=$BUILDNR \
-DMIRALL_VERSION_SUFFIX="$VERSION_SUFFIX" \
-DCMAKE_UNITY_BUILD=ON \
${DESKTOP_CLIENT_ROOT}
cmake --build . --target all
DESTDIR=/app cmake --install .
@ -64,48 +63,37 @@ rm -rf etc
# com.nextcloud.desktopclient.nextcloud.desktop
DESKTOP_FILE=$(ls /app/usr/share/applications/*.desktop)
sed -i -e 's|Icon=nextcloud|Icon=Nextcloud|g' ${DESKTOP_FILE} # Bug in desktop file?
cp ./usr/share/icons/hicolor/512x512/apps/*.png . # Workaround for linuxeployqt bug, FIXME
# Because distros need to get their shit together
cp -R /usr/lib/x86_64-linux-gnu/libssl.so* ./usr/lib/
cp -R /usr/lib/x86_64-linux-gnu/libcrypto.so* ./usr/lib/
cp -P /usr/local/lib*/libssl.so* ./usr/lib/
cp -P /usr/local/lib*/libcrypto.so* ./usr/lib/
cp -P /usr/local/lib*/libsqlite*.so* ./usr/lib/
# Use linuxdeploy to deploy
export APPIMAGE_NAME=linuxdeploy-x86_64.AppImage
wget -O ${APPIMAGE_NAME} --ca-directory=/etc/ssl/certs -c "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
chmod a+x ${APPIMAGE_NAME}
./${APPIMAGE_NAME} --appimage-extract
rm ./${APPIMAGE_NAME}
cp -r ./squashfs-root ./linuxdeploy-squashfs-root
# NSS fun
cp -P -r /usr/lib/x86_64-linux-gnu/nss ./usr/lib/
export LD_LIBRARY_PATH=/app/usr/lib:${QT_BASE_DIR}/lib:/usr/local/lib/x86_64-linux-gnu:/usr/local/lib:/usr/local/lib64
./linuxdeploy-squashfs-root/AppRun --desktop-file=${DESKTOP_FILE} --icon-file=usr/share/icons/hicolor/512x512/apps/Nextcloud.png --executable=usr/bin/${EXECUTABLE_NAME} --appdir=AppDir
# Use linuxdeployqt to deploy
LINUXDEPLOYQT_VERSION="continuous"
wget -O linuxdeployqt.AppImage --ca-directory=/etc/ssl/certs -c "https://github.com/probonopd/linuxdeployqt/releases/download/${LINUXDEPLOYQT_VERSION}/linuxdeployqt-continuous-x86_64.AppImage"
chmod a+x linuxdeployqt.AppImage
./linuxdeployqt.AppImage --appimage-extract
rm ./linuxdeployqt.AppImage
cp -r ./squashfs-root ./linuxdeployqt-squashfs-root
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs -qmldir=${DESKTOP_CLIENT_ROOT}/src/gui
# Use linuxdeploy-plugin-qt to deploy qt dependencies
export APPIMAGE_NAME=linuxdeploy-plugin-qt-x86_64.AppImage
wget -O ${APPIMAGE_NAME} --ca-directory=/etc/ssl/certs -c "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
chmod a+x ${APPIMAGE_NAME}
./${APPIMAGE_NAME} --appimage-extract
rm ./${APPIMAGE_NAME}
cp -r ./squashfs-root ./linuxdeploy-plugin-qt-squashfs-root
# Set origin
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/lib*sync.so.0
export PATH=${QT_BASE_DIR}/bin:${PATH}
export QML_SOURCES_PATHS=${DESKTOP_CLIENT_ROOT}/src/gui
./linuxdeploy-plugin-qt-squashfs-root/AppRun --appdir=AppDir
# Build AppImage
./squashfs-root/AppRun ${DESKTOP_FILE} -appimage -updateinformation="gh-releases-zsync|nextcloud-releases|desktop|latest|Nextcloud-*-x86_64.AppImage.zsync"
# Workaround issue #103
rm -rf ./squashfs-root
APPIMAGE=$(ls *.AppImage)
"./${APPIMAGE}" --appimage-extract
rm "./${APPIMAGE}"
rm ./squashfs-root/usr/lib/libglib-2.0.so.0
rm ./squashfs-root/usr/lib/libgobject-2.0.so.0
PATH=./linuxdeployqt-squashfs-root/usr/bin:$PATH appimagetool -n ./squashfs-root "$APPIMAGE"
./linuxdeploy-squashfs-root/AppRun --desktop-file=${DESKTOP_FILE} --icon-file=usr/share/icons/hicolor/512x512/apps/Nextcloud.png --executable=usr/bin/${EXECUTABLE_NAME} --appdir=AppDir --output appimage
#move AppImage
if [ ! -z "$DRONE_COMMIT" ]
then
mv *.AppImage ${APPNAME}-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
mv *.AppImage ${EXECUTABLE_NAME}-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
else
mv *.AppImage ${EXECUTABLE_NAME}-${SUFFIX}-x86_64.AppImage
fi
mv *.AppImage ${DESKTOP_CLIENT_ROOT}/

View File

@ -18,7 +18,7 @@ if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then
UBUNTU_DISTRIBUTIONS="bionic focal jammy kinetic"
DEBIAN_DISTRIBUTIONS="buster stretch testing"
else
UBUNTU_DISTRIBUTIONS="jammy lunar mantic"
UBUNTU_DISTRIBUTIONS="jammy mantic noble"
DEBIAN_DISTRIBUTIONS="bullseye bookworm testing"
fi

View File

@ -9,7 +9,7 @@ else()
set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "0")
endif()
find_package(Qt5 5.15 COMPONENTS Core REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} COMPONENTS Core REQUIRED)
configure_file(create_mac.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
configure_file(macosx.pkgproj.cmake ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh)

View File

@ -10,6 +10,4 @@ if [ -x "$(command -v pluginkit)" ]; then
pluginkit -e use -i @APPLICATION_REV_DOMAIN@.FinderSyncExt
fi
open -a @APPLICATION_NAME@.app
exit 0

25
admin/test/wait_for_server.sh Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-FileCopyrightText: 2019-2020 Tobias Kaminsky <tobias@kaminsky.me>
# SPDX-License-Identifier: AGPL-3.0-or-later
counter=0
status=""
until [[ $status = "false" ]]; do
status=$(curl 2>/dev/null "http://$1/status.php" | jq .maintenance)
echo "($counter) $status"
if [[ "$status" =~ "false" || "$status" = "" ]]; then
let "counter += 1"
if [[ $counter -gt 90 ]]; then
echo "Failed to wait for server"
exit 1
fi
fi
sleep 10
done

View File

@ -194,14 +194,14 @@
</Component>
<!-- Register URI handler -->
<Component Id="RegistryUriHandler" Guid="*" Win64="$(var.PlatformWin64)">
<RegistryKey Root="HKCU" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryKey Root="HKLM" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryValue Type="string" Value="URL:$(var.AppName) Protocol" />
<RegistryValue Type="string" Name="URL Protocol" Value="" />
</RegistryKey>
<RegistryKey Root="HKCU" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)\DefaultIcon" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryKey Root="HKLM" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)\DefaultIcon" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryValue Type="string" Value="[INSTALLDIR]$(var.AppExe)" />
</RegistryKey>
<RegistryKey Root="HKCU" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)\shell\open\command" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryKey Root="HKLM" Key="Software\Classes\$(var.AppCommandOpenUrlScheme)\shell\open\command" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes">
<RegistryValue Type="string" Value="&quot;[INSTALLDIR]$(var.AppExe)&quot; &quot;%1&quot;" />
</RegistryKey>
</Component>

View File

@ -1,60 +1,68 @@
# - Try to find SQLite3
# Once done this will define
#
# SQLITE3_FOUND - system has SQLite3
# SQLITE3_INCLUDE_DIRS - the SQLite3 include directory
# SQLITE3_LIBRARIES - Link these to use SQLite3
# SQLITE3_DEFINITIONS - Compiler switches required for using SQLite3
#
# Copyright (c) 2009-2013 Andreas Schneider <asn@cryptomilk.org>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindSQLite3
-----------
if (UNIX)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_SQLITE3 sqlite3)
endif (PKG_CONFIG_FOUND)
endif (UNIX)
.. versionadded:: 3.14
find_path(SQLITE3_INCLUDE_DIR
NAMES
sqlite3.h
PATHS
${_SQLITE3_INCLUDEDIR}
${SQLITE3_INCLUDE_DIRS}
)
Find the SQLite libraries, v3
find_library(SQLITE3_LIBRARY
NAMES
sqlite3 sqlite3-0
PATHS
${_SQLITE3_LIBDIR}
${SQLITE3_LIBRARIES}
)
IMPORTED targets
^^^^^^^^^^^^^^^^
set(SQLITE3_INCLUDE_DIRS
${SQLITE3_INCLUDE_DIR}
)
This module defines the following :prop_tgt:`IMPORTED` target:
if (SQLITE3_LIBRARY)
set(SQLITE3_LIBRARIES
${SQLITE3_LIBRARIES}
${SQLITE3_LIBRARY}
)
endif (SQLITE3_LIBRARY)
``SQLite::SQLite3``
if (SQLite3_FIND_VERSION AND _SQLITE3_VERSION)
set(SQLite3_VERSION _SQLITE3_VERSION)
endif (SQLite3_FIND_VERSION AND _SQLITE3_VERSION)
Result variables
^^^^^^^^^^^^^^^^
This module will set the following variables if found:
``SQLite3_INCLUDE_DIRS``
where to find sqlite3.h, etc.
``SQLite3_LIBRARIES``
the libraries to link against to use SQLite3.
``SQLite3_VERSION``
version of the SQLite3 library found
``SQLite3_FOUND``
TRUE if found
#]=======================================================================]
# Look for the necessary header
find_path(SQLite3_INCLUDE_DIR NAMES sqlite3.h)
mark_as_advanced(SQLite3_INCLUDE_DIR)
# Look for the necessary library
find_library(SQLite3_LIBRARY NAMES sqlite3 sqlite)
mark_as_advanced(SQLite3_LIBRARY)
# Extract version information from the header file
if(SQLite3_INCLUDE_DIR)
file(STRINGS ${SQLite3_INCLUDE_DIR}/sqlite3.h _ver_line
REGEX "^#define SQLITE_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\""
LIMIT_COUNT 1)
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+"
SQLite3_VERSION "${_ver_line}")
unset(_ver_line)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARIES SQLITE3_INCLUDE_DIRS)
# show the SQLITE3_INCLUDE_DIRS and SQLITE3_LIBRARIES variables only in the advanced view
mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES)
find_package_handle_standard_args(SQLite3
REQUIRED_VARS SQLite3_INCLUDE_DIR SQLite3_LIBRARY
VERSION_VAR SQLite3_VERSION)
# Create the imported target
if(SQLite3_FOUND)
set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDE_DIR})
set(SQLite3_LIBRARIES ${SQLite3_LIBRARY})
if(NOT TARGET SQLite::SQLite3)
add_library(SQLite::SQLite3 UNKNOWN IMPORTED)
set_target_properties(SQLite::SQLite3 PROPERTIES
IMPORTED_LOCATION "${SQLite3_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${SQLite3_INCLUDE_DIR}")
endif()
endif()

View File

@ -5,7 +5,7 @@
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSMinimumSystemVersion</key>
<string>12.0.0</string>
<string>10.13.0</string>
<key>LSUIElement</key>
<true/>
<key>CFBundleDevelopmentRegion</key>

View File

@ -61,4 +61,6 @@
#cmakedefine CFAPI_SHELL_EXTENSIONS_LIB_NAME "@CFAPI_SHELL_EXTENSIONS_LIB_NAME@"
#cmakedefine01 NEXTCLOUD_DEV
#endif

View File

@ -15,12 +15,7 @@ CreateCache = False
# Category is case sensitive
[GeneralSettings]
General/MacDeploymentTarget = 10.15
## This is the location of your python installation.
## This value must be set.
Paths/Python = C:\Python39-x64
Paths/Python27 = C:\Python27-x64
General/MacDeploymentTarget = 12.0
Compile/BuildType = RelWithDebInfo
@ -30,9 +25,8 @@ Paths/downloaddir = ${Variables:Root}\downloads
ShortPath/Enabled = False
ShortPath/EnableJunctions = False
; Packager/RepositoryUrl = https://files.kde.org/craft/
Packager/RepositoryUrl = https://files.kde.org/craft/Qt6
Packager/PackageType = NullsoftInstallerPackager
Packager/RepositoryUrl = https://files.kde.org/craft/master/
ContinuousIntegration/Enabled = True
@ -44,40 +38,22 @@ Packager/UseCache = ${Variables:UseCache}
Packager/CreateCache = ${Variables:CreateCache}
Packager/CacheDir = ${Variables:Root}\cache
#CodeSigning/Enabled = ${Env:SIGN_PACKAGE}
#CodeSigning/Protected = True
#CodeSigning/Certificate = ${Env:CRAFT_CODESIGN_CERTIFICATE}
#CodeSigning/CommonName =
#CodeSigning/MacDeveloperId = ownCloud GmbH (4AP2STM4H5)
#CodeSigning/MacKeychainPath = sign-${Env:DRONE_BUILD_NUMBER}.keychain
[BlueprintSettings]
# don't try to pip install on the ci
python-modules.ignored = True
dev-utils/python2.ignored = True
dev-utils/python3.ignored = True
nextcloud-client.buildTests = True
binary/mysql.useMariaDB = False
[windows-msvc2019_64-cl]
QtSDK/Compiler = msvc2019_64
General/ABI = windows-msvc2019_64-cl
[windows-msvc2022_64-cl]
QtSDK/Compiler = msvc2022_64
General/ABI = windows-msvc2022_64-cl
Paths/Python = C:\Python312-x64
[macos-64-clang]
General/ABI = macos-64-clang
# Packager/PackageType = MacPkgPackager
[macos-64-clang-debug]
General/ABI = macos-64-clang
Compile/BuildType = Debug
[macos-clang-arm64]
General/ABI = macos-clang-arm64
[macos-clang-arm64-debug]
General/ABI = macos-clang-arm64
Compile/BuildType = Debug
Paths/Python = /Users/runner/hostedtoolcache/Python/3.12.3/arm64
[Env]
CRAFT_CODESIGN_CERTIFICATE =
SIGN_PACKAGE = False
SIGN_PACKAGE = False

View File

@ -128,7 +128,7 @@ Then, in Terminal:
.. code-block:: bash
% brew install git qt qtkeychain cmake openssl glib cmocka
% brew install git qt qtkeychain cmake openssl glib cmocka karchive
5. Certain Homebrew packages are not automatically linked in places where
the build scripts can find them, so you can create a shell-profile script
@ -136,9 +136,8 @@ Then, in Terminal:
.. code-block:: bash
% echo 'export OPENSSL_ROOT_DIR=$(brew --prefix openssl)' >> ~/.nextcloud_build_variables
% echo 'export QT_PATH=$(brew --prefix qt5)/bin' >> ~/.nextcloud_build_variables
% echo 'export Qt5LinguistTools_DIR=$(brew --prefix qt5)/lib/cmake/Qt5LinguistTools/' >> ~/.nextcloud_build_variables
% echo 'export QT_PATH=$(brew --prefix qt6)/bin' >> ~/.nextcloud_build_variables
% echo 'export CMAKE_PREFIX_PATH=$(brew --prefix qt6);$(brew --prefix karchive)' >> ~/.nextcloud_build_variables
.. note:: The name ``~/.nextcloud_build_variables`` is just a suggestion for
convenience. You can use a different file or create an entire shell
@ -207,58 +206,88 @@ Then, in Terminal:
Windows Development Build
-------------------------
If you want to test some changes and deploy them locally, you can build natively
on Windows using MinGW. If you want to generate an installer for deployment, please
follow `Windows Installer Build (Cross-Compile)`_ instead.
System requirements
-------------------
- Windows 10 or Windows 11
- `The desktop client code <https://github.com/nextcloud/desktop>`_
- Python 3
- PowerShell
- Microsoft Visual Studio 2022 and tools to compile C++
- `KDE Craft <https://community.kde.org/Craft>`_
1. Get the required dependencies:
Setting up Microsoft Visual Studio
----------------------------------
* Make sure that you have CMake_ and Git_.
* Download the Qt_ MinGW package. You will use the MinGW version bundled with it.
* Download an `OpenSSL Windows Build`_ (the non-"Light" version)
1. Click on 'Modify' in the Visual Studio Installer:
2. Get the QtKeychain_ sources as well as the latest versions of the Nextcloud client
from Git as follows
.. image:: ./images/building/visual-studio-installer.png
:alt: Visual Studio Installer
.. code-block:: bash
git clone https://github.com/frankosterfeld/qtkeychain.git
git clone git://github.com/nextcloud/client.git
2. Select 'Desktop development with C++'
3. Open the Qt MinGW shortcut console from the Start Menu
.. image:: ./images/building/desktop-development-with-cpp.png
:alt: Desktop development with C++
4. Make sure that OpenSSL's ``bin`` directory as well as your qtkeychain source
directories are in your PATH. This will allow CMake to find the library and
headers, as well as allow the Nextcloud client to find the DLLs at runtime::
Handling the dependencies
-------------------------
We handle the dependencies using `KDE Craft <https://community.kde.org/Craft>`_ because it is easy to set it up and it makes the maintenance much more reliable in all platforms.
set PATH=C:\<OpenSSL Install Dir>\bin;%PATH%
set PATH=C:\<qtkeychain Clone Dir>;%PATH%
1. Set up KDE Craft as instructed in `Get Involved/development/Windows - KDE Community Wiki <https://community.kde.org/Get_Involved/development/Windows>`_ - it requires Python 3 and PowerShell.
2. After running:
5. Build qtkeychain **directly in the source directory** so that the DLL is built
in the same directory as the headers to let CMake find them together through PATH::
.. code-block:: winbatch
cd <qtkeychain Clone Dir>
cmake -G "MinGW Makefiles" .
mingw32-make
cd ..
C:\CraftRoot\craft\craftenv.ps1
6. Create the build directory::
3. Add the `desktop client blueprints <https://github.com/nextcloud/desktop-client-blueprints>`_ - the instructions to handle the client dependencies:
mkdir client-build
cd client-build
.. code-block:: winbatch
7. Build the client::
craft --add-blueprint-repository [git]https://github.com/nextcloud/desktop-client-blueprints.git
craft craft
cmake -G "MinGW Makefiles" ../client
mingw32-make
4. Install all client dependencies:
.. note:: You can try using ninja to build in parallel using
``cmake -G Ninja ../client`` and ``ninja`` instead.
.. note:: Refer to the :ref:`generic-build-instructions` section for additional options.
.. code-block:: winbatch
The Nextcloud binary will appear in the ``bin`` directory.
craft --install-deps nextcloud-client
.. _`Windows Installer Build (Cross-Compile)`:
Compiling
---------
1. Make sure your environment variable %PATH% has no conflicting information to the environment you will use to compile the client. For instance, if you have installed OpenSSL previously and have added it to %PATH%, the OpenSSL installed might be a different version than what was installed via KDE Craft.
2. Open the Command Prompt (cmd.exe)
3. Run:
.. code-block:: winbatch
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
4. To use the tools installed with Visual Studio, you need the following in your %PATH%:
.. image:: ./images/building/path.png
:alt: Windows environment variables
5. Alternatively you can use the tools installed with KDE Craft by adding them to %PATH%:
.. code-block:: winbatch
set "PATH=C:\CraftRoot\bin;C:\CraftRoot\dev-utils\bin;%PATH%"
.. note::
C:\CraftRoot is the path used by default by KDE Craft. When you are setting it up you may choose a different folder.
6. Create build folder, run cmake, compile and install:
.. code-block:: winbatch
cd <desktop-repo-path>
mkdir build
cd build
cmake .. -G Ninja -DCMAKE_INSTALL_PREFIX=. -DCMAKE_PREFIX_PATH=C:\CraftRoot -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build . --target install
7. Now you can use `Qt Creator <https://doc.qt.io/qtcreator>`_ to import the build folder with its configurations to be able to work with the code.
Windows Installer (i.e. Deployment) Build (Cross-Compile)
---------------------------------------------------------
@ -355,7 +384,7 @@ To build the most up-to-date version of the client:
.. note:: qtkeychain must be compiled with the same prefix e.g ``CMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ .``
.. note:: Example:: ``cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/``
.. note:: Example:: ``cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt6 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/``
4. Call ``make``.
@ -371,8 +400,7 @@ The following are known cmake parameters:
You need to compile QtKeychain with the same Qt version.
* ``WITH_DOC=TRUE``: Creates doc and manpages through running ``make``; also adds install statements,
providing the ability to install using ``make install``.
* ``CMAKE_PREFIX_PATH=/path/to/Qt5.2.0/5.2.0/yourarch/lib/cmake/``: Builds using Qt5.
* ``BUILD_WITH_QT4=ON``: Builds using Qt4 (even if Qt5 is found).
* ``CMAKE_PREFIX_PATH=/path/to/Qt6/6.7.0/yourarch/lib/cmake/``: Builds using Qt6.
* ``CMAKE_INSTALL_PREFIX=path``: Set an install prefix. This is mandatory on Mac OS
Address Sanitizer

View File

@ -41,16 +41,16 @@ master_doc = 'index'
# General information about the project.
project = u'Nextcloud Client Manual'
copyright = u'2013-2023, The Nextcloud developers'
copyright = u'2013-2024, The Nextcloud developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '3.11'
version = '3.13'
# The full version, including alpha/beta/rc tags.
release = '3.10.50'
release = '3.12.50'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -41,15 +41,17 @@ Some interesting values that can be set on the configuration file are:
| ``chunkSize`` | ``10000000`` (10 MB) | Specifies the chunk size of uploaded files in bytes. |
| | | The client will dynamically adjust this size within the maximum and minimum bounds (see below). |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``minChunkSize`` | ``1000000`` (1 MB) | Specifies the minimum chunk size of uploaded files in bytes. |
| ``forceLoginV2`` | ``false`` | If the client should force the new login flow, eventhough some circumstances might need the old flow. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``maxChunkSize`` | ``1000000000`` (1000 MB) | Specifies the maximum chunk size of uploaded files in bytes. |
| ``minChunkSize`` | ``5000000`` (5 MB) | Specifies the minimum chunk size of uploaded files in bytes. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``maxChunkSize`` | ``5000000000`` (5000 MB) | Specifies the maximum chunk size of uploaded files in bytes. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``targetChunkUploadDuration`` | ``60000`` (1 minute) | Target duration in milliseconds for chunk uploads. |
| | | The client adjusts the chunk size until each chunk upload takes approximately this long. |
| | | Set to 0 to disable dynamic chunk sizing. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``promptDeleteAllFiles`` | ``true`` | If a UI prompt should ask for confirmation if it was detected that all files and folders were deleted. |
| ``promptDeleteAllFiles`` | ``false`` | If a UI prompt should ask for confirmation if it was detected that all files and folders were deleted. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+
| ``timeout`` | ``300`` | The timeout for network connections in seconds. |
+----------------------------------+--------------------------+--------------------------------------------------------------------------------------------------------+

View File

@ -1,15 +1,40 @@
FAQ
===
How the "Edit locally" functionality works when running the desktop client AppImage
-----------------------------------------------------------------------------------
How the "Edit locally" functionality works
------------------------------------------
This functionality depends on the desktop client ability to register the mime to handle the nc:// scheme. That is the handler used by the server to open a file locally. This will allow the desktop client to open a document with the local editor when you click on the option "Edit locally" in your Nextcloud instance.
This functionality depends on the desktop client ability to register the mime to handle the nc:// scheme. That is the handler used by the server to open a file locally.
.. note::
Without properly registering the mime, independent of the browser and distro being used, the desktop client will fail to open a document with the local editor when you click on the option "Edit locally" in your Nextcloud instance.
The browser will warn you of the failure: "Failed to launch 'nc://...' because the scheme does not have a registered handler."
We use AppImage due to its universal compatibility but to take full advantage of the desktop client features you will need a third part software to integrate the AppImage in your system.
We have tested `AppImageLauncher <https://github.com/TheAssassin/AppImageLauncher>`_ and alternatively there is `Go AppImage <https://github.com/probonopd/go-appimage>`_.
How to enable it
^^^^^^^^^^^^^^^^^
Without it, independent of the browser and distro being used, the desktop client will fail to open a document with the local editor when you click on the option "Edit locally" in your Nextcloud instance.
In order to do that, you need to install the desktop client with the MSI installer on Windows or use a third party software to integrate the AppImage in your system on Linux.
On Linux
^^^^^^^^
We use AppImage due to its universal compatibility but to take full advantage of the desktop client features you will need a third part software to integrate the AppImage in your system: we have tested `AppImageLauncher <https://github.com/TheAssassin/AppImageLauncher>`_ and alternatively there is `Go AppImage <https://github.com/probonopd/go-appimage>`_.
On Windows
^^^^^^^^^^
The MSI installer will alter your system registry to register the mime to handle the nc:// scheme.
Alternatively, you can manually register the mime to handle the nc:// scheme:
1. Save the following content to a .reg file:
```
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\nc\shell\open\command]
@="\"C:\\Program Files\\Nextcloud\\nextcloud.exe\" \"%1\""
```
2. Double click on the .reg file to import it into the registry.
See https://nextcloud.com/blog/nextcloud-office-release-solves-document-compatibility-overhauls-knowledge-management/ for more information.

View File

@ -33,13 +33,13 @@ download page.
System Requirements
----------------------------------
- Windows 8.1+
- macOS 12+ (64-bit only)
- Linux
- FreeBSD
- Windows 10+ (64-bits only)
- macOS 11.4+ (64-bits only)
- Linux (ubuntu 22.04 or openSUSE 15.5 or ...) (64-bits only)
.. note::
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous LTS.
For Linux distributions, we support, if technically feasible, the current LTS releases.
For BSD, we support them if technically feasible but we do not test
Customizing the Windows Installation
------------------------------------

View File

@ -23,5 +23,5 @@ Icon=@APPLICATION_EXECUTABLE@
# Translations
Name[da]=Skrivebordsklient til @APPLICATION_NAME@
Comment[da]=Klient til @APPLICATION_NAME@-skrivebordssynkronisering
GenericName[da]=Mappesynkronisering
GenericName[da]=Mappe Synkronisering
Icon[da]=@APPLICATION_ICON_NAME@

View File

@ -23,5 +23,5 @@ Icon=@APPLICATION_EXECUTABLE@
# Translations
Name[ro]=@Numele_aplicației@ Client de sincronizare pentru PC
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
GenericName[ro]=Sincronizare director
GenericName[ro]=Sincronizare folder
Icon[ro]=@APPLICATION_ICON_NAME@

View File

@ -12,6 +12,7 @@
<file>src/gui/filedetails/FileDetailsView.qml</file>
<file>src/gui/filedetails/FileDetailsWindow.qml</file>
<file>src/gui/filedetails/FileTag.qml</file>
<file>src/gui/filedetails/NCInputDateField.qml</file>
<file>src/gui/filedetails/NCInputTextEdit.qml</file>
<file>src/gui/filedetails/NCInputTextField.qml</file>
<file>src/gui/filedetails/NCTabButton.qml</file>
@ -61,6 +62,11 @@
<file>src/gui/ResolveConflictsDialog.qml</file>
<file>src/gui/ConflictDelegate.qml</file>
<file>src/gui/ConflictItemFileInfo.qml</file>
<file>src/gui/filedetails/NCInputDateField.qml</file>
<file>src/gui/macOS/ui/FileProviderSettings.qml</file>
<file>src/gui/macOS/ui/FileProviderFileDelegate.qml</file>
<file>src/gui/macOS/ui/FileProviderEvictionDialog.qml</file>
<file>src/gui/macOS/ui/FileProviderSyncStatus.qml</file>
<file>src/gui/macOS/ui/FileProviderStorageInfo.qml</file>
<file>src/gui/macOS/ui/FileProviderFastEnumerationSettings.qml</file>
</qresource>
</RCC>

View File

@ -11,11 +11,12 @@ if( UNIX AND NOT APPLE )
endif()
if(BUILD_SHELL_INTEGRATION_DOLPHIN)
find_package(KF5KIO "5.16")
if(KF5KIO_FOUND)
find_package(KF5KIO "5.16" CONFIG QUIET)
find_package(KF6KIO "5.240" CONFIG QUIET)
if(KF5KIO_FOUND OR KF6KIO_FOUND)
add_subdirectory(dolphin)
else()
message("Dolphin plugin disabled: KDE Frameworks 5.16 not found")
message("Dolphin plugin disabled: KDE Frameworks 5 and 6 not found")
endif()
endif()
endif()

View File

@ -35,7 +35,20 @@ if(APPLE)
COMMENT building macOS File Provider extension
VERBATIM)
add_dependencies(mac_overlayplugin mac_fileproviderplugin nextcloud) # for the ownCloud.icns to be generated
add_custom_target( mac_fileprovideruiplugin ALL
xcodebuild ARCHS=${CMAKE_OSX_ARCHITECTURES} ONLY_ACTIVE_ARCH=NO
-project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj
-target FileProviderUIExt -configuration ${XCODE_TARGET_CONFIGURATION} "SYMROOT=${CMAKE_CURRENT_BINARY_DIR}"
"OC_APPLICATION_EXECUTABLE_NAME=${APPLICATION_EXECUTABLE}"
"OC_APPLICATION_VENDOR=${APPLICATION_VENDOR}"
"OC_APPLICATION_NAME=${APPLICATION_NAME}"
"OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN}"
"OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}"
DEPENDS mac_fileproviderplugin
COMMENT building macOS File Provider UI extension
VERBATIM)
add_dependencies(mac_overlayplugin mac_fileproviderplugin mac_fileprovideruiplugin nextcloud) # for the ownCloud.icns to be generated
else()
add_dependencies(mac_overlayplugin nextcloud) # for the ownCloud.icns to be generated
endif()
@ -55,6 +68,10 @@ if(APPLE)
install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)
install(DIRECTORY ${OSX_PLUGINS_BINARY_DIR}/FileProviderUIExt.appex
DESTINATION ${OSX_PLUGINS_INSTALL_DIR}
USE_SOURCE_PERMISSIONS)
endif()
endif()
endif()

View File

@ -1,145 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import OSLog
extension NextcloudFilesDatabaseManager {
func directoryMetadata(account: String, serverUrl: String) -> NextcloudItemMetadataTable? {
// We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com"
let problematicSeparator = "://"
let placeholderSeparator = "__TEMP_REPLACE__"
let serverUrlWithoutPrefix = serverUrl.replacingOccurrences(of: problematicSeparator, with: placeholderSeparator)
var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/")
let directoryItemFileName = String(splitServerUrl.removeLast())
let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences(of: placeholderSeparator, with: problematicSeparator)
if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", account, directoryItemServerUrl, directoryItemFileName).first {
return NextcloudItemMetadataTable(value: metadata)
}
return nil
}
func childItemsForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@", directoryServerUrl)
return sortedItemMetadatas(metadatas)
}
func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND directory == true", directoryServerUrl)
return sortedItemMetadatas(metadatas)
}
func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) -> NextcloudItemMetadataTable? {
return directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl)
}
func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? {
if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first {
return NextcloudItemMetadataTable(value: metadata)
}
return nil
}
func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account)
return sortedItemMetadatas(metadatas)
}
func directoryMetadatas(account: String, parentDirectoryServerUrl: String) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, parentDirectoryServerUrl)
return sortedItemMetadatas(metadatas)
}
// Deletes all metadatas related to the info of the directory provided
func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) -> [NextcloudItemMetadataTable]? {
let database = ncDatabase()
guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
Logger.ncFilesDatabase.error("Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion")
return nil
}
let directoryMetadataCopy = NextcloudItemMetadataTable(value: directoryMetadata)
let directoryUrlPath = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
let directoryAccount = directoryMetadata.account
let directoryEtag = directoryMetadata.etag
Logger.ncFilesDatabase.debug("Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)")
guard deleteItemMetadata(ocId: directoryMetadata.ocId) else {
Logger.ncFilesDatabase.debug("Failure to delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)")
return nil
}
var deletedMetadatas: [NextcloudItemMetadataTable] = [directoryMetadataCopy]
let results = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryAccount, directoryUrlPath)
for result in results {
let successfulItemMetadataDelete = deleteItemMetadata(ocId: result.ocId)
if (successfulItemMetadataDelete) {
deletedMetadatas.append(NextcloudItemMetadataTable(value: result))
}
if localFileMetadataFromOcId(result.ocId) != nil {
deleteLocalFileMetadata(ocId: result.ocId)
}
}
Logger.ncFilesDatabase.debug("Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)")
return deletedMetadatas
}
func renameDirectoryAndPropagateToChildren(ocId: String, newServerUrl: String, newFileName: String) -> [NextcloudItemMetadataTable]? {
let database = ncDatabase()
guard let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@ AND directory == true", ocId).first else {
Logger.ncFilesDatabase.error("Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming")
return nil
}
let oldItemServerUrl = directoryMetadata.serverUrl
let oldDirectoryServerUrl = oldItemServerUrl + "/" + directoryMetadata.fileName
let newDirectoryServerUrl = newServerUrl + "/" + newFileName
let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, oldDirectoryServerUrl)
renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName)
Logger.ncFilesDatabase.debug("Renamed root renaming directory")
do {
try database.write {
for childItem in childItemResults {
let oldServerUrl = childItem.serverUrl
let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldDirectoryServerUrl, with: newDirectoryServerUrl)
childItem.serverUrl = movedServerUrl
database.add(childItem, update: .all)
Logger.ncFilesDatabase.debug("Moved childItem at \(oldServerUrl) to \(movedServerUrl)")
}
}
} catch let error {
Logger.ncFilesDatabase.error("Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)")
return nil
}
let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, newDirectoryServerUrl)
return sortedItemMetadatas(updatedChildItemResults)
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import RealmSwift
import OSLog
extension NextcloudFilesDatabaseManager {
func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
return NextcloudLocalFileMetadataTable(value: metadata)
}
return nil
}
func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) {
let database = ncDatabase()
do {
try database.write {
let newLocalFileMetadata = NextcloudLocalFileMetadataTable()
newLocalFileMetadata.ocId = itemMetadata.ocId
newLocalFileMetadata.fileName = itemMetadata.fileName
newLocalFileMetadata.account = itemMetadata.account
newLocalFileMetadata.etag = itemMetadata.etag
newLocalFileMetadata.exifDate = Date()
newLocalFileMetadata.exifLatitude = "-1"
newLocalFileMetadata.exifLongitude = "-1"
database.add(newLocalFileMetadata, update: .all)
Logger.ncFilesDatabase.debug("Added local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public)")
}
} catch let error {
Logger.ncFilesDatabase.error("Could not add local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
}
}
func deleteLocalFileMetadata(ocId: String) {
let database = ncDatabase()
do {
try database.write {
let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
database.delete(results)
}
} catch let error {
Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
}
}
private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>) -> [NextcloudLocalFileMetadataTable] {
let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) })
}
func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] {
let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("account == %@", account)
return sortedLocalFileMetadatas(results)
}
func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
let localFileMetadatas = localFileMetadatas(account: account)
let localFileMetadatasOcIds = Array(localFileMetadatas.map { $0.ocId })
var itemMetadatas: [NextcloudItemMetadataTable] = []
for ocId in localFileMetadatasOcIds {
guard let itemMetadata = itemMetadataFromOcId(ocId) else {
Logger.ncFilesDatabase.error("Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)")
continue;
}
itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata))
}
return itemMetadatas
}
}

View File

@ -1,326 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import RealmSwift
import FileProvider
import NextcloudKit
import OSLog
class NextcloudFilesDatabaseManager : NSObject {
static let shared = {
return NextcloudFilesDatabaseManager();
}()
let relativeDatabaseFolderPath = "Database/"
let databaseFilename = "fileproviderextdatabase.realm"
let relativeDatabaseFilePath: String
var databasePath: URL?
let schemaVersion: UInt64 = 100
override init() {
self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename
guard let fileProviderDataDirUrl = pathForFileProviderExtData() else {
super.init()
return
}
self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath)
// Disable file protection for directory DB
// https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm
let dbFolder = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFolderPath)
let dbFolderPath = dbFolder.path
do {
try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true)
try FileManager.default.setAttributes([FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: dbFolderPath)
} catch let error {
Logger.ncFilesDatabase.error("Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)")
}
let config = Realm.Configuration(
fileURL: self.databasePath,
schemaVersion: self.schemaVersion,
objectTypes: [NextcloudItemMetadataTable.self, NextcloudLocalFileMetadataTable.self]
)
Realm.Configuration.defaultConfiguration = config
do {
_ = try Realm()
Logger.ncFilesDatabase.info("Successfully started Realm db for FileProviderExt")
} catch let error as NSError {
Logger.ncFilesDatabase.error("Error opening Realm db: \(error.localizedDescription, privacy: .public)")
}
super.init()
}
func ncDatabase() -> Realm {
let realm = try! Realm()
realm.refresh()
return realm
}
func anyItemMetadatasForAccount(_ account: String) -> Bool {
return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty
}
func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? {
// Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db
// Let's therefore create a copy
if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first {
return NextcloudItemMetadataTable(value: itemMetadata)
}
return nil
}
func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>) -> [NextcloudItemMetadataTable] {
let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true)
return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) })
}
func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account)
return sortedItemMetadatas(metadatas)
}
func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@", account, serverUrl)
return sortedItemMetadatas(metadatas)
}
func itemMetadatas(account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, status.rawValue)
return sortedItemMetadatas(metadatas)
}
func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> NextcloudItemMetadataTable? {
let ocId = identifier.rawValue
return itemMetadataFromOcId(ocId)
}
private func processItemMetadatasToDelete(existingMetadatas: Results<NextcloudItemMetadataTable>,
updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
var deletedMetadatas: [NextcloudItemMetadataTable] = []
for existingMetadata in existingMetadatas {
guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }),
let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) else { continue }
deletedMetadatas.append(metadataToDelete)
Logger.ncFilesDatabase.debug("Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: .public)")
}
return deletedMetadatas
}
private func processItemMetadatasToUpdate(existingMetadatas: Results<NextcloudItemMetadataTable>,
updatedMetadatas: [NextcloudItemMetadataTable],
updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], directoriesNeedingRename: [NextcloudItemMetadataTable]) {
var returningNewMetadatas: [NextcloudItemMetadataTable] = []
var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = []
var directoriesNeedingRename: [NextcloudItemMetadataTable] = []
for updatedMetadata in updatedMetadatas {
if let existingMetadata = existingMetadatas.first(where: { $0.ocId == updatedMetadata.ocId }) {
if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue &&
!existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) {
if updatedMetadata.directory {
if updatedMetadata.serverUrl != existingMetadata.serverUrl || updatedMetadata.fileName != existingMetadata.fileName {
directoriesNeedingRename.append(NextcloudItemMetadataTable(value: updatedMetadata))
updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually
} else if !updateDirectoryEtags {
updatedMetadata.etag = existingMetadata.etag
}
}
returningUpdatedMetadatas.append(updatedMetadata)
Logger.ncFilesDatabase.debug("Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)")
} else {
Logger.ncFilesDatabase.debug("Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)")
}
} else { // This is a new metadata
if !updateDirectoryEtags && updatedMetadata.directory {
updatedMetadata.etag = ""
}
returningNewMetadatas.append(updatedMetadata)
Logger.ncFilesDatabase.debug("Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)")
}
}
return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename)
}
func updateItemMetadatas(account: String, serverUrl: String, updatedMetadatas: [NextcloudItemMetadataTable], updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) {
let database = ncDatabase()
do {
let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter("account == %@ AND serverUrl == %@ AND status == %@", account, serverUrl, NextcloudItemMetadataTable.Status.normal.rawValue)
let metadatasToDelete = processItemMetadatasToDelete(existingMetadatas: existingMetadatas,
updatedMetadatas: updatedMetadatas)
let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas,
updatedMetadatas: updatedMetadatas,
updateDirectoryEtags: updateDirectoryEtags)
var metadatasToUpdate = metadatasToChange.updatedMetadatas
let metadatasToCreate = metadatasToChange.newMetadatas
let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename
let metadatasToAdd = Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) +
Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) })
for metadata in directoriesNeedingRename {
if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(ocId: metadata.ocId, newServerUrl: metadata.serverUrl, newFileName: metadata.fileName) {
metadatasToUpdate += updatedDirectoryChildren
}
}
try database.write {
for metadata in metadatasToDelete {
// Can't pass copies, we need the originals from the database
database.delete(ncDatabase().objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId))
}
for metadata in metadatasToAdd {
database.add(metadata, update: .all)
}
}
return (newMetadatas: metadatasToCreate, updatedMetadatas: metadatasToUpdate, deletedMetadatas: metadatasToDelete)
} catch let error {
Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
return (nil, nil, nil)
}
}
func setStatusForItemMetadata(_ metadata: NextcloudItemMetadataTable, status: NextcloudItemMetadataTable.Status, completionHandler: @escaping(_ updatedMetadata: NextcloudItemMetadataTable?) -> Void) {
let database = ncDatabase()
do {
try database.write {
guard let result = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", metadata.ocId).first else {
Logger.ncFilesDatabase.debug("Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)")
return
}
result.status = status.rawValue
database.add(result, update: .all)
Logger.ncFilesDatabase.debug("Updated status for item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)")
completionHandler(NextcloudItemMetadataTable(value: result))
}
} catch let error {
Logger.ncFilesDatabase.error("Could not update status for item metadata with ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
completionHandler(nil)
}
}
func addItemMetadata(_ metadata: NextcloudItemMetadataTable) {
let database = ncDatabase()
do {
try database.write {
database.add(metadata, update: .all)
Logger.ncFilesDatabase.debug("Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)")
}
} catch let error {
Logger.ncFilesDatabase.error("Could not add item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
}
}
@discardableResult func deleteItemMetadata(ocId: String) -> Bool {
let database = ncDatabase()
do {
try database.write {
let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
database.delete(results)
}
return true
} catch let error {
Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
return false
}
}
func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) {
let database = ncDatabase()
do {
try database.write {
guard let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId).first else {
Logger.ncFilesDatabase.debug("Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: .public)")
return
}
let oldFileName = itemMetadata.fileName
let oldServerUrl = itemMetadata.serverUrl
itemMetadata.fileName = newFileName
itemMetadata.fileNameView = newFileName
itemMetadata.serverUrl = newServerUrl
database.add(itemMetadata, update: .all)
Logger.ncFilesDatabase.debug("Renamed item \(oldFileName, privacy: .public) to \(newFileName, privacy: .public), moved from serverUrl: \(oldServerUrl, privacy: .public) to serverUrl: \(newServerUrl, privacy: .public)")
}
} catch let error {
Logger.ncFilesDatabase.error("Could not rename filename of item metadata with ocID: \(ocId, privacy: .public) to proposed name \(newFileName, privacy: .public) at proposed serverUrl \(newServerUrl, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
}
}
func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) -> NSFileProviderItemIdentifier? {
let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
if metadata.serverUrl == homeServerFilesUrl {
return .rootContainer
}
guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else {
Logger.ncFilesDatabase.error("Could not get item parent directory metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)")
return nil
}
if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) {
return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId)
}
Logger.ncFilesDatabase.error("Could not get item parent directory item metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)")
return nil
}
}

View File

@ -1,128 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import NextcloudKit
extension NextcloudItemMetadataTable {
static func fromNKFile(_ file: NKFile, account: String) -> NextcloudItemMetadataTable {
let metadata = NextcloudItemMetadataTable()
metadata.account = account
metadata.checksums = file.checksums
metadata.commentsUnread = file.commentsUnread
metadata.contentType = file.contentType
if let date = file.creationDate {
metadata.creationDate = date as Date
} else {
metadata.creationDate = file.date as Date
}
metadata.dataFingerprint = file.dataFingerprint
metadata.date = file.date as Date
metadata.directory = file.directory
metadata.downloadURL = file.downloadURL
metadata.e2eEncrypted = file.e2eEncrypted
metadata.etag = file.etag
metadata.favorite = file.favorite
metadata.fileId = file.fileId
metadata.fileName = file.fileName
metadata.fileNameView = file.fileName
metadata.hasPreview = file.hasPreview
metadata.iconName = file.iconName
metadata.mountType = file.mountType
metadata.name = file.name
metadata.note = file.note
metadata.ocId = file.ocId
metadata.ownerId = file.ownerId
metadata.ownerDisplayName = file.ownerDisplayName
metadata.lock = file.lock
metadata.lockOwner = file.lockOwner
metadata.lockOwnerEditor = file.lockOwnerEditor
metadata.lockOwnerType = file.lockOwnerType
metadata.lockOwnerDisplayName = file.lockOwnerDisplayName
metadata.lockTime = file.lockTime
metadata.lockTimeOut = file.lockTimeOut
metadata.path = file.path
metadata.permissions = file.permissions
metadata.quotaUsedBytes = file.quotaUsedBytes
metadata.quotaAvailableBytes = file.quotaAvailableBytes
metadata.richWorkspace = file.richWorkspace
metadata.resourceType = file.resourceType
metadata.serverUrl = file.serverUrl
metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices
for element in file.sharePermissionsCloudMesh {
metadata.sharePermissionsCloudMesh.append(element)
}
for element in file.shareType {
metadata.shareType.append(element)
}
metadata.size = file.size
metadata.classFile = file.classFile
//FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown
if (metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown") && metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue {
metadata.classFile = NKCommon.TypeClassFile.document.rawValue
}
if let date = file.uploadDate {
metadata.uploadDate = date as Date
} else {
metadata.uploadDate = file.date as Date
}
metadata.urlBase = file.urlBase
metadata.user = file.user
metadata.userId = file.userId
// Support for finding the correct filename for e2ee files should go here
return metadata
}
static func metadatasFromDirectoryReadNKFiles(_ files: [NKFile],
account: String,
completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable,
_ childDirectoriesMetadatas: [NextcloudItemMetadataTable],
_ metadatas: [NextcloudItemMetadataTable]) -> Void) {
var directoryMetadataSet = false
var directoryMetadata = NextcloudItemMetadataTable()
var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = []
var metadatas: [NextcloudItemMetadataTable] = []
let conversionQueue = DispatchQueue(label: "nkFileToMetadataConversionQueue", qos: .userInitiated, attributes: .concurrent)
let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) // Serial queue
let dispatchGroup = DispatchGroup()
for file in files {
if metadatas.isEmpty && !directoryMetadataSet {
let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account)
directoryMetadata = metadata;
directoryMetadataSet = true;
} else {
conversionQueue.async(group: dispatchGroup) {
let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account)
appendQueue.async(group: dispatchGroup) {
metadatas.append(metadata)
if metadata.directory {
childDirectoriesMetadatas.append(metadata)
}
}
}
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas)
}
}
}

View File

@ -1,213 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import RealmSwift
import FileProvider
import NextcloudKit
class NextcloudItemMetadataTable: Object {
enum Status: Int {
case downloadError = -4
case downloading = -3
case inDownload = -2
case waitDownload = -1
case normal = 0
case waitUpload = 1
case inUpload = 2
case uploading = 3
case uploadError = 4
}
enum SharePermissions: Int {
case readShare = 1
case updateShare = 2
case createShare = 4
case deleteShare = 8
case shareShare = 16
case maxFileShare = 19
case maxFolderShare = 31
}
@Persisted(primaryKey: true) var ocId: String
@Persisted var account = ""
@Persisted var assetLocalIdentifier = ""
@Persisted var checksums = ""
@Persisted var chunk: Bool = false
@Persisted var classFile = ""
@Persisted var commentsUnread: Bool = false
@Persisted var contentType = ""
@Persisted var creationDate = Date()
@Persisted var dataFingerprint = ""
@Persisted var date = Date()
@Persisted var directory: Bool = false
@Persisted var deleteAssetLocalIdentifier: Bool = false
@Persisted var downloadURL = ""
@Persisted var e2eEncrypted: Bool = false
@Persisted var edited: Bool = false
@Persisted var etag = ""
@Persisted var etagResource = ""
@Persisted var favorite: Bool = false
@Persisted var fileId = ""
@Persisted var fileName = ""
@Persisted var fileNameView = ""
@Persisted var hasPreview: Bool = false
@Persisted var iconName = ""
@Persisted var iconUrl = ""
@Persisted var isExtractFile: Bool = false
@Persisted var livePhoto: Bool = false
@Persisted var mountType = ""
@Persisted var name = "" // for unifiedSearch is the provider.id
@Persisted var note = ""
@Persisted var ownerId = ""
@Persisted var ownerDisplayName = ""
@Persisted var lock = false
@Persisted var lockOwner = ""
@Persisted var lockOwnerEditor = ""
@Persisted var lockOwnerType = 0
@Persisted var lockOwnerDisplayName = ""
@Persisted var lockTime: Date?
@Persisted var lockTimeOut: Date?
@Persisted var path = ""
@Persisted var permissions = ""
@Persisted var quotaUsedBytes: Int64 = 0
@Persisted var quotaAvailableBytes: Int64 = 0
@Persisted var resourceType = ""
@Persisted var richWorkspace: String?
@Persisted var serverUrl = "" // For parent directory!!
@Persisted var session = ""
@Persisted var sessionError = ""
@Persisted var sessionSelector = ""
@Persisted var sessionTaskIdentifier: Int = 0
@Persisted var sharePermissionsCollaborationServices: Int = 0
let sharePermissionsCloudMesh = List<String>() // TODO: Find a way to compare these in remote state check
let shareType = List<Int>()
@Persisted var size: Int64 = 0
@Persisted var status: Int = 0
@Persisted var subline: String?
@Persisted var trashbinFileName = ""
@Persisted var trashbinOriginalLocation = ""
@Persisted var trashbinDeletionTime = Date()
@Persisted var uploadDate = Date()
@Persisted var url = ""
@Persisted var urlBase = ""
@Persisted var user = ""
@Persisted var userId = ""
var fileExtension: String {
(fileNameView as NSString).pathExtension
}
var fileNoExtension: String {
(fileNameView as NSString).deletingPathExtension
}
var isRenameable: Bool {
return lock
}
var isPrintable: Bool {
if isDocumentViewableOnly {
return false
}
if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue {
return true
}
return false
}
var isDocumentViewableOnly: Bool {
return sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue &&
classFile == NKCommon.TypeClassFile.document.rawValue
}
var isCopyableInPasteboard: Bool {
!isDocumentViewableOnly && !directory
}
var isModifiableWithQuickLook: Bool {
if directory || isDocumentViewableOnly {
return false
}
return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue
}
var isSettableOnOffline: Bool {
return session.isEmpty && !isDocumentViewableOnly
}
var canOpenIn: Bool {
return session.isEmpty && !isDocumentViewableOnly && !directory
}
var isDownloadUpload: Bool {
return status == Status.inDownload.rawValue ||
status == Status.downloading.rawValue ||
status == Status.inUpload.rawValue ||
status == Status.uploading.rawValue
}
var isDownload: Bool {
status == Status.inDownload.rawValue || status == Status.downloading.rawValue
}
var isUpload: Bool {
status == Status.inUpload.rawValue || status == Status.uploading.rawValue
}
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? NextcloudItemMetadataTable {
return self.fileId == object.fileId &&
self.account == object.account &&
self.path == object.path &&
self.fileName == object.fileName
}
return false
}
func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool {
return comparingMetadata.etag == self.etag &&
comparingMetadata.fileNameView == self.fileNameView &&
comparingMetadata.date == self.date &&
comparingMetadata.permissions == self.permissions &&
comparingMetadata.hasPreview == self.hasPreview &&
comparingMetadata.note == self.note &&
comparingMetadata.lock == self.lock &&
comparingMetadata.sharePermissionsCollaborationServices == self.sharePermissionsCollaborationServices &&
comparingMetadata.favorite == self.favorite
}
/// Returns false if the user is lokced out of the file. I.e. The file is locked but by someone else
func canUnlock(as user: String) -> Bool {
return !lock || (lockOwner == user && lockOwnerType == 0)
}
func thumbnailUrl(size: CGSize) -> URL? {
guard hasPreview else {
return nil
}
let urlBase = urlBase.urlEncoded!
let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user // Leave the leading slash
let serverFileRelativeUrl = serverUrl.replacingOccurrences(of: webdavUrl, with: "") + "/" + fileName
let urlString = "\(urlBase)/index.php/core/preview.png?file=\(serverFileRelativeUrl)&x=\(size.width)&y=\(size.height)&a=1&mode=cover"
return URL(string: urlString)
}
}

View File

@ -17,11 +17,31 @@ import OSLog
extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
static let desktopClientConnection = Logger(subsystem: subsystem, category: "desktopclientconnection")
static let enumeration = Logger(subsystem: subsystem, category: "enumeration")
static let fileProviderExtension = Logger(subsystem: subsystem, category: "fileproviderextension")
static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer")
static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations")
static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase")
static let materialisedFileHandling = Logger(subsystem: subsystem, category: "materialisedfilehandling")
static let desktopClientConnection = Logger(
subsystem: subsystem, category: "desktopclientconnection")
static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService")
static let fileProviderExtension = Logger(
subsystem: subsystem, category: "fileproviderextension")
static let shares = Logger(subsystem: subsystem, category: "shares")
static let logger = Logger(subsystem: subsystem, category: "logger")
@available(macOSApplicationExtension 12.0, *)
static func logEntries(interval: TimeInterval = -3600) -> (Array<String>?, 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)
}
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import FileProvider
import NextcloudKit
extension NKError {
static var noChangesErrorCode: Int {
return -200
}
var isCouldntConnectError: Bool {
return errorCode == -9999 ||
errorCode == -1001 ||
errorCode == -1004 ||
errorCode == -1005 ||
errorCode == -1009 ||
errorCode == -1012 ||
errorCode == -1200 ||
errorCode == -1202 ||
errorCode == 500 ||
errorCode == 503 ||
errorCode == 200
}
var isUnauthenticatedError: Bool {
return errorCode == -1013
}
var isGoingOverQuotaError: Bool {
return errorCode == 507
}
var isNotFoundError: Bool {
return errorCode == 404
}
var isNoChangesError: Bool {
return errorCode == NKError.noChangesErrorCode
}
var fileProviderError: NSFileProviderError {
if isNotFoundError {
return NSFileProviderError(.noSuchItem)
} else if isCouldntConnectError {
// Provide something the file provider can do something with
return NSFileProviderError(.serverUnreachable)
} else if isUnauthenticatedError {
return NSFileProviderError(.notAuthenticated)
} else if isGoingOverQuotaError {
return NSFileProviderError(.insufficientQuota)
} else {
return NSFileProviderError(.cannotSynchronize)
}
}
}

View File

@ -1,57 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import Alamofire
extension Progress {
func setHandlersFromAfRequest(_ request: Request) {
self.cancellationHandler = { request.cancel() }
self.pausingHandler = { request.suspend() }
self.resumingHandler = { request.resume() }
}
func copyCurrentStateToProgress(_ otherProgress: Progress, includeHandlers: Bool = false) {
if includeHandlers {
otherProgress.cancellationHandler = self.cancellationHandler
otherProgress.pausingHandler = self.pausingHandler
otherProgress.resumingHandler = self.resumingHandler
}
otherProgress.totalUnitCount = self.totalUnitCount
otherProgress.completedUnitCount = self.completedUnitCount
otherProgress.estimatedTimeRemaining = self.estimatedTimeRemaining
otherProgress.localizedDescription = self.localizedAdditionalDescription
otherProgress.localizedAdditionalDescription = self.localizedAdditionalDescription
otherProgress.isCancellable = self.isCancellable
otherProgress.isPausable = self.isPausable
otherProgress.fileCompletedCount = self.fileCompletedCount
otherProgress.fileURL = self.fileURL
otherProgress.fileTotalCount = self.fileTotalCount
otherProgress.fileCompletedCount = self.fileCompletedCount
otherProgress.fileOperationKind = self.fileOperationKind
otherProgress.kind = self.kind
otherProgress.throughput = self.throughput
for (key, object) in self.userInfo {
otherProgress.setUserInfoObject(object, forKey: key)
}
}
func copyOfCurrentState(includeHandlers: Bool = false) -> Progress {
let newProgress = Progress()
copyCurrentStateToProgress(newProgress, includeHandlers: includeHandlers)
return newProgress
}
}

View File

@ -0,0 +1,40 @@
//
// FileProviderConfig.swift
// FileProviderExt
//
// Created by Claudio Cambra on 5/2/24.
//
import FileProvider
import Foundation
struct FileProviderConfig {
private enum ConfigKey: String {
case fastEnumerationEnabled = "fastEnumerationEnabled"
}
let domainIdentifier: NSFileProviderDomainIdentifier
private var internalConfig: [String: Any] {
get {
let defaults = UserDefaults.standard
if let settings = defaults.dictionary(forKey: domainIdentifier.rawValue) {
return settings
}
let dictionary: [String: Any] = [:]
defaults.setValue(dictionary, forKey: domainIdentifier.rawValue)
return dictionary
}
set {
let defaults = UserDefaults.standard
defaults.setValue(newValue, forKey: domainIdentifier.rawValue)
}
}
var fastEnumerationEnabled: Bool {
get { internalConfig[ConfigKey.fastEnumerationEnabled.rawValue] as? Bool ?? true }
set { internalConfig[ConfigKey.fastEnumerationEnabled.rawValue] = newValue }
}
lazy var fastEnumerationSet = internalConfig[ConfigKey.fastEnumerationEnabled.rawValue] != nil
}

View File

@ -1,313 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 FileProvider
import NextcloudKit
import OSLog
extension FileProviderEnumerator {
func fullRecursiveScan(ncAccount: NextcloudAccount,
ncKit: NextcloudKit,
scanChangesOnly: Bool,
completionHandler: @escaping(_ metadatas: [NextcloudItemMetadataTable],
_ newMetadatas: [NextcloudItemMetadataTable],
_ updatedMetadatas: [NextcloudItemMetadataTable],
_ deletedMetadatas: [NextcloudItemMetadataTable],
_ error: NKError?) -> Void) {
let rootContainerDirectoryMetadata = NextcloudItemMetadataTable()
rootContainerDirectoryMetadata.directory = true
rootContainerDirectoryMetadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue
// Create a serial dispatch queue
let dispatchQueue = DispatchQueue(label: "recursiveChangeEnumerationQueue", qos: .userInitiated)
dispatchQueue.async {
let results = self.scanRecursively(rootContainerDirectoryMetadata,
ncAccount: ncAccount,
ncKit: ncKit,
scanChangesOnly: scanChangesOnly)
// Run a check to ensure files deleted in one location are not updated in another (e.g. when moved)
// The recursive scan provides us with updated/deleted metadatas only on a folder by folder basis;
// so we need to check we are not simultaneously marking a moved file as deleted and updated
var checkedDeletedMetadatas = results.deletedMetadatas
for updatedMetadata in results.updatedMetadatas {
guard let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: { $0.ocId == updatedMetadata.ocId } ) else {
continue;
}
checkedDeletedMetadatas.remove(at: matchingDeletedMetadataIdx)
}
DispatchQueue.main.async {
completionHandler(results.metadatas, results.newMetadatas, results.updatedMetadatas, checkedDeletedMetadatas, results.error)
}
}
}
private func scanRecursively(_ directoryMetadata: NextcloudItemMetadataTable,
ncAccount: NextcloudAccount,
ncKit: NextcloudKit,
scanChangesOnly: Bool) -> (metadatas: [NextcloudItemMetadataTable],
newMetadatas: [NextcloudItemMetadataTable],
updatedMetadatas: [NextcloudItemMetadataTable],
deletedMetadatas: [NextcloudItemMetadataTable],
error: NKError?) {
if self.isInvalidated {
return ([], [], [], [], nil)
}
assert(directoryMetadata.directory, "Can only recursively scan a directory.")
// Will include results of recursive calls
var allMetadatas: [NextcloudItemMetadataTable] = []
var allNewMetadatas: [NextcloudItemMetadataTable] = []
var allUpdatedMetadatas: [NextcloudItemMetadataTable] = []
var allDeletedMetadatas: [NextcloudItemMetadataTable] = []
let dbManager = NextcloudFilesDatabaseManager.shared
let dispatchGroup = DispatchGroup() // TODO: Maybe own thread?
dispatchGroup.enter()
var criticalError: NKError?
let itemServerUrl = directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue ?
ncAccount.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
Logger.enumeration.debug("About to read: \(itemServerUrl, privacy: .public)")
FileProviderEnumerator.readServerUrl(itemServerUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: scanChangesOnly) { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError in
if readError != nil {
let nkReadError = NKError(error: readError!)
// Is the error is that we have found matching etags on this item, then ignore it
// if we are doing a full rescan
guard nkReadError.isNoChangesError && scanChangesOnly else {
Logger.enumeration.error("Finishing enumeration of changes at \(itemServerUrl, privacy: .public) with \(readError!.localizedDescription, privacy: .public)")
if nkReadError.isNotFoundError {
Logger.enumeration.info("404 error means item no longer exists. Deleting metadata and reporting as deletion without error")
if let deletedMetadatas = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: directoryMetadata.ocId) {
allDeletedMetadatas += deletedMetadatas
} else {
Logger.enumeration.error("An error occurred while trying to delete directory and children not found in recursive scan")
}
} else if nkReadError.isNoChangesError { // All is well, just no changed etags
Logger.enumeration.info("Error was to say no changed files -- not bad error. No need to check children.")
} else if nkReadError.isUnauthenticatedError || nkReadError.isCouldntConnectError {
// If it is a critical error then stop, if not then continue
Logger.enumeration.error("Error will affect next enumerated items, so stopping enumeration.")
criticalError = nkReadError
}
dispatchGroup.leave()
return
}
}
Logger.enumeration.info("Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
if let metadatas = metadatas {
allMetadatas += metadatas
} else {
Logger.enumeration.warning("WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
}
if let newMetadatas = newMetadatas {
allNewMetadatas += newMetadatas
} else {
Logger.enumeration.warning("WARNING: Nil new metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
}
if let updatedMetadatas = updatedMetadatas {
allUpdatedMetadatas += updatedMetadatas
} else {
Logger.enumeration.warning("WARNING: Nil updated metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
}
if let deletedMetadatas = deletedMetadatas {
allDeletedMetadatas += deletedMetadatas
} else {
Logger.enumeration.warning("WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
}
dispatchGroup.leave()
}
dispatchGroup.wait()
guard criticalError == nil else {
return ([], [], [], [], error: criticalError)
}
var childDirectoriesToScan: [NextcloudItemMetadataTable] = []
var candidateMetadatas: [NextcloudItemMetadataTable]
if scanChangesOnly {
candidateMetadatas = allUpdatedMetadatas + allNewMetadatas
} else {
candidateMetadatas = allMetadatas
}
for candidateMetadata in candidateMetadatas {
if candidateMetadata.directory {
childDirectoriesToScan.append(candidateMetadata)
}
}
if childDirectoriesToScan.isEmpty {
return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil)
}
for childDirectory in childDirectoriesToScan {
let childScanResult = scanRecursively(childDirectory,
ncAccount: ncAccount,
ncKit: ncKit,
scanChangesOnly: scanChangesOnly)
allMetadatas += childScanResult.metadatas
allNewMetadatas += childScanResult.newMetadatas
allUpdatedMetadatas += childScanResult.updatedMetadatas
allDeletedMetadatas += childScanResult.deletedMetadatas
}
return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil)
}
static func handleDepth1ReadFileOrFolder(serverUrl: String,
ncAccount: NextcloudAccount,
files: [NKFile],
error: NKError,
completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?,
_ newMetadatas: [NextcloudItemMetadataTable]?,
_ updatedMetadatas: [NextcloudItemMetadataTable]?,
_ deletedMetadatas: [NextcloudItemMetadataTable]?,
_ readError: Error?) -> Void) {
guard error == .success else {
Logger.enumeration.error("1 depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)")
completionHandler(nil, nil, nil, nil, error.error)
return
}
Logger.enumeration.debug("Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
let dbManager = NextcloudFilesDatabaseManager.shared
DispatchQueue.global(qos: .userInitiated).async {
NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles(files, account: ncAccount.ncKitAccount) { directoryMetadata, childDirectoriesMetadata, metadatas in
// STORE DATA FOR CURRENTLY SCANNED DIRECTORY
// We have now scanned this directory's contents, so update with etag in order to not check again if not needed
// unless it's the root container
if serverUrl != ncAccount.davFilesUrl {
dbManager.addItemMetadata(directoryMetadata)
}
// Don't update the etags for folders as we haven't checked their contents.
// When we do a recursive check, if we update the etags now, we will think
// that our local copies are up to date -- instead, leave them as the old.
// They will get updated when they are the subject of a readServerUrl call.
// (See above)
let changedMetadatas = dbManager.updateItemMetadatas(account: ncAccount.ncKitAccount, serverUrl: serverUrl, updatedMetadatas: metadatas, updateDirectoryEtags: false)
DispatchQueue.main.async {
completionHandler(metadatas, changedMetadatas.newMetadatas, changedMetadatas.updatedMetadatas, changedMetadatas.deletedMetadatas, nil)
}
}
}
}
static func readServerUrl(_ serverUrl: String,
ncAccount: NextcloudAccount,
ncKit: NextcloudKit,
stopAtMatchingEtags: Bool = false,
depth: String = "1",
completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?,
_ newMetadatas: [NextcloudItemMetadataTable]?,
_ updatedMetadatas: [NextcloudItemMetadataTable]?,
_ deletedMetadatas: [NextcloudItemMetadataTable]?,
_ readError: Error?) -> Void) {
let dbManager = NextcloudFilesDatabaseManager.shared
let ncKitAccount = ncAccount.ncKitAccount
Logger.enumeration.debug("Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public) at depth \(depth, privacy: .public). NCKit info: userId: \(ncKit.nkCommonInstance.user, privacy: .public), password is empty: \(ncKit.nkCommonInstance.password == "" ? "EMPTY PASSWORD" : "NOT EMPTY PASSWORD"), urlBase: \(ncKit.nkCommonInstance.urlBase, privacy: .public), ncVersion: \(ncKit.nkCommonInstance.nextcloudVersion, privacy: .public)")
ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: depth, showHiddenFiles: true) { _, files, _, error in
guard error == .success else {
Logger.enumeration.error("\(depth, privacy: .public) depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)")
completionHandler(nil, nil, nil, nil, error.error)
return
}
guard let receivedFile = files.first else {
Logger.enumeration.error("Received no items from readFileOrFolder of \(serverUrl, privacy: .public), not much we can do...")
completionHandler(nil, nil, nil, nil, error.error)
return
}
guard receivedFile.directory else {
Logger.enumeration.debug("Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
let itemMetadata = NextcloudItemMetadataTable.fromNKFile(receivedFile, account: ncKitAccount)
dbManager.addItemMetadata(itemMetadata) // TODO: Return some value when it is an update
completionHandler([itemMetadata], nil, nil, nil, error.error)
return
}
if stopAtMatchingEtags,
let directoryMetadata = dbManager.directoryMetadata(account: ncKitAccount, serverUrl: serverUrl) {
let directoryEtag = directoryMetadata.etag
guard directoryEtag == "" || directoryEtag != receivedFile.etag else {
Logger.enumeration.debug("Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error.")
let description = "Fetched directory etag is same as that stored locally. Not fetching child items."
let nkError = NKError(errorCode: NKError.noChangesErrorCode, errorDescription: description)
let metadatas = dbManager.itemMetadatas(account: ncKitAccount, serverUrl: serverUrl)
completionHandler(metadatas, nil, nil, nil, nkError.error)
return
}
}
if depth == "0" {
if serverUrl != ncAccount.davFilesUrl {
let metadata = NextcloudItemMetadataTable.fromNKFile(receivedFile, account: ncKitAccount)
let isNew = dbManager.itemMetadataFromOcId(metadata.ocId) == nil
let updatedMetadatas = isNew ? [] : [metadata]
let newMetadatas = isNew ? [metadata] : []
dbManager.addItemMetadata(metadata)
DispatchQueue.main.async {
completionHandler([metadata], newMetadatas, updatedMetadatas, nil, nil)
}
}
} else {
handleDepth1ReadFileOrFolder(serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error, completionHandler: completionHandler)
}
}
}
}

View File

@ -1,352 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 FileProvider
import NextcloudKit
import OSLog
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
private let enumeratedItemIdentifier: NSFileProviderItemIdentifier
private var enumeratedItemMetadata: NextcloudItemMetadataTable?
private var enumeratingSystemIdentifier: Bool {
return FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier)
}
private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests
private static let maxItemsPerFileProviderPage = 100
let ncAccount: NextcloudAccount
let ncKit: NextcloudKit
var serverUrl: String = ""
var isInvalidated = false
private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool {
return identifier == .rootContainer ||
identifier == .trashContainer ||
identifier == .workingSet
}
init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, ncKit: NextcloudKit) {
self.enumeratedItemIdentifier = enumeratedItemIdentifier
self.ncAccount = ncAccount
self.ncKit = ncKit
if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) {
Logger.enumeration.debug("Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)")
self.serverUrl = ncAccount.davFilesUrl
} else {
Logger.enumeration.debug("Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)")
let dbManager = NextcloudFilesDatabaseManager.shared
enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(enumeratedItemIdentifier)
if enumeratedItemMetadata != nil {
self.serverUrl = enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName
} else {
Logger.enumeration.error("Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)")
}
}
Logger.enumeration.info("Set up enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
super.init()
}
func invalidate() {
Logger.enumeration.debug("Enumerator is being invalidated for item with identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)")
self.isInvalidated = true
}
// MARK: - Protocol methods
func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
Logger.enumeration.debug("Received enumerate items request for enumerator with user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
/*
- inspect the page to determine whether this is an initial or a follow-up request (TODO)
If this is an enumerator for a directory, the root container or all directories:
- perform a server request to fetch directory contents
If this is an enumerator for the working set:
- perform a server request to update your local database
- fetch the working set from your local database
- inform the observer about the items returned by the server (possibly multiple times)
- inform the observer that you are finished with this page
*/
if enumeratedItemIdentifier == .trashContainer {
Logger.enumeration.debug("Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
// TODO!
observer.finishEnumerating(upTo: nil)
return
}
// Handle the working set as if it were the root container
// If we do a full server scan per the recommendations of the File Provider documentation,
// we will be stuck for a huge period of time without being able to access files as the
// entire server gets scanned. Instead, treat the working set as the root container here.
// Then, when we enumerate changes, we'll go through everything -- while we can still
// navigate a little bit in Finder, file picker, etc
guard serverUrl != "" else {
Logger.enumeration.error("Enumerator has empty serverUrl -- can't enumerate that! For identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)")
observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem))
return
}
// TODO: Make better use of pagination and handle paging properly
if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage ||
page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage {
Logger.enumeration.debug("Enumerating initial page for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) { metadatas, _, _, _, readError in
guard readError == nil else {
Logger.enumeration.error("Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.localizedDescription, privacy: .public)")
let nkReadError = NKError(error: readError!)
observer.finishEnumeratingWithError(nkReadError.fileProviderError)
return
}
guard let metadatas = metadatas else {
Logger.enumeration.error("Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas.")
observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize))
return
}
Logger.enumeration.info("Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas")
FileProviderEnumerator.completeEnumerationObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas)
}
return;
}
let numPage = Int(String(data: page.rawValue, encoding: .utf8)!)!
Logger.enumeration.debug("Enumerating page \(numPage, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
// TODO: Handle paging properly
// FileProviderEnumerator.completeObserver(observer, ncKit: ncKit, numPage: numPage, itemMetadatas: nil)
observer.finishEnumerating(upTo: nil)
}
func enumerateChanges(for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor) {
Logger.enumeration.debug("Received enumerate changes request for enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
/*
- query the server for updates since the passed-in sync anchor (TODO)
If this is an enumerator for the working set:
- note the changes in your local database
- inform the observer about item deletions and updates (modifications + insertions)
- inform the observer when you have finished enumerating up to a subsequent sync anchor
*/
if enumeratedItemIdentifier == .workingSet {
Logger.enumeration.debug("Enumerating changes in working set for user: \(self.ncAccount.ncKitAccount, privacy: .public)")
// Unlike when enumerating items we can't progressively enumerate items as we need to wait to resolve which items are truly deleted and which
// have just been moved elsewhere.
fullRecursiveScan(ncAccount: self.ncAccount,
ncKit: self.ncKit,
scanChangesOnly: true) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in
if self.isInvalidated {
Logger.enumeration.info("Enumerator invalidated during working set change scan. For user: \(self.ncAccount.ncKitAccount, privacy: .public)")
observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize))
return
}
guard error == nil else {
Logger.enumeration.info("Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public)")
observer.finishEnumeratingWithError(error!.fileProviderError)
return
}
Logger.enumeration.info("Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public). Enumerating items.")
FileProviderEnumerator.completeChangesObserver(observer,
anchor: anchor,
ncKit: self.ncKit,
newMetadatas: newMetadatas,
updatedMetadatas: updatedMetadatas,
deletedMetadatas: deletedMetadatas)
}
return
} else if enumeratedItemIdentifier == .trashContainer {
Logger.enumeration.debug("Enumerating changes in trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public)")
// TODO!
observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
return
}
Logger.enumeration.info("Enumerating changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)")
// No matter what happens here we finish enumeration in some way, either from the error
// handling below or from the completeChangesObserver
// TODO: Move to the sync engine extension
FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: true) { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError in
// If we get a 404 we might add more deleted metadatas
var currentDeletedMetadatas: [NextcloudItemMetadataTable] = []
if let notNilDeletedMetadatas = deletedMetadatas {
currentDeletedMetadatas = notNilDeletedMetadatas
}
guard readError == nil else {
Logger.enumeration.error("Finishing enumeration of changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.localizedDescription, privacy: .public)")
let nkReadError = NKError(error: readError!)
let fpError = nkReadError.fileProviderError
if nkReadError.isNotFoundError {
Logger.enumeration.info("404 error means item no longer exists. Deleting metadata and reporting \(self.serverUrl, privacy: .public) as deletion without error")
guard let itemMetadata = self.enumeratedItemMetadata else {
Logger.enumeration.error("Invalid enumeratedItemMetadata, could not delete metadata nor report deletion")
observer.finishEnumeratingWithError(fpError)
return
}
let dbManager = NextcloudFilesDatabaseManager.shared
if itemMetadata.directory {
if let deletedDirectoryMetadatas = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: itemMetadata.ocId) {
currentDeletedMetadatas += deletedDirectoryMetadatas
} else {
Logger.enumeration.error("Something went wrong when recursively deleting directory not found.")
}
} else {
dbManager.deleteItemMetadata(ocId: itemMetadata.ocId)
}
FileProviderEnumerator.completeChangesObserver(observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: nil, updatedMetadatas: nil, deletedMetadatas: [itemMetadata])
return
} else if nkReadError.isNoChangesError { // All is well, just no changed etags
Logger.enumeration.info("Error was to say no changed files -- not bad error. Finishing change enumeration.")
observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
return;
}
observer.finishEnumeratingWithError(fpError)
return
}
Logger.enumeration.info("Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)")
FileProviderEnumerator.completeChangesObserver(observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: newMetadatas, updatedMetadatas: updatedMetadatas, deletedMetadatas: deletedMetadatas)
}
}
func currentSyncAnchor(completionHandler: @escaping (NSFileProviderSyncAnchor?) -> Void) {
completionHandler(anchor)
}
// MARK: - Helper methods
private static func metadatasToFileProviderItems(_ itemMetadatas: [NextcloudItemMetadataTable], ncKit: NextcloudKit, completionHandler: @escaping(_ items: [NSFileProviderItem]) -> Void) {
var items: [NSFileProviderItem] = []
let conversionQueue = DispatchQueue(label: "metadataToItemConversionQueue", qos: .userInitiated, attributes: .concurrent)
let appendQueue = DispatchQueue(label: "enumeratorItemAppendQueue", qos: .userInitiated) // Serial queue
let dispatchGroup = DispatchGroup()
for itemMetadata in itemMetadatas {
conversionQueue.async(group: dispatchGroup) {
if itemMetadata.e2eEncrypted {
Logger.enumeration.info("Skipping encrypted metadata in enumeration: \(itemMetadata.ocId, privacy: .public) \(itemMetadata.fileName, privacy: .public)")
return
}
if let parentItemIdentifier = NextcloudFilesDatabaseManager.shared.parentItemIdentifierFromMetadata(itemMetadata) {
let item = FileProviderItem(metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit)
Logger.enumeration.debug("Will enumerate item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public)")
appendQueue.async(group: dispatchGroup) {
items.append(item)
}
} else {
Logger.enumeration.error("Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration")
}
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
completionHandler(items)
}
}
private static func fileProviderPageforNumPage(_ numPage: Int) -> NSFileProviderPage {
return NSFileProviderPage("\(numPage)".data(using: .utf8)!)
}
private static func completeEnumerationObserver(_ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, itemMetadatas: [NextcloudItemMetadataTable]) {
metadatasToFileProviderItems(itemMetadatas, ncKit: ncKit) { items in
observer.didEnumerate(items)
Logger.enumeration.info("Did enumerate \(items.count) items")
// TODO: Handle paging properly
/*
if items.count == maxItemsPerFileProviderPage {
let nextPage = numPage + 1
let providerPage = NSFileProviderPage("\(nextPage)".data(using: .utf8)!)
observer.finishEnumerating(upTo: providerPage)
} else {
observer.finishEnumerating(upTo: nil)
}
*/
observer.finishEnumerating(upTo: fileProviderPageforNumPage(numPage))
}
}
private static func completeChangesObserver(_ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, ncKit: NextcloudKit, newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) {
guard newMetadatas != nil || updatedMetadatas != nil || deletedMetadatas != nil else {
Logger.enumeration.error("Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error.")
observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem))
return
}
// Observer does not care about new vs updated, so join
var allUpdatedMetadatas: [NextcloudItemMetadataTable] = []
var allDeletedMetadatas: [NextcloudItemMetadataTable] = []
if let newMetadatas = newMetadatas {
allUpdatedMetadatas += newMetadatas
}
if let updatedMetadatas = updatedMetadatas {
allUpdatedMetadatas += updatedMetadatas
}
if let deletedMetadatas = deletedMetadatas {
allDeletedMetadatas = deletedMetadatas
}
let allFpItemDeletionsIdentifiers = Array(allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) })
if !allFpItemDeletionsIdentifiers.isEmpty {
observer.didDeleteItems(withIdentifiers: allFpItemDeletionsIdentifiers)
}
metadatasToFileProviderItems(allUpdatedMetadatas, ncKit: ncKit) { updatedItems in
if !updatedItems.isEmpty {
observer.didUpdate(updatedItems)
}
Logger.enumeration.info("Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas.")
observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
}
}
}

View File

@ -12,18 +12,9 @@
* for more details.
*/
import Foundation
import RealmSwift
#ifndef FileProviderExt_Bridging_Header_h
#define FileProviderExt_Bridging_Header_h
class NextcloudLocalFileMetadataTable: Object {
@Persisted(primaryKey: true) var ocId: String
@Persisted var account = ""
@Persisted var etag = ""
@Persisted var exifDate: Date?
@Persisted var exifLatitude = ""
@Persisted var exifLongitude = ""
@Persisted var exifLensModel: String?
@Persisted var favorite: Bool = false
@Persisted var fileName = ""
@Persisted var offline: Bool = false
}
#import "Services/ClientCommunicationProtocol.h"
#endif /* FileProviderExt_Bridging_Header_h */

View File

@ -12,14 +12,46 @@
* for more details.
*/
import Foundation
import FileProvider
import OSLog
import Foundation
import NCDesktopClientSocketKit
import NextcloudKit
import NextcloudFileProviderKit
import OSLog
extension FileProviderExtension {
func sendFileProviderDomainIdentifier() {
extension FileProviderExtension: NSFileProviderServicing {
/*
This FileProviderExtension extension contains everything needed to communicate with the client.
We have two systems for communicating between the extensions and the client.
Apple's XPC based File Provider APIs let us easily communicate client -> extension.
This is what ClientCommunicationService is for.
We also use sockets, because the File Provider XPC system does not let us easily talk from
extension->client.
We need this because the extension needs to be able to request account details. We can't
reliably do this via XPC because the extensions get torn down by the system, out of the control
of the app, and we can receive nil/no services from NSFileProviderManager. Once this is done
then XPC works ok.
*/
func supportedServiceSources(
for itemIdentifier: NSFileProviderItemIdentifier,
completionHandler: @escaping ([NSFileProviderServiceSource]?, Error?) -> Void
) -> Progress {
Logger.desktopClientConnection.debug("Serving supported service sources")
let clientCommService = ClientCommunicationService(fpExtension: self)
let fpuiExtService = FPUIExtensionServiceSource(fpExtension: self)
let services: [NSFileProviderServiceSource] = [clientCommService, fpuiExtService]
completionHandler(services, nil)
let progress = Progress()
progress.cancellationHandler = {
let error = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError)
completionHandler(nil, error)
}
return progress
}
@objc func sendFileProviderDomainIdentifier() {
let command = "FILE_PROVIDER_DOMAIN_IDENTIFIER_REQUEST_REPLY"
let argument = domain.identifier.rawValue
let message = command + ":" + argument + "\n"
@ -28,7 +60,9 @@ extension FileProviderExtension {
private func signalEnumeratorAfterAccountSetup() {
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup")
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup"
)
return
}
@ -36,36 +70,53 @@ extension FileProviderExtension {
fpManager.signalErrorResolved(NSFileProviderError(.notAuthenticated)) { error in
if error != nil {
Logger.fileProviderExtension.error("Error resolving not authenticated, received error: \(error!.localizedDescription)")
Logger.fileProviderExtension.error(
"Error resolving not authenticated, received error: \(error!.localizedDescription)"
)
}
}
Logger.fileProviderExtension.debug("Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)")
Logger.fileProviderExtension.debug(
"Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)"
)
fpManager.signalEnumerator(for: .workingSet) { error in
if error != nil {
Logger.fileProviderExtension.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)")
Logger.fileProviderExtension.error(
"Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)"
)
}
}
}
func setupDomainAccount(user: String, serverUrl: String, password: String) {
ncAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password)
ncKit.setup(user: ncAccount!.username,
userId: ncAccount!.username,
password: ncAccount!.password,
urlBase: ncAccount!.serverUrl,
userAgent: "Nextcloud-macOS/FileProviderExt",
nextcloudVersion: 25,
delegate: nil) // TODO: add delegate methods for self
@objc func setupDomainAccount(user: String, serverUrl: String, password: String) {
let newNcAccount = Account(user: user, serverUrl: serverUrl, password: password)
guard newNcAccount != ncAccount else { return }
ncAccount = newNcAccount
ncKit.setup(
account: newNcAccount.ncKitAccount,
user: newNcAccount.username,
userId: newNcAccount.username,
password: newNcAccount.password,
urlBase: newNcAccount.serverUrl,
userAgent: "Nextcloud-macOS/FileProviderExt",
nextcloudVersion: 25,
delegate: nil) // TODO: add delegate methods for self
Logger.fileProviderExtension.info("Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)")
changeObserver = RemoteChangeObserver(ncKit: ncKit, domain: domain)
ncKit.setup(delegate: changeObserver)
Logger.fileProviderExtension.info(
"Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)"
)
signalEnumeratorAfterAccountSetup()
}
func removeAccountConfig() {
Logger.fileProviderExtension.info("Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)")
@objc func removeAccountConfig() {
Logger.fileProviderExtension.info(
"Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)"
)
ncAccount = nil
}
}

View File

@ -12,51 +12,29 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
import NextcloudKit
import NextcloudFileProviderKit
import OSLog
extension FileProviderExtension: NSFileProviderThumbnailing {
func fetchThumbnails(for itemIdentifiers: [NSFileProviderItemIdentifier],
requestedSize size: CGSize,
perThumbnailCompletionHandler: @escaping (NSFileProviderItemIdentifier,
Data?,
Error?) -> Void,
completionHandler: @escaping (Error?) -> Void) -> Progress {
let progress = Progress(totalUnitCount: Int64(itemIdentifiers.count))
var progressCounter: Int64 = 0
func finishCurrent() {
progressCounter += 1
if progressCounter == progress.totalUnitCount {
completionHandler(nil)
}
}
for itemIdentifier in itemIdentifiers {
Logger.fileProviderExtension.debug("Fetching thumbnail for item with identifier:\(itemIdentifier.rawValue, privacy: .public)")
guard let metadata = NextcloudFilesDatabaseManager.shared.itemMetadataFromFileProviderItemIdentifier(itemIdentifier),
let thumbnailUrl = metadata.thumbnailUrl(size: size) else {
Logger.fileProviderExtension.debug("Did not fetch thumbnail URL")
finishCurrent()
continue
}
Logger.fileProviderExtension.debug("Fetching thumbnail for file:\(metadata.fileName) at:\(thumbnailUrl.absoluteString, privacy: .public)")
self.ncKit.getPreview(url: thumbnailUrl) { _, data, error in
if error == .success && data != nil {
perThumbnailCompletionHandler(itemIdentifier, data, nil)
} else {
perThumbnailCompletionHandler(itemIdentifier, nil, NSFileProviderError(.serverUnreachable))
}
finishCurrent()
}
}
return progress
func fetchThumbnails(
for itemIdentifiers: [NSFileProviderItemIdentifier],
requestedSize size: CGSize,
perThumbnailCompletionHandler: @escaping (
NSFileProviderItemIdentifier,
Data?,
Error?
) -> Void,
completionHandler: @escaping (Error?) -> Void
) -> Progress {
return NextcloudFileProviderKit.fetchThumbnails(
for: itemIdentifiers,
requestedSize: size,
usingKit: self.ncKit,
perThumbnailCompletionHandler: perThumbnailCompletionHandler,
completionHandler: completionHandler
)
}
}

View File

@ -13,33 +13,31 @@
*/
import FileProvider
import OSLog
import NCDesktopClientSocketKit
import NextcloudKit
import NextcloudFileProviderKit
import OSLog
class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate {
@objc class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate {
let domain: NSFileProviderDomain
let ncKit = NextcloudKit()
lazy var ncKitBackground: NKBackground = {
let nckb = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)
return nckb
}()
let appGroupIdentifier: String? = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String
var ncAccount: NextcloudAccount?
let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String
var ncAccount: Account?
var changeObserver: RemoteChangeObserver?
lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)
lazy var socketClient: LocalSocketClient? = {
guard let containerUrl = pathForAppGroupContainer() else {
Logger.fileProviderExtension.critical("Could not start file provider socket client properly as could not get container url")
Logger.fileProviderExtension.critical("Won't start socket client, no container url")
return nil;
}
let socketPath = containerUrl.appendingPathComponent(".fileprovidersocket", conformingTo: .archive)
let socketPath = containerUrl.appendingPathComponent(
".fileprovidersocket", conformingTo: .archive)
let lineProcessor = FileProviderSocketLineProcessor(delegate: self)
return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor)
}()
let urlSessionIdentifier: String = "com.nextcloud.session.upload.fileproviderext"
let urlSessionIdentifier = "com.nextcloud.session.upload.fileproviderext"
let urlSessionMaximumConnectionsPerHost = 5
lazy var urlSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: urlSessionIdentifier)
@ -50,571 +48,313 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
configuration.sharedContainerIdentifier = appGroupIdentifier
let session = URLSession(configuration: configuration, delegate: ncKitBackground, delegateQueue: OperationQueue.main)
let session = URLSession(
configuration: configuration,
delegate: ncKitBackground,
delegateQueue: OperationQueue.main
)
return session
}()
required init(domain: NSFileProviderDomain) {
self.domain = domain
// The containing application must create a domain using `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the application extension process, call `FileProviderExtension.init(domain:)` to instantiate the extension for that domain, and call methods on the instance.
// Whether or not we are going to recursively scan new folders when they are discovered.
// Apple's recommendation is that we should always scan the file hierarchy fully.
// This does lead to long load times when a file provider domain is initially configured.
// We can instead do a fast enumeration where we only scan folders as the user navigates through
// them, thereby avoiding this issue; the trade-off is that we will be unable to detect
// materialised file moves to unexplored folders, therefore deleting the item when we could have
// just moved it instead.
//
// Since it's not desirable to cancel a long recursive enumeration half-way through, we do the
// fast enumeration by default. We prompt the user on the client side to run a proper, full
// enumeration if they want for safety.
lazy var config = FileProviderConfig(domainIdentifier: domain.identifier)
required init(domain: NSFileProviderDomain) {
// The containing application must create a domain using
// `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the
// application extension process, call `FileProviderExtension.init(domain:)` to instantiate
// the extension for that domain, and call methods on the instance.
self.domain = domain
super.init()
self.socketClient?.start()
socketClient?.start()
}
func invalidate() {
// TODO: cleanup any resources
Logger.fileProviderExtension.debug("Extension for domain \(self.domain.displayName, privacy: .public) is being torn down")
Logger.fileProviderExtension.debug(
"Extension for domain \(self.domain.displayName, privacy: .public) is being torn down"
)
}
// MARK: NSFileProviderReplicatedExtension protocol methods
func item(for identifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) -> Progress {
// resolve the given identifier to a record in the model
// MARK: - NSFileProviderReplicatedExtension protocol methods
Logger.fileProviderExtension.debug("Received item request for item with identifier: \(identifier.rawValue, privacy: .public)")
if identifier == .rootContainer {
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not providing item: \(identifier.rawValue, privacy: .public) as account not set up yet")
completionHandler(nil, NSFileProviderError(.notAuthenticated))
return Progress()
}
let metadata = NextcloudItemMetadataTable()
metadata.account = ncAccount.ncKitAccount
metadata.directory = true
metadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue
metadata.fileName = "root"
metadata.fileNameView = "root"
metadata.serverUrl = ncAccount.serverUrl
metadata.classFile = NKCommon.TypeClassFile.directory.rawValue
completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: NSFileProviderItemIdentifier.rootContainer, ncKit: ncKit), nil)
return Progress()
}
let dbManager = NextcloudFilesDatabaseManager.shared
guard let metadata = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier),
let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(metadata) else {
func item(
for identifier: NSFileProviderItemIdentifier,
request _: NSFileProviderRequest,
completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void
) -> Progress {
if let item = Item.storedItem(identifier: identifier, usingKit: ncKit) {
completionHandler(item, nil)
} else {
completionHandler(nil, NSFileProviderError(.noSuchItem))
return Progress()
}
completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil)
return Progress()
}
func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier, version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress {
Logger.fileProviderExtension.debug("Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)")
func fetchContents(
for itemIdentifier: NSFileProviderItemIdentifier,
version requestedVersion: NSFileProviderItemVersion?,
request: NSFileProviderRequest,
completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void
) -> Progress {
Logger.fileProviderExtension.debug(
"Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)"
)
guard requestedVersion == nil else {
// TODO: Add proper support for file versioning
Logger.fileProviderExtension.error("Can't return contents for specific version as this is not supported.")
completionHandler(nil, nil, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:]))
Logger.fileProviderExtension.error(
"Can't return contents for a specific version as this is not supported."
)
completionHandler(
nil,
nil,
NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError)
)
return Progress()
}
guard ncAccount != nil else {
Logger.fileProviderExtension.error("Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet")
Logger.fileProviderExtension.error(
"""
Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public)
as account not set up yet.
"""
)
completionHandler(nil, nil, NSFileProviderError(.notAuthenticated))
return Progress()
}
let dbManager = NextcloudFilesDatabaseManager.shared
let ocId = itemIdentifier.rawValue
guard let metadata = dbManager.itemMetadataFromOcId(ocId) else {
Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)")
guard let item = Item.storedItem(identifier: itemIdentifier, usingKit: ncKit) else {
Logger.fileProviderExtension.error(
"""
Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public)
as item not found.
"""
)
completionHandler(nil, nil, NSFileProviderError(.noSuchItem))
return Progress()
}
guard !metadata.isDocumentViewableOnly else {
Logger.fileProviderExtension.error("Could not get contents of item as is readonly: \(itemIdentifier.rawValue, privacy: .public) \(metadata.fileName, privacy: .public)")
completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize))
return Progress()
}
let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
Logger.fileProviderExtension.debug("Fetching file with name \(metadata.fileName, privacy: .public) at URL: \(serverUrlFileName, privacy: .public)")
let progress = Progress()
// TODO: Handle folders nicely
do {
let fileNameLocalPath = try localPathForNCFile(ocId: metadata.ocId, fileNameView: metadata.fileNameView, domain: self.domain)
dbManager.setStatusForItemMetadata(metadata, status: NextcloudItemMetadataTable.Status.downloading) { updatedMetadata in
guard let updatedMetadata = updatedMetadata else {
Logger.fileProviderExtension.error("Could not acquire updated metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public), unable to update item status to downloading")
completionHandler(nil, nil, NSFileProviderError(.noSuchItem))
return
}
self.ncKit.download(serverUrlFileName: serverUrlFileName,
fileNameLocalPath: fileNameLocalPath.path,
requestHandler: { request in
progress.setHandlersFromAfRequest(request)
}, taskHandler: { task in
NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: itemIdentifier, completionHandler: { _ in })
}, progressHandler: { downloadProgress in
downloadProgress.copyCurrentStateToProgress(progress)
}) { _, etag, date, _, _, _, error in
if error == .success {
Logger.fileTransfer.debug("Acquired contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and filename: \(updatedMetadata.fileName, privacy: .public)")
updatedMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue
updatedMetadata.sessionError = ""
updatedMetadata.date = (date ?? NSDate()) as Date
updatedMetadata.etag = etag ?? ""
dbManager.addLocalFileMetadataFromItemMetadata(updatedMetadata)
dbManager.addItemMetadata(updatedMetadata)
guard let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(updatedMetadata) else {
completionHandler(nil, nil, NSFileProviderError(.noSuchItem))
return
}
let fpItem = FileProviderItem(metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
completionHandler(fileNameLocalPath, fpItem, nil)
} else {
Logger.fileTransfer.error("Could not acquire contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and fileName: \(updatedMetadata.fileName, privacy: .public)")
updatedMetadata.status = NextcloudItemMetadataTable.Status.downloadError.rawValue
updatedMetadata.sessionError = error.errorDescription
dbManager.addItemMetadata(updatedMetadata)
completionHandler(nil, nil, error.fileProviderError)
}
}
}
} catch let error {
Logger.fileProviderExtension.error("Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize))
Task {
let (localUrl, updatedItem, error) = await item.fetchContents(
domain: self.domain, progress: progress
)
completionHandler(localUrl, updatedItem, error)
}
return progress
}
func createItem(basedOn itemTemplate: NSFileProviderItem, fields: NSFileProviderItemFields, contents url: URL?, options: NSFileProviderCreateItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress {
// TODO: a new item was created on disk, process the item's creation
Logger.fileProviderExtension.debug("Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)")
func createItem(
basedOn itemTemplate: NSFileProviderItem,
fields: NSFileProviderItemFields,
contents url: URL?,
options: NSFileProviderCreateItemOptions = [],
request: NSFileProviderRequest,
completionHandler: @escaping (
NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?
) -> Void
) -> Progress {
guard itemTemplate.contentType != .symbolicLink else {
Logger.fileProviderExtension.error("Cannot create item, symbolic links not supported.")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:]))
return Progress()
}
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as account not set up yet")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.notAuthenticated))
return Progress()
}
let dbManager = NextcloudFilesDatabaseManager.shared
let parentItemIdentifier = itemTemplate.parentItemIdentifier
let itemTemplateIsFolder = itemTemplate.contentType == .folder ||
itemTemplate.contentType == .directory
if options.contains(.mayAlreadyExist) {
// TODO: This needs to be properly handled with a check in the db
Logger.fileProviderExtension.info("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as it may already exist")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem))
return Progress()
}
var parentItemServerUrl: String
if parentItemIdentifier == .rootContainer {
parentItemServerUrl = ncAccount.davFilesUrl
} else {
guard let parentItemMetadata = dbManager.directoryMetadata(ocId: parentItemIdentifier.rawValue) else {
Logger.fileProviderExtension.error("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem))
return Progress()
}
parentItemServerUrl = parentItemMetadata.serverUrl + "/" + parentItemMetadata.fileName
}
let fileNameLocalPath = url?.path ?? ""
let newServerUrlFileName = parentItemServerUrl + "/" + itemTemplate.filename
Logger.fileProviderExtension.debug("About to upload item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) of type: \(itemTemplate.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(itemTemplate.filename) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)")
if itemTemplateIsFolder {
self.ncKit.createFolder(serverUrlFileName: newServerUrlFileName) { account, ocId, _, error in
guard error == .success else {
Logger.fileTransfer.error("Could not create new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
completionHandler(itemTemplate, [], false, error.fileProviderError)
return
}
// Read contents after creation
self.ncKit.readFileOrFolder(serverUrlFileName: newServerUrlFileName, depth: "0", showHiddenFiles: true) { account, files, _, error in
guard error == .success else {
Logger.fileTransfer.error("Could not read new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
return
}
DispatchQueue.global().async {
NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles(files, account: account) { directoryMetadata, childDirectoriesMetadata, metadatas in
dbManager.addItemMetadata(directoryMetadata)
let fpItem = FileProviderItem(metadata: directoryMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
completionHandler(fpItem, [], true, nil)
}
}
}
}
let tempId = itemTemplate.itemIdentifier.rawValue
Logger.fileProviderExtension.debug(
"""
Received create item request for item with identifier: \(tempId, privacy: .public)
and filename: \(itemTemplate.filename, privacy: .public)
"""
)
guard let ncAccount else {
Logger.fileProviderExtension.error(
"""
Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public)
as account not set up yet
"""
)
completionHandler(
itemTemplate,
NSFileProviderItemFields(),
false,
NSFileProviderError(.notAuthenticated)
)
return Progress()
}
let progress = Progress()
self.ncKit.upload(serverUrlFileName: newServerUrlFileName,
fileNameLocalPath: fileNameLocalPath,
requestHandler: { request in
progress.setHandlersFromAfRequest(request)
}, taskHandler: { task in
NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: itemTemplate.itemIdentifier, completionHandler: { _ in })
}, progressHandler: { uploadProgress in
uploadProgress.copyCurrentStateToProgress(progress)
}) { account, ocId, etag, date, size, _, _, error in
guard error == .success, let ocId = ocId else {
Logger.fileTransfer.error("Could not upload item with filename: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
completionHandler(itemTemplate, [], false, error.fileProviderError)
return
Task {
let (item, error) = await Item.create(
basedOn: itemTemplate,
fields: fields,
contents: url,
request: request,
domain: self.domain,
ncKit: ncKit,
ncAccount: ncAccount,
progress: progress
)
if error != nil {
signalEnumerator(completionHandler: { _ in })
}
Logger.fileTransfer.info("Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)")
if size != itemTemplate.documentSize as? Int64 {
Logger.fileTransfer.warning("Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(itemTemplate.documentSize??.int64Value ?? 0))")
}
let newMetadata = NextcloudItemMetadataTable()
newMetadata.date = (date ?? NSDate()) as Date
newMetadata.etag = etag ?? ""
newMetadata.account = account
newMetadata.fileName = itemTemplate.filename
newMetadata.fileNameView = itemTemplate.filename
newMetadata.ocId = ocId
newMetadata.size = size
newMetadata.contentType = itemTemplate.contentType?.preferredMIMEType ?? ""
newMetadata.directory = itemTemplateIsFolder
newMetadata.serverUrl = parentItemServerUrl
newMetadata.session = ""
newMetadata.sessionError = ""
newMetadata.sessionTaskIdentifier = 0
newMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue
dbManager.addLocalFileMetadataFromItemMetadata(newMetadata)
dbManager.addItemMetadata(newMetadata)
let fpItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
completionHandler(fpItem, [], false, nil)
completionHandler(
item ?? itemTemplate,
NSFileProviderItemFields(),
false,
error
)
}
return progress
}
func modifyItem(_ item: NSFileProviderItem, baseVersion version: NSFileProviderItemVersion, changedFields: NSFileProviderItemFields, contents newContents: URL?, options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress {
func modifyItem(
_ item: NSFileProviderItem,
baseVersion: NSFileProviderItemVersion,
changedFields: NSFileProviderItemFields,
contents newContents: URL?,
options: NSFileProviderModifyItemOptions = [],
request: NSFileProviderRequest,
completionHandler: @escaping (
NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?
) -> Void
) -> Progress {
// An item was modified on disk, process the item's modification
// TODO: Handle finder things like tags, other possible item changed fields
Logger.fileProviderExtension.debug("Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)")
let identifier = item.itemIdentifier
let ocId = identifier.rawValue
Logger.fileProviderExtension.debug(
"""
Received modify item request for item with identifier: \(ocId, privacy: .public)
and filename: \(item.filename, privacy: .public)
"""
)
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public) as account not set up yet")
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not modifying item: \(ocId, privacy: .public) as account not set up yet."
)
completionHandler(item, [], false, NSFileProviderError(.notAuthenticated))
return Progress()
}
let dbManager = NextcloudFilesDatabaseManager.shared
let parentItemIdentifier = item.parentItemIdentifier
let itemTemplateIsFolder = item.contentType == .folder ||
item.contentType == .directory
if options.contains(.mayAlreadyExist) {
// TODO: This needs to be properly handled with a check in the db
Logger.fileProviderExtension.warning("Modification for item: \(item.itemIdentifier.rawValue, privacy: .public) may already exist")
}
var parentItemServerUrl: String
if parentItemIdentifier == .rootContainer {
parentItemServerUrl = ncAccount.davFilesUrl
} else {
guard let parentItemMetadata = dbManager.directoryMetadata(ocId: parentItemIdentifier.rawValue) else {
Logger.fileProviderExtension.error("Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)")
completionHandler(item, [], false, NSFileProviderError(.noSuchItem))
return Progress()
}
parentItemServerUrl = parentItemMetadata.serverUrl + "/" + parentItemMetadata.fileName
}
let fileNameLocalPath = newContents?.path ?? ""
let newServerUrlFileName = parentItemServerUrl + "/" + item.filename
Logger.fileProviderExtension.debug("About to upload modified item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) of type: \(item.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(item.filename, privacy: .public) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)")
var modifiedItem = item
// Create a serial dispatch queue
// We want to wait for network operations to finish before we fire off subsequent network
// operations, or we might cause explosions (e.g. trying to modify items that have just been
// moved elsewhere)
let dispatchQueue = DispatchQueue(label: "modifyItemQueue", qos: .userInitiated)
if changedFields.contains(.filename) || changedFields.contains(.parentItemIdentifier) {
dispatchQueue.async {
let ocId = item.itemIdentifier.rawValue
Logger.fileProviderExtension.debug("Changed fields for item \(ocId, privacy: .public) with filename \(item.filename, privacy: .public) includes filename or parentitemidentifier...")
guard let metadata = dbManager.itemMetadataFromOcId(ocId) else {
Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(item.itemIdentifier.rawValue, privacy: .public)")
completionHandler(item, [], false, NSFileProviderError(.noSuchItem))
return
}
var renameError: NSFileProviderError?
let oldServerUrlFileName = metadata.serverUrl + "/" + metadata.fileName
let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done
moveFileOrFolderDispatchGroup.enter()
self.ncKit.moveFileOrFolder(serverUrlFileNameSource: oldServerUrlFileName,
serverUrlFileNameDestination: newServerUrlFileName,
overwrite: false) { account, error in
guard error == .success else {
Logger.fileTransfer.error("Could not move file or folder: \(oldServerUrlFileName, privacy: .public) to \(newServerUrlFileName, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
renameError = error.fileProviderError
moveFileOrFolderDispatchGroup.leave()
return
}
// Remember that a folder metadata's serverUrl is its direct server URL, while for
// an item metadata the server URL is the parent folder's URL
if itemTemplateIsFolder {
_ = dbManager.renameDirectoryAndPropagateToChildren(ocId: ocId, newServerUrl: newServerUrlFileName, newFileName: item.filename)
self.signalEnumerator { error in
if error != nil {
Logger.fileTransfer.error("Error notifying change in moved directory: \(error)")
}
}
} else {
dbManager.renameItemMetadata(ocId: ocId, newServerUrl: parentItemServerUrl, newFileName: item.filename)
}
guard let newMetadata = dbManager.itemMetadataFromOcId(ocId) else {
Logger.fileTransfer.error("Could not acquire metadata of item with identifier: \(ocId, privacy: .public), cannot correctly inform of modification")
renameError = NSFileProviderError(.noSuchItem)
moveFileOrFolderDispatchGroup.leave()
return
}
modifiedItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
moveFileOrFolderDispatchGroup.leave()
}
moveFileOrFolderDispatchGroup.wait()
guard renameError == nil else {
Logger.fileTransfer.error("Stopping rename of item with ocId \(ocId, privacy: .public) due to error: \(renameError!.localizedDescription, privacy: .public)")
completionHandler(modifiedItem, [], false, renameError)
return
}
guard !itemTemplateIsFolder else {
Logger.fileTransfer.debug("Only handling renaming for folders. ocId: \(ocId, privacy: .public)")
completionHandler(modifiedItem, [], false, nil)
return
}
}
// Return the progress if item is folder here while the async block runs
guard !itemTemplateIsFolder else {
return Progress()
}
}
guard !itemTemplateIsFolder else {
Logger.fileTransfer.debug("System requested modification for folder with ocID \(item.itemIdentifier.rawValue, privacy: .public) (\(newServerUrlFileName, privacy: .public)) of something other than folder name.")
completionHandler(modifiedItem, [], false, nil)
guard let existingItem = Item.storedItem(identifier: identifier, usingKit: ncKit) else {
Logger.fileProviderExtension.error(
"Not modifying item: \(ocId, privacy: .public) as item not found."
)
completionHandler(item, [], false, NSFileProviderError(.noSuchItem))
return Progress()
}
let progress = Progress()
if changedFields.contains(.contents) {
dispatchQueue.async {
Logger.fileProviderExtension.debug("Item modification for \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public) includes contents")
guard newContents != nil else {
Logger.fileProviderExtension.warning("WARNING. Could not upload modified contents as was provided nil contents url. ocId: \(item.itemIdentifier.rawValue, privacy: .public)")
completionHandler(modifiedItem, [], false, NSFileProviderError(.noSuchItem))
return
}
let ocId = item.itemIdentifier.rawValue
guard let metadata = dbManager.itemMetadataFromOcId(ocId) else {
Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(ocId, privacy: .public)")
completionHandler(item, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem))
return
}
dbManager.setStatusForItemMetadata(metadata, status: NextcloudItemMetadataTable.Status.uploading) { updatedMetadata in
if updatedMetadata == nil {
Logger.fileProviderExtension.warning("Could not acquire updated metadata of item with identifier: \(ocId, privacy: .public), unable to update item status to uploading")
}
self.ncKit.upload(serverUrlFileName: newServerUrlFileName,
fileNameLocalPath: fileNameLocalPath,
requestHandler: { request in
progress.setHandlersFromAfRequest(request)
}, taskHandler: { task in
NSFileProviderManager(for: self.domain)?.register(task, forItemWithIdentifier: item.itemIdentifier, completionHandler: { _ in })
}, progressHandler: { uploadProgress in
uploadProgress.copyCurrentStateToProgress(progress)
}) { account, ocId, etag, date, size, _, _, error in
if error == .success, let ocId = ocId {
Logger.fileProviderExtension.info("Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(item.filename, privacy: .public)")
if size != item.documentSize as? Int64 {
Logger.fileTransfer.warning("Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(item.documentSize??.int64Value ?? 0))")
}
let newMetadata = NextcloudItemMetadataTable()
newMetadata.date = (date ?? NSDate()) as Date
newMetadata.etag = etag ?? ""
newMetadata.account = account
newMetadata.fileName = item.filename
newMetadata.fileNameView = item.filename
newMetadata.ocId = ocId
newMetadata.size = size
newMetadata.contentType = item.contentType?.preferredMIMEType ?? ""
newMetadata.directory = itemTemplateIsFolder
newMetadata.serverUrl = parentItemServerUrl
newMetadata.session = ""
newMetadata.sessionError = ""
newMetadata.sessionTaskIdentifier = 0
newMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue
dbManager.addLocalFileMetadataFromItemMetadata(newMetadata)
dbManager.addItemMetadata(newMetadata)
modifiedItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
completionHandler(modifiedItem, [], false, nil)
} else {
Logger.fileTransfer.error("Could not upload item \(item.itemIdentifier.rawValue, privacy: .public) with filename: \(item.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
metadata.status = NextcloudItemMetadataTable.Status.uploadError.rawValue
metadata.sessionError = error.errorDescription
dbManager.addItemMetadata(metadata)
completionHandler(modifiedItem, [], false, error.fileProviderError)
return
}
}
}
Task {
let (modifiedItem, error) = await existingItem.modify(
itemTarget: item,
baseVersion: baseVersion,
changedFields: changedFields,
contents: newContents,
options: options,
request: request,
ncAccount: ncAccount,
domain: domain,
progress: progress
)
if error != nil {
signalEnumerator(completionHandler: { _ in })
}
} else {
Logger.fileProviderExtension.debug("Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete")
completionHandler(modifiedItem, [], false, nil)
completionHandler(modifiedItem ?? item, [], false, error)
}
return progress
}
func deleteItem(identifier: NSFileProviderItemIdentifier, baseVersion version: NSFileProviderItemVersion, options: NSFileProviderDeleteItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (Error?) -> Void) -> Progress {
Logger.fileProviderExtension.debug("Received delete item request for item with identifier: \(identifier.rawValue, privacy: .public)")
func deleteItem(
identifier: NSFileProviderItemIdentifier,
baseVersion _: NSFileProviderItemVersion,
options _: NSFileProviderDeleteItemOptions = [],
request _: NSFileProviderRequest,
completionHandler: @escaping (Error?) -> Void
) -> Progress {
Logger.fileProviderExtension.debug(
"Received delete request for item: \(identifier.rawValue, privacy: .public)"
)
guard ncAccount != nil else {
Logger.fileProviderExtension.error("Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet")
Logger.fileProviderExtension.error(
"Not deleting item \(identifier.rawValue, privacy: .public), account not set up yet"
)
completionHandler(NSFileProviderError(.notAuthenticated))
return Progress()
}
let dbManager = NextcloudFilesDatabaseManager.shared
let ocId = identifier.rawValue
guard let itemMetadata = dbManager.itemMetadataFromOcId(ocId) else {
guard let item = Item.storedItem(identifier: identifier, usingKit: ncKit) else {
Logger.fileProviderExtension.error(
"Not deleting item \(identifier.rawValue, privacy: .public), item not found"
)
completionHandler(NSFileProviderError(.noSuchItem))
return Progress()
}
let serverFileNameUrl = itemMetadata.serverUrl + "/" + itemMetadata.fileName
guard serverFileNameUrl != "" else {
completionHandler(NSFileProviderError(.noSuchItem))
return Progress()
}
self.ncKit.deleteFileOrFolder(serverUrlFileName: serverFileNameUrl) { account, error in
guard error == .success else {
Logger.fileTransfer.error("Could not delete item with ocId \(identifier.rawValue, privacy: .public) at \(serverFileNameUrl, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
completionHandler(error.fileProviderError)
return
let progress = Progress(totalUnitCount: 1)
Task {
let error = await item.delete()
if error != nil {
signalEnumerator(completionHandler: { _ in })
}
Logger.fileTransfer.info("Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)")
if itemMetadata.directory {
_ = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: ocId)
} else {
dbManager.deleteItemMetadata(ocId: ocId)
if dbManager.localFileMetadataFromOcId(ocId) != nil {
dbManager.deleteLocalFileMetadata(ocId: ocId)
}
}
completionHandler(nil)
progress.completedUnitCount = 1
completionHandler(await item.delete())
}
return Progress()
return progress
}
func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest) throws -> NSFileProviderEnumerator {
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up")
func enumerator(
for containerItemIdentifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest
) throws -> NSFileProviderEnumerator {
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up"
)
throw NSFileProviderError(.notAuthenticated)
}
return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit)
return Enumerator(
enumeratedItemIdentifier: containerItemIdentifier,
ncAccount: ncAccount,
ncKit: ncKit,
domain: domain,
fastEnumeration: config.fastEnumerationEnabled
)
}
func materializedItemsDidChange(completionHandler: @escaping () -> Void) {
guard let ncAccount = self.ncAccount else {
Logger.fileProviderExtension.error("Not purging stale local file metadatas, account not set up")
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not purging stale local file metadatas, account not set up")
completionHandler()
return
}
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)")
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)"
)
completionHandler()
return
}
let materialisedEnumerator = fpManager.enumeratorForMaterializedItems()
let materialisedObserver = FileProviderMaterialisedEnumerationObserver(ncKitAccount: ncAccount.ncKitAccount) { _ in
let materialisedObserver = MaterialisedEnumerationObserver(
ncKitAccount: ncAccount.ncKitAccount
) { _ in
completionHandler()
}
let startingPage = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data)
@ -622,9 +362,11 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
materialisedEnumerator.enumerateItems(for: materialisedObserver, startingAt: startingPage)
}
func signalEnumerator(completionHandler: @escaping(_ error: Error?) -> Void) {
guard let fpManager = NSFileProviderManager(for: self.domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts.")
func signalEnumerator(completionHandler: @escaping (_ error: Error?) -> Void) {
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts."
)
return
}

View File

@ -1,133 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 FileProvider
import UniformTypeIdentifiers
import NextcloudKit
class FileProviderItem: NSObject, NSFileProviderItem {
enum FileProviderItemTransferError: Error {
case downloadError
case uploadError
}
let metadata: NextcloudItemMetadataTable
let parentItemIdentifier: NSFileProviderItemIdentifier
let ncKit: NextcloudKit
var itemIdentifier: NSFileProviderItemIdentifier {
return NSFileProviderItemIdentifier(metadata.ocId)
}
var capabilities: NSFileProviderItemCapabilities {
guard !metadata.directory else {
return [ .allowsAddingSubItems,
.allowsContentEnumerating,
.allowsReading,
.allowsDeleting,
.allowsRenaming ]
}
guard !metadata.lock else {
return [ .allowsReading ]
}
return [ .allowsWriting,
.allowsReading,
.allowsDeleting,
.allowsRenaming,
.allowsReparenting ]
}
var itemVersion: NSFileProviderItemVersion {
NSFileProviderItemVersion(contentVersion: metadata.etag.data(using: .utf8)!,
metadataVersion: metadata.etag.data(using: .utf8)!)
}
var filename: String {
return metadata.fileNameView
}
var contentType: UTType {
if self.itemIdentifier == .rootContainer || metadata.directory {
return .folder
}
let internalType = ncKit.nkCommonInstance.getInternalType(fileName: metadata.fileNameView,
mimeType: "",
directory: metadata.directory)
return UTType(filenameExtension: internalType.ext) ?? .content
}
var documentSize: NSNumber? {
return NSNumber(value: metadata.size)
}
var creationDate: Date? {
return metadata.creationDate as Date
}
var lastUsedDate: Date? {
return metadata.date as Date
}
var contentModificationDate: Date? {
return metadata.date as Date
}
var isDownloaded: Bool {
return metadata.directory || NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
}
var isDownloading: Bool {
return metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue
}
var downloadingError: Error? {
if metadata.status == NextcloudItemMetadataTable.Status.downloadError.rawValue {
return FileProviderItemTransferError.downloadError
}
return nil
}
var isUploaded: Bool {
return NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
}
var isUploading: Bool {
return metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue
}
var uploadingError: Error? {
if metadata.status == NextcloudItemMetadataTable.Status.uploadError.rawValue {
return FileProviderItemTransferError.uploadError
} else {
return nil
}
}
var childItemCount: NSNumber? {
if metadata.directory {
return NSNumber(integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory(metadata).count)
} else {
return nil
}
}
required init(metadata: NextcloudItemMetadataTable, parentItemIdentifier: NSFileProviderItemIdentifier, ncKit: NextcloudKit) {
self.metadata = metadata
self.parentItemIdentifier = parentItemIdentifier
self.ncKit = ncKit
super.init()
}
}

View File

@ -12,45 +12,55 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
import NextcloudFileProviderKit
import OSLog
class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnumerationObserver {
class FileProviderMaterialisedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver {
let ncKitAccount: String
let completionHandler: (_ deletedOcIds: Set<String>) -> Void
var allEnumeratedItemIds: Set<String> = Set<String>()
var allEnumeratedItemIds: Set<String> = .init()
required init(ncKitAccount: String, completionHandler: @escaping(_ deletedOcIds: Set<String>) -> Void) {
required init(
ncKitAccount: String, completionHandler: @escaping (_ deletedOcIds: Set<String>) -> Void
) {
self.ncKitAccount = ncKitAccount
self.completionHandler = completionHandler
super.init()
}
func didEnumerate(_ updatedItems: [NSFileProviderItemProtocol]) {
let updatedItemsIds = Array(updatedItems.map { $0.itemIdentifier.rawValue })
let updatedItemsIds = Array(updatedItems.map(\.itemIdentifier.rawValue))
for updatedItemsId in updatedItemsIds {
allEnumeratedItemIds.insert(updatedItemsId)
}
}
func finishEnumerating(upTo nextPage: NSFileProviderPage?) {
func finishEnumerating(upTo _: NSFileProviderPage?) {
Logger.materialisedFileHandling.debug("Handling enumerated materialised items.")
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds,
account: self.ncKitAccount,
completionHandler: self.completionHandler)
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(
allEnumeratedItemIds,
account: ncKitAccount,
completionHandler: completionHandler)
}
func finishEnumeratingWithError(_ error: Error) {
Logger.materialisedFileHandling.error("Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far")
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds,
account: self.ncKitAccount,
completionHandler: self.completionHandler)
Logger.materialisedFileHandling.error(
"Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far"
)
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(
allEnumeratedItemIds,
account: ncKitAccount,
completionHandler: completionHandler)
}
static func handleEnumeratedItems(_ itemIds: Set<String>, account: String, completionHandler: @escaping(_ deletedOcIds: Set<String>) -> Void) {
let dbManager = NextcloudFilesDatabaseManager.shared
static func handleEnumeratedItems(
_ itemIds: Set<String>, account: String,
completionHandler: @escaping (_ deletedOcIds: Set<String>) -> Void
) {
let dbManager = FilesDatabaseManager.shared
let databaseLocalFileMetadatas = dbManager.localFileMetadatas(account: account)
var noLongerMaterialisedIds = Set<String>()
@ -60,12 +70,13 @@ class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnum
guard itemIds.contains(localFileOcId) else {
noLongerMaterialisedIds.insert(localFileOcId)
continue;
continue
}
}
DispatchQueue.main.async {
Logger.materialisedFileHandling.info("Cleaning up local file metadatas for unmaterialised items")
Logger.materialisedFileHandling.info(
"Cleaning up local file metadatas for unmaterialised items")
for itemId in noLongerMaterialisedIds {
dbManager.deleteLocalFileMetadata(ocId: itemId)
}

View File

@ -24,25 +24,27 @@ class FileProviderSocketLineProcessor: NSObject, LineProcessor {
}
func process(_ line: String) {
if (line.contains("~")) { // We use this as the separator specifically in ACCOUNT_DETAILS
Logger.desktopClientConnection.debug("Processing file provider line with potentially sensitive user data")
if line.contains("~") { // We use this as the separator specifically in ACCOUNT_DETAILS
Logger.desktopClientConnection.debug(
"Processing file provider line with potentially sensitive user data")
} else {
Logger.desktopClientConnection.debug("Processing file provider line: \(line, privacy: .public)")
Logger.desktopClientConnection.debug(
"Processing file provider line: \(line, privacy: .public)")
}
let splitLine = line.split(separator: ":", maxSplits: 1)
guard let commandSubsequence = splitLine.first else {
Logger.desktopClientConnection.error("Input line did not have a first element")
return;
return
}
let command = String(commandSubsequence);
let command = String(commandSubsequence)
Logger.desktopClientConnection.debug("Received command: \(command, privacy: .public)")
if (command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER") {
if command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER" {
delegate.sendFileProviderDomainIdentifier()
} else if (command == "ACCOUNT_NOT_AUTHENTICATED") {
} else if command == "ACCOUNT_NOT_AUTHENTICATED" {
delegate.removeAccountConfig()
} else if (command == "ACCOUNT_DETAILS") {
} else if command == "ACCOUNT_DETAILS" {
guard let accountDetailsSubsequence = splitLine.last else { return }
let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 2)

View File

@ -1,57 +0,0 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import FileProvider
import OSLog
func pathForAppGroupContainer() -> URL? {
guard let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String else {
Logger.localFileOps.critical("Could not get container url as missing SocketApiPrefix info in app Info.plist")
return nil
}
return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier)
}
func pathForFileProviderExtData() -> URL? {
let containerUrl = pathForAppGroupContainer()
return containerUrl?.appendingPathComponent("FileProviderExt/")
}
func pathForFileProviderTempFilesForDomain(_ domain: NSFileProviderDomain) throws -> URL? {
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.localFileOps.error("Unable to get file provider manager for domain: \(domain.displayName, privacy: .public)")
throw NSFileProviderError(.providerNotFound)
}
let fileProviderDataUrl = try fpManager.temporaryDirectoryURL()
return fileProviderDataUrl.appendingPathComponent("TemporaryNextcloudFiles/")
}
func localPathForNCFile(ocId: String, fileNameView: String, domain: NSFileProviderDomain) throws -> URL {
guard let fileProviderFilesPathUrl = try pathForFileProviderTempFilesForDomain(domain) else {
Logger.localFileOps.error("Unable to get path for file provider temp files for domain: \(domain.displayName, privacy: .public)")
throw URLError(.badURL)
}
let filePathUrl = fileProviderFilesPathUrl.appendingPathComponent(fileNameView)
let filePath = filePathUrl.path
if !FileManager.default.fileExists(atPath: filePath) {
FileManager.default.createFile(atPath: filePath, contents: nil)
}
return filePathUrl
}

View File

@ -1,32 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import FileProvider
class NextcloudAccount: NSObject {
static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/"
let username, password, ncKitAccount, serverUrl, davFilesUrl: String
init(user: String, serverUrl: String, password: String) {
self.username = user
self.password = password
self.ncKitAccount = user + " " + serverUrl
self.serverUrl = serverUrl
self.davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user
super.init()
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef ClientCommunicationProtocol_h
#define ClientCommunicationProtocol_h
#import <Foundation/Foundation.h>
@protocol ClientCommunicationProtocol
- (void)getExtensionAccountIdWithCompletionHandler:(void(^)(NSString *extensionAccountId, NSError *error))completionHandler;
- (void)configureAccountWithUser:(NSString *)user
serverUrl:(NSString *)serverUrl
password:(NSString *)password;
- (void)removeAccountConfig;
- (void)createDebugLogStringWithCompletionHandler:(void(^)(NSString *debugLogString, NSError *error))completionHandler;
- (void)getFastEnumerationStateWithCompletionHandler:(void(^)(BOOL enabled, BOOL set))completionHandler;
- (void)setFastEnumerationEnabled:(BOOL)enabled;
@end
#endif /* ClientCommunicationProtocol_h */

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2023 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* 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 Foundation
import FileProvider
import OSLog
class ClientCommunicationService: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, ClientCommunicationProtocol {
let listener = NSXPCListener.anonymous()
let serviceName = NSFileProviderServiceName("com.nextcloud.desktopclient.ClientCommunicationService")
let fpExtension: FileProviderExtension
init(fpExtension: FileProviderExtension) {
Logger.desktopClientConnection.debug("Instantiating client communication service")
self.fpExtension = fpExtension
super.init()
}
func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
listener.delegate = self
listener.resume()
return listener.endpoint
}
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: ClientCommunicationProtocol.self)
newConnection.exportedObject = self
newConnection.resume()
return true
}
//MARK: - Client Communication Protocol methods
func getExtensionAccountId(completionHandler: @escaping (String?, Error?) -> Void) {
let accountUserId = self.fpExtension.domain.identifier.rawValue
Logger.desktopClientConnection.info("Sending extension account ID \(accountUserId, privacy: .public)")
completionHandler(accountUserId, nil)
}
func configureAccount(withUser user: String,
serverUrl: String,
password: String) {
Logger.desktopClientConnection.info("Received configure account information over client communication service")
self.fpExtension.setupDomainAccount(user: user,
serverUrl: serverUrl,
password: password)
}
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)
}
}
func getFastEnumerationState(completionHandler: @escaping (Bool, Bool) -> Void) {
let enabled = fpExtension.config.fastEnumerationEnabled
let set = fpExtension.config.fastEnumerationSet
completionHandler(enabled, set)
}
func setFastEnumerationEnabled(_ enabled: Bool) {
fpExtension.config.fastEnumerationEnabled = enabled
Logger.fileProviderExtension.info("Fast enumeration setting changed to: \(enabled, privacy: .public)")
guard enabled else { return }
// If enabled, start full enumeration
guard let fpManager = NSFileProviderManager(for: fpExtension.domain) else {
let domainName = self.fpExtension.domain.displayName
Logger.fileProviderExtension.error("Could not get file provider manager for domain \(domainName, privacy: .public), cannot run enumeration after fast enumeration setting change")
return
}
fpManager.signalEnumerator(for: .workingSet) { error in
if error != nil {
Logger.fileProviderExtension.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)")
}
}
}
}

View File

@ -0,0 +1,18 @@
//
// FPUIExtensionCommunicationProtocol.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//
import FileProvider
import NextcloudKit
let fpUiExtensionServiceName = NSFileProviderServiceName(
"com.nextcloud.desktopclient.FPUIExtensionService"
)
@objc protocol FPUIExtensionService {
func credentials() async -> NSDictionary
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString?
}

View File

@ -0,0 +1,65 @@
//
// FPUIExtensionCommunicationService.swift
// FileProviderExt
//
// Created by Claudio Cambra on 21/2/24.
//
import FileProvider
import Foundation
import NextcloudKit
import NextcloudFileProviderKit
import OSLog
class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, FPUIExtensionService {
let listener = NSXPCListener.anonymous()
let serviceName = fpUiExtensionServiceName
let fpExtension: FileProviderExtension
init(fpExtension: FileProviderExtension) {
Logger.fpUiExtensionService.debug("Instantiating FPUIExtensionService service")
self.fpExtension = fpExtension
super.init()
}
func makeListenerEndpoint() throws -> NSXPCListenerEndpoint {
listener.delegate = self
listener.resume()
return listener.endpoint
}
func listener(
_ listener: NSXPCListener,
shouldAcceptNewConnection newConnection: NSXPCConnection
) -> Bool {
newConnection.exportedInterface = NSXPCInterface(with: FPUIExtensionService.self)
newConnection.exportedObject = self
newConnection.resume()
return true
}
//MARK: - FPUIExtensionService protocol methods
func credentials() async -> NSDictionary {
return (fpExtension.ncAccount?.dictionary() ?? [:]) as NSDictionary
}
func itemServerPath(identifier: NSFileProviderItemIdentifier) async -> NSString? {
let rawIdentifier = identifier.rawValue
Logger.shares.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
guard let baseUrl = fpExtension.ncAccount?.davFilesUrl else {
Logger.shares.error("Could not fetch shares as ncAccount on parent extension is nil")
return nil
}
let dbManager = FilesDatabaseManager.shared
guard let item = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else {
Logger.shares.error("No item \(rawIdentifier, privacy: .public) in db, no shares.")
return nil
}
let completePath = item.serverUrl + "/" + item.fileName
return completePath.replacingOccurrences(of: baseUrl, with: "") as NSString
}
}

View File

@ -0,0 +1,57 @@
//
// DocumentActionViewController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 20/2/24.
//
import FileProviderUI
import OSLog
class DocumentActionViewController: FPUIActionExtensionViewController {
var domain: NSFileProviderDomain {
guard let identifier = extensionContext.domainIdentifier else {
fatalError("not expected to be called with default domain")
}
return NSFileProviderDomain(
identifier: NSFileProviderDomainIdentifier(rawValue: identifier.rawValue),
displayName: ""
)
}
func prepare(childViewController: NSViewController) {
addChild(childViewController)
view.addSubview(childViewController.view)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: childViewController.view.leadingAnchor),
view.trailingAnchor.constraint(equalTo: childViewController.view.trailingAnchor),
view.topAnchor.constraint(equalTo: childViewController.view.topAnchor),
view.bottomAnchor.constraint(equalTo: childViewController.view.bottomAnchor)
])
}
override func prepare(
forAction actionIdentifier: String, itemIdentifiers: [NSFileProviderItemIdentifier]
) {
Logger.actionViewController.info("Preparing action: \(actionIdentifier, privacy: .public)")
if actionIdentifier == "com.nextcloud.desktopclient.FileProviderUIExt.ShareAction" {
prepare(childViewController: ShareViewController(itemIdentifiers))
}
}
override func prepare(forError error: Error) {
Logger.actionViewController.info(
"""
Preparing for error: \(error.localizedDescription, privacy: .public)
"""
)
}
override public func loadView() {
self.view = NSView()
}
}

View File

@ -0,0 +1,21 @@
//
// Logger+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 21/2/24.
//
import OSLog
extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
static let actionViewController = Logger(subsystem: subsystem, category: "actionViewController")
static let shareCapabilities = Logger(subsystem: subsystem, category: "shareCapabilities")
static let shareController = Logger(subsystem: subsystem, category: "shareController")
static let shareeDataSource = Logger(subsystem: subsystem, category: "shareeDataSource")
static let sharesDataSource = Logger(subsystem: subsystem, category: "sharesDataSource")
static let shareOptionsView = Logger(subsystem: subsystem, category: "shareOptionsView")
static let shareViewController = Logger(subsystem: subsystem, category: "shareViewController")
}

View File

@ -0,0 +1,130 @@
//
// NKShare+Extensions.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import NextcloudKit
extension NKShare {
enum ShareType: Int {
case user = 0
case group = 1
case publicLink = 3
case email = 4
case federatedCloud = 6
case circle = 7
case talkConversation = 10
}
enum PermissionValues: Int {
case readShare = 1
case updateShare = 2
case createShare = 4
case deleteShare = 8
case shareShare = 16
case all = 31
}
var typeImage: NSImage? {
var image: NSImage?
switch shareType {
case ShareType.user.rawValue:
image = NSImage(
systemSymbolName: "person.circle.fill",
accessibilityDescription: "User share icon"
)
case ShareType.group.rawValue:
image = NSImage(
systemSymbolName: "person.2.circle.fill",
accessibilityDescription: "Group share icon"
)
case ShareType.publicLink.rawValue:
image = NSImage(
systemSymbolName: "link.circle.fill",
accessibilityDescription: "Public link share icon"
)
case ShareType.email.rawValue:
image = NSImage(
systemSymbolName: "envelope.circle.fill",
accessibilityDescription: "Email share icon"
)
case ShareType.federatedCloud.rawValue:
image = NSImage(
systemSymbolName: "cloud.circle.fill",
accessibilityDescription: "Federated cloud share icon"
)
case ShareType.circle.rawValue:
image = NSImage(
systemSymbolName: "circle.circle.fill",
accessibilityDescription: "Circle share icon"
)
case ShareType.talkConversation.rawValue:
image = NSImage(
systemSymbolName: "message.circle.fill",
accessibilityDescription: "Talk conversation share icon"
)
default:
return nil
}
var config = NSImage.SymbolConfiguration(textStyle: .body, scale: .large)
if #available(macOS 12.0, *) {
config = config.applying(
.init(paletteColors: [.controlBackgroundColor, .controlAccentColor])
)
}
return image?.withSymbolConfiguration(config)
}
var displayString: String {
if label != "" {
return label
}
switch shareType {
case ShareType.user.rawValue:
return "User share (\(shareWith))"
case ShareType.group.rawValue:
return "Group share (\(shareWith))"
case ShareType.publicLink.rawValue:
return "Public link share"
case ShareType.email.rawValue:
return "Email share (\(shareWith))"
case ShareType.federatedCloud.rawValue:
return "Federated cloud share (\(shareWith))"
case ShareType.circle.rawValue:
return "Circle share (\(shareWith))"
case ShareType.talkConversation.rawValue:
return "Talk conversation share (\(shareWith))"
default:
return "Unknown share"
}
}
var expirationDateString: String? {
guard let date = expirationDate else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date as Date)
}
var shareesCanEdit: Bool {
get { (permissions & PermissionValues.updateShare.rawValue) != 0 }
set {
if newValue {
permissions |= NKShare.PermissionValues.updateShare.rawValue
} else {
permissions &= ~NKShare.PermissionValues.updateShare.rawValue
}
}
}
static func formattedDateString(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
return dateFormatter.string(from: date)
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX)$(OC_APPLICATION_REV_DOMAIN)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleDisplayName</key>
<string>$(OC_APPLICATION_NAME) File Provider UI Extension</string>
<key>CFBundleIdentifier</key>
<string>$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionFileProviderActions</key>
<array>
<dict>
<key>NSExtensionFileProviderActionActivationRule</key>
<string>TRUEPREDICATE</string>
<key>NSExtensionFileProviderActionIdentifier</key>
<string>com.nextcloud.desktopclient.FileProviderUIExt.ShareAction</string>
<key>NSExtensionFileProviderActionName</key>
<string>Share options</string>
</dict>
</array>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.fileprovider-actionsui</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).DocumentActionViewController</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,155 @@
//
// ShareController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 4/3/24.
//
import Combine
import Foundation
import NextcloudKit
import OSLog
class ShareController: ObservableObject {
@Published private(set) var share: NKShare
private let kit: NextcloudKit
static func create(
kit: NextcloudKit,
shareType: NKShare.ShareType,
itemServerRelativePath: String,
shareWith: String?,
password: String? = nil,
expireDate: String? = nil,
permissions: Int = 1,
publicUpload: Bool = false,
note: String? = nil,
label: String? = nil,
hideDownload: Bool,
attributes: String? = nil,
options: NKRequestOptions = NKRequestOptions()
) async -> NKError? {
Logger.shareController.info("Creating share: \(itemServerRelativePath)")
return await withCheckedContinuation { continuation in
if shareType == .publicLink {
kit.createShareLink(
path: itemServerRelativePath,
hideDownload: hideDownload,
publicUpload: publicUpload,
password: password,
permissions: permissions,
options: options
) { account, share, data, error in
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error(
"""
Error creating link share: \(error.errorDescription, privacy: .public)
"""
)
return
}
}
} else {
guard let shareWith = shareWith else {
let errorString = "No recipient for share!"
Logger.shareController.error("\(errorString, privacy: .public)")
let error = NKError(statusCode: 0, fallbackDescription: errorString)
continuation.resume(returning: error)
return
}
kit.createShare(
path: itemServerRelativePath,
shareType: shareType.rawValue,
shareWith: shareWith,
password: password,
permissions: permissions,
options: options,
attributes: attributes
) { account, share, data, error in
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error(
"""
Error creating share: \(error.errorDescription, privacy: .public)
"""
)
return
}
}
}
}
}
init(share: NKShare, kit: NextcloudKit) {
self.share = share
self.kit = kit
}
func save(
password: String? = nil,
expireDate: String? = nil,
permissions: Int = 1,
publicUpload: Bool = false,
note: String? = nil,
label: String? = nil,
hideDownload: Bool,
attributes: String? = nil,
options: NKRequestOptions = NKRequestOptions()
) async -> NKError? {
Logger.shareController.info("Saving share: \(self.share.url, privacy: .public)")
return await withCheckedContinuation { continuation in
kit.updateShare(
idShare: share.idShare,
password: password,
expireDate: expireDate,
permissions: permissions,
publicUpload: publicUpload,
note: note,
label: label,
hideDownload: hideDownload,
attributes: attributes,
options: options
) { account, share, data, error in
Logger.shareController.info(
"""
Received update response: \(share?.url ?? "", privacy: .public)
"""
)
defer { continuation.resume(returning: error) }
guard error == .success, let share = share else {
Logger.shareController.error(
"""
Error updating save: \(error.errorDescription, privacy: .public)
"""
)
return
}
self.share = share
}
}
}
func delete() async -> NKError? {
Logger.shareController.info("Deleting share: \(self.share.url, privacy: .public)")
return await withCheckedContinuation { continuation in
kit.deleteShare(idShare: share.idShare) { account, error in
Logger.shareController.info(
"""
Received delete response: \(self.share.url, privacy: .public)
"""
)
defer { continuation.resume(returning: error) }
guard error == .success else {
Logger.shareController.error(
"""
Error deleting save: \(error.errorDescription, privacy: .public)
"""
)
return
}
}
}
}
}

View File

@ -0,0 +1,354 @@
//
// ShareOptionsView.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import Combine
import NextcloudKit
import OSLog
import SuggestionsTextFieldKit
class ShareOptionsView: NSView {
@IBOutlet private weak var optionsTitleTextField: NSTextField!
@IBOutlet private weak var shareRecipientTextField: NSTextField! // Hide if public link share
@IBOutlet private weak var labelTextField: NSTextField!
@IBOutlet private weak var uploadEditPermissionCheckbox: NSButton!
@IBOutlet private weak var hideDownloadCheckbox: NSButton!
@IBOutlet private weak var passwordProtectCheckbox: NSButton!
@IBOutlet private weak var passwordSecureField: NSSecureTextField!
@IBOutlet private weak var expirationDateCheckbox: NSButton!
@IBOutlet private weak var expirationDatePicker: NSDatePicker!
@IBOutlet private weak var noteForRecipientCheckbox: NSButton!
@IBOutlet private weak var noteTextField: NSTextField!
@IBOutlet private weak var saveButton: NSButton!
@IBOutlet private weak var deleteButton: NSButton!
@IBOutlet private weak var shareTypePicker: NSPopUpButton!
@IBOutlet private weak var publicLinkShareMenuItem: NSMenuItem!
@IBOutlet private weak var userShareMenuItem: NSMenuItem!
@IBOutlet private weak var groupShareMenuItem: NSMenuItem!
@IBOutlet private weak var emailShareMenuItem: NSMenuItem!
@IBOutlet private weak var federatedCloudShareMenuItem: NSMenuItem!
@IBOutlet private weak var circleShare: NSMenuItem!
@IBOutlet private weak var talkConversationShare: NSMenuItem!
var kit: NextcloudKit? {
didSet {
Logger.shareOptionsView.info("Setting up the kit.")
guard let kit = kit else {
Logger.shareOptionsView.error("Could not configure suggestions data source.")
return
}
suggestionsTextFieldDelegate.suggestionsDataSource = ShareeSuggestionsDataSource(
kit: kit
)
suggestionsTextFieldDelegate.confirmationHandler = { suggestion in
guard let sharee = suggestion?.data as? NKSharee else { return }
self.shareRecipientTextField.stringValue = sharee.shareWith
Logger.shareOptionsView.debug("Chose sharee \(sharee.shareWith, privacy: .public)")
}
suggestionsTextFieldDelegate.targetTextField = shareRecipientTextField
}
}
var dataSource: ShareTableViewDataSource?
var controller: ShareController? {
didSet {
guard controller != nil else { return }
optionsTitleTextField.stringValue = "Share options"
deleteButton.title = "Delete"
deleteButton.image = NSImage(
systemSymbolName: "trash", accessibilityDescription: "Delete trash icon"
)
deleteButton.bezelColor = NSColor.systemRed
cancellable?.cancel()
createMode = false
update()
cancellable = controller.publisher.sink { _ in self.update() }
}
}
var createMode = false {
didSet {
Logger.shareOptionsView.info("Create mode set: \(self.createMode, privacy: .public)")
shareTypePicker.isHidden = !createMode
shareRecipientTextField.isHidden = !createMode
labelTextField.isHidden = createMode // Cannot set label on create API call
guard createMode else { return }
optionsTitleTextField.stringValue = "Create new share"
deleteButton.title = "Cancel"
deleteButton.image = NSImage(
systemSymbolName: "xmark.bin", accessibilityDescription: "Cancel create icon"
)
deleteButton.bezelColor = NSColor.controlColor
cancellable?.cancel()
cancellable = nil
controller = nil
reset()
setupCreateForm()
}
}
private var cancellable: AnyCancellable?
private var suggestionsWindowController = SuggestionsWindowController()
private var suggestionsTextFieldDelegate = SuggestionsTextFieldDelegate()
private func update() {
guard let share = controller?.share else {
reset()
setAllFields(enabled: false)
saveButton.isEnabled = false
deleteButton.isEnabled = false
return
}
deleteButton.isEnabled = share.canDelete
saveButton.isEnabled = share.canEdit
setAllFields(enabled: share.canEdit)
reset()
shareRecipientTextField.stringValue = share.shareWithDisplayname
labelTextField.stringValue = share.label
uploadEditPermissionCheckbox.state = share.shareesCanEdit ? .on : .off
hideDownloadCheckbox.state = share.hideDownload ? .on : .off
passwordProtectCheckbox.state = share.password.isEmpty ? .off : .on
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
passwordSecureField.stringValue = share.password
expirationDateCheckbox.state = share.expirationDate == nil ? .off : .on
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
expirationDatePicker.dateValue = share.expirationDate as? Date ?? Date()
noteForRecipientCheckbox.state = share.note.isEmpty ? .off : .on
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
noteForRecipientCheckbox.stringValue = share.note
}
private func reset() {
shareRecipientTextField.stringValue = ""
labelTextField.stringValue = ""
uploadEditPermissionCheckbox.state = .off
hideDownloadCheckbox.state = .off
passwordProtectCheckbox.state = .off
passwordSecureField.isHidden = true
passwordSecureField.stringValue = ""
expirationDateCheckbox.state = .off
expirationDatePicker.isHidden = true
expirationDatePicker.dateValue = NSDate.now
expirationDatePicker.minDate = NSDate.now
expirationDatePicker.maxDate = nil
noteForRecipientCheckbox.state = .off
noteTextField.isHidden = true
noteTextField.stringValue = ""
}
private func setupCreateForm() {
guard createMode else { return }
setAllFields(enabled: true)
let type = pickedShareType()
shareRecipientTextField.isHidden = type == .publicLink
if let caps = dataSource?.capabilities?.filesSharing {
uploadEditPermissionCheckbox.state =
caps.defaultPermissions & NKShare.PermissionValues.updateShare.rawValue != 0
? .on : .off
switch type {
case .publicLink:
passwordProtectCheckbox.isHidden = false
passwordProtectCheckbox.state = caps.publicLink?.passwordEnforced == true ? .on : .off
passwordProtectCheckbox.isEnabled = caps.publicLink?.passwordEnforced == false
expirationDateCheckbox.state = caps.publicLink?.expireDateEnforced == true ? .on : .off
expirationDateCheckbox.isEnabled = caps.publicLink?.expireDateEnforced == false
expirationDatePicker.dateValue = Date(
timeIntervalSinceNow:
TimeInterval((caps.publicLink?.expireDateDays ?? 1) * 24 * 60 * 60)
)
if caps.publicLink?.expireDateEnforced == true {
expirationDatePicker.maxDate = expirationDatePicker.dateValue
}
case .email:
passwordProtectCheckbox.isHidden = caps.email?.passwordEnabled == false
passwordProtectCheckbox.state = caps.email?.passwordEnforced == true ? .on : .off
default:
passwordProtectCheckbox.isHidden = true
passwordProtectCheckbox.state = .off
break
}
}
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
}
private func setAllFields(enabled: Bool) {
shareTypePicker.isEnabled = enabled
shareRecipientTextField.isEnabled = enabled
labelTextField.isEnabled = enabled
uploadEditPermissionCheckbox.isEnabled = enabled
hideDownloadCheckbox.isEnabled = enabled
passwordProtectCheckbox.isEnabled = enabled
passwordSecureField.isEnabled = enabled
expirationDateCheckbox.isEnabled = enabled
expirationDatePicker.isEnabled = enabled
noteForRecipientCheckbox.isEnabled = enabled
noteTextField.isEnabled = enabled
saveButton.isEnabled = enabled
deleteButton.isEnabled = enabled
}
private func pickedShareType() -> NKShare.ShareType {
let selectedShareTypeItem = shareTypePicker.selectedItem
var selectedShareType = NKShare.ShareType.publicLink
if selectedShareTypeItem == publicLinkShareMenuItem {
selectedShareType = .publicLink
} else if selectedShareTypeItem == userShareMenuItem {
selectedShareType = .user
} else if selectedShareTypeItem == groupShareMenuItem {
selectedShareType = .group
} else if selectedShareTypeItem == emailShareMenuItem {
selectedShareType = .email
} else if selectedShareTypeItem == federatedCloudShareMenuItem {
selectedShareType = .federatedCloud
} else if selectedShareTypeItem == circleShare {
selectedShareType = .circle
} else if selectedShareTypeItem == talkConversationShare {
selectedShareType = .talkConversation
}
return selectedShareType
}
@IBAction func shareTypePickerAction(_ sender: Any) {
if createMode {
setupCreateForm()
}
}
@IBAction func passwordCheckboxAction(_ sender: Any) {
passwordSecureField.isHidden = passwordProtectCheckbox.state == .off
}
@IBAction func expirationDateCheckboxAction(_ sender: Any) {
expirationDatePicker.isHidden = expirationDateCheckbox.state == .off
}
@IBAction func noteForRecipientCheckboxAction(_ sender: Any) {
noteTextField.isHidden = noteForRecipientCheckbox.state == .off
}
@IBAction func save(_ sender: Any) {
Task { @MainActor in
let password = passwordProtectCheckbox.state == .on
? passwordSecureField.stringValue
: ""
let expireDate = expirationDateCheckbox.state == .on
? NKShare.formattedDateString(date: expirationDatePicker.dateValue)
: ""
let note = noteForRecipientCheckbox.state == .on
? noteTextField.stringValue
: ""
let label = labelTextField.stringValue
let hideDownload = hideDownloadCheckbox.state == .on
let uploadAndEdit = uploadEditPermissionCheckbox.state == .on
guard !createMode else {
Logger.shareOptionsView.info("Creating new share!")
guard let dataSource = dataSource,
let kit = kit,
let itemServerRelativePath = dataSource.itemServerRelativePath
else {
Logger.shareOptionsView.error("Cannot create new share due to missing data.")
Logger.shareOptionsView.error("dataSource: \(self.dataSource, privacy: .public)")
Logger.shareOptionsView.error("kit: \(self.kit, privacy: .public)")
Logger.shareOptionsView.error(
"path: \(self.dataSource?.itemServerRelativePath ?? "", privacy: .public)"
)
return
}
let selectedShareType = pickedShareType()
let shareWith = shareRecipientTextField.stringValue
var permissions = NKShare.PermissionValues.all.rawValue
permissions = uploadAndEdit
? permissions | NKShare.PermissionValues.updateShare.rawValue
: permissions & ~NKShare.PermissionValues.updateShare.rawValue
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await ShareController.create(
kit: kit,
shareType: selectedShareType,
itemServerRelativePath: itemServerRelativePath,
shareWith: shareWith,
password: password,
expireDate: expireDate,
permissions: permissions,
note: note,
label: label,
hideDownload: hideDownload
)
if let error = error, error != .success {
dataSource.uiDelegate?.showError("Error creating: \(error.errorDescription)")
setAllFields(enabled: true)
} else {
dataSource.uiDelegate?.hideOptions(self)
await dataSource.reload()
}
return
}
Logger.shareOptionsView.info("Editing existing share!")
guard let controller = controller else {
Logger.shareOptionsView.error("No valid share controller, cannot edit share.")
return
}
let share = controller.share
let permissions = uploadAndEdit
? share.permissions | NKShare.PermissionValues.updateShare.rawValue
: share.permissions & ~NKShare.PermissionValues.updateShare.rawValue
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await controller.save(
password: password,
expireDate: expireDate,
permissions: permissions,
note: note,
label: label,
hideDownload: hideDownload
)
if let error = error, error != .success {
dataSource?.uiDelegate?.showError("Error updating share: \(error.errorDescription)")
setAllFields(enabled: true)
} else {
dataSource?.uiDelegate?.hideOptions(self)
await dataSource?.reload()
}
}
}
@IBAction func delete(_ sender: Any) {
Task { @MainActor in
guard !createMode else {
dataSource?.uiDelegate?.hideOptions(self)
reset()
return
}
setAllFields(enabled: false)
deleteButton.isEnabled = false
saveButton.isEnabled = false
let error = await controller?.delete()
if let error = error, error != .success {
dataSource?.uiDelegate?.showError("Error deleting share: \(error.errorDescription)")
}
await dataSource?.reload()
}
}
}

View File

@ -0,0 +1,64 @@
//
// ShareTableItemView.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import AppKit
import NextcloudKit
class ShareTableItemView: NSTableCellView {
@IBOutlet private weak var typeImageView: NSImageView!
@IBOutlet private weak var label: NSTextField!
@IBOutlet private weak var copyLinkButton: NSButton!
private var originalCopyImage: NSImage?
private var copiedButtonImage: NSImage?
private var tempButtonTimer: Timer?
var share: NKShare? {
didSet {
guard let share = share else {
prepareForReuse()
return
}
typeImageView.image = share.typeImage
label.stringValue = share.displayString
copyLinkButton.isHidden = share.shareType != NKShare.ShareType.publicLink.rawValue
}
}
override func prepareForReuse() {
typeImageView.image = nil
label.stringValue = ""
copyLinkButton.isHidden = false
super.prepareForReuse()
}
@IBAction func copyShareLink(sender: Any) {
guard let share = share else { return }
let pasteboard = NSPasteboard.general
pasteboard.declareTypes([.string], owner: nil)
pasteboard.setString(share.url, forType: .string)
guard tempButtonTimer == nil else { return }
originalCopyImage = copyLinkButton.image
copiedButtonImage = NSImage(
systemSymbolName: "checkmark.circle.fill",
accessibilityDescription: "Public link has been copied icon"
)
var config = NSImage.SymbolConfiguration(scale: .medium)
if #available(macOS 12.0, *) {
config = config.applying(.init(hierarchicalColor: .systemGreen))
}
copiedButtonImage = copiedButtonImage?.withSymbolConfiguration(config)
copyLinkButton.image = copiedButtonImage
tempButtonTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { timer in
self.copyLinkButton.image = self.originalCopyImage
self.copiedButtonImage = nil
self.tempButtonTimer?.invalidate()
self.tempButtonTimer = nil
}
}
}

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner"/>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<tableCellView id="WWf-Il-fKw" customClass="ShareTableItemView" customModule="FileProviderUIExt" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="322" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wdu-Dj-FUn">
<rect key="frame" x="5" y="5" width="312" height="32"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Ex8-CB-iNc">
<rect key="frame" x="0.0" y="-5" width="32" height="43"/>
<constraints>
<constraint firstAttribute="width" secondItem="Ex8-CB-iNc" secondAttribute="height" multiplier="1:1" id="5ak-dc-HzR"/>
<constraint firstAttribute="height" constant="32" id="Cus-qI-uen"/>
</constraints>
<imageCell key="cell" controlSize="large" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" id="0Xf-Au-QjJ">
<imageReference key="image" image="link.circle.fill" catalog="system" symbolScale="large"/>
</imageCell>
<color key="contentTintColor" name="AccentColor"/>
</imageView>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MwZ-er-vB4">
<rect key="frame" x="38" y="8" width="236" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="left" title="Share link" id="Bcz-ws-5yW">
<font key="font" metaFont="systemMedium" size="13"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button horizontalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y6i-wm-BtQ">
<rect key="frame" x="280" y="0.0" width="32" height="32"/>
<buttonCell key="cell" type="inline" title="Copy share link" bezelStyle="inline" image="doc.on.doc.fill" catalog="system" imagePosition="only" alignment="center" controlSize="large" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ram-fe-8Pt">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="smallSystemBold"/>
</buttonCell>
<constraints>
<constraint firstAttribute="width" secondItem="y6i-wm-BtQ" secondAttribute="height" multiplier="1:1" id="BlJ-WU-1y5"/>
</constraints>
<connections>
<action selector="copyShareLinkWithSender:" target="WWf-Il-fKw" id="dgW-8v-wfd"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Ex8-CB-iNc" firstAttribute="top" secondItem="wdu-Dj-FUn" secondAttribute="top" id="2x8-EU-gHs"/>
<constraint firstItem="y6i-wm-BtQ" firstAttribute="top" secondItem="wdu-Dj-FUn" secondAttribute="top" id="BjV-lg-nyL"/>
<constraint firstAttribute="bottom" secondItem="Ex8-CB-iNc" secondAttribute="bottom" id="dd0-Vh-Pbi"/>
<constraint firstAttribute="bottom" secondItem="y6i-wm-BtQ" secondAttribute="bottom" id="ha0-oR-OxV"/>
<constraint firstItem="MwZ-er-vB4" firstAttribute="centerY" secondItem="Ex8-CB-iNc" secondAttribute="centerY" id="m3A-Tu-qMJ"/>
<constraint firstItem="Ex8-CB-iNc" firstAttribute="leading" secondItem="wdu-Dj-FUn" secondAttribute="leading" id="o1t-TB-uhe"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="wdu-Dj-FUn" firstAttribute="top" secondItem="WWf-Il-fKw" secondAttribute="top" constant="5" id="HIY-8X-dM2"/>
<constraint firstItem="wdu-Dj-FUn" firstAttribute="leading" secondItem="WWf-Il-fKw" secondAttribute="leading" constant="5" id="g7a-hl-t5g"/>
<constraint firstAttribute="bottom" secondItem="wdu-Dj-FUn" secondAttribute="bottom" constant="5" id="gAZ-x8-C4K"/>
<constraint firstAttribute="trailing" secondItem="wdu-Dj-FUn" secondAttribute="trailing" constant="5" id="grc-5X-tMi"/>
</constraints>
<connections>
<outlet property="copyLinkButton" destination="y6i-wm-BtQ" id="8bc-yO-Txo"/>
<outlet property="label" destination="MwZ-er-vB4" id="Gba-jf-C8H"/>
<outlet property="typeImageView" destination="Ex8-CB-iNc" id="T7o-zd-qOo"/>
</connections>
<point key="canvasLocation" x="128" y="-22"/>
</tableCellView>
</objects>
<resources>
<image name="doc.on.doc.fill" catalog="system" width="17" height="19"/>
<image name="link.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@ -0,0 +1,256 @@
//
// ShareTableViewDataSource.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 27/2/24.
//
import AppKit
import FileProvider
import NextcloudKit
import NextcloudFileProviderKit
import NextcloudCapabilitiesKit
import OSLog
class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDelegate {
private let shareItemViewIdentifier = NSUserInterfaceItemIdentifier("ShareTableItemView")
private let shareItemViewNib = NSNib(nibNamed: "ShareTableItemView", bundle: nil)
private let reattemptInterval: TimeInterval = 3.0
var uiDelegate: ShareViewDataSourceUIDelegate?
var sharesTableView: NSTableView? {
didSet {
sharesTableView?.register(shareItemViewNib, forIdentifier: shareItemViewIdentifier)
sharesTableView?.rowHeight = 42.0 // Height of view in ShareTableItemView XIB
sharesTableView?.dataSource = self
sharesTableView?.delegate = self
sharesTableView?.reloadData()
}
}
var capabilities: Capabilities?
var itemMetadata: NKFile?
private(set) var kit: NextcloudKit?
private(set) var itemURL: URL?
private(set) var itemServerRelativePath: String?
private(set) var shares: [NKShare] = [] {
didSet { Task { @MainActor in sharesTableView?.reloadData() } }
}
private var account: Account? {
didSet {
guard let account = account else { return }
kit = NextcloudKit()
kit?.setup(
user: account.username,
userId: account.username,
password: account.password,
urlBase: account.serverUrl
)
}
}
func loadItem(url: URL) {
itemServerRelativePath = nil
itemURL = url
Task {
await reload()
}
}
func reattempt() {
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: self.reattemptInterval, repeats: false) { _ in
Task { await self.reload() }
}
}
}
func reload() async {
guard let itemURL = itemURL else { return }
guard let itemIdentifier = await withCheckedContinuation({
(continuation: CheckedContinuation<NSFileProviderItemIdentifier?, Never>) -> Void in
NSFileProviderManager.getIdentifierForUserVisibleFile(
at: itemURL
) { identifier, domainIdentifier, error in
defer { continuation.resume(returning: identifier) }
guard error == nil else {
self.presentError("No item with identifier: \(error.debugDescription)")
return
}
}
}) else {
presentError("Could not get identifier for item, no shares can be acquired.")
return
}
do {
let connection = try await serviceConnection(url: itemURL)
guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier),
let credentials = await connection.credentials() as? Dictionary<String, String>,
let convertedAccount = Account(dictionary: credentials),
!convertedAccount.password.isEmpty
else {
presentError("Failed to get details from File Provider Extension. Retrying.")
reattempt()
return
}
let serverPathString = serverPath as String
itemServerRelativePath = serverPathString
account = convertedAccount
await sharesTableView?.deselectAll(self)
capabilities = await fetchCapabilities()
guard capabilities != nil else { return }
guard capabilities?.filesSharing?.apiEnabled == true else {
presentError("Server does not support shares.")
return
}
itemMetadata = await fetchItemMetadata(itemRelativePath: serverPathString)
guard itemMetadata?.permissions.contains("R") == true else {
presentError("This file cannot be shared.")
return
}
shares = await fetch(
itemIdentifier: itemIdentifier, itemRelativePath: serverPathString
)
} catch let error {
presentError("Could not reload data: \(error), will try again.")
reattempt()
}
}
private func serviceConnection(url: URL) async throws -> FPUIExtensionService {
let services = try await FileManager().fileProviderServicesForItem(at: url)
guard let service = services[fpUiExtensionServiceName] else {
Logger.sharesDataSource.error("Couldn't get service, required service not present")
throw NSFileProviderError(.providerNotFound)
}
let connection: NSXPCConnection
connection = try await service.fileProviderConnection()
connection.remoteObjectInterface = NSXPCInterface(with: FPUIExtensionService.self)
connection.interruptionHandler = {
Logger.sharesDataSource.error("Service connection interrupted")
}
connection.resume()
guard let proxy = connection.remoteObjectProxy as? FPUIExtensionService else {
throw NSFileProviderError(.serverUnreachable)
}
return proxy
}
private func fetch(
itemIdentifier: NSFileProviderItemIdentifier, itemRelativePath: String
) async -> [NKShare] {
Task { @MainActor in uiDelegate?.fetchStarted() }
defer { Task { @MainActor in uiDelegate?.fetchFinished() } }
let rawIdentifier = itemIdentifier.rawValue
Logger.sharesDataSource.info("Fetching shares for item \(rawIdentifier, privacy: .public)")
guard let kit = kit else {
self.presentError("NextcloudKit instance is unavailable, cannot fetch shares!")
return []
}
let parameter = NKShareParameter(path: itemRelativePath)
return await withCheckedContinuation { continuation in
kit.readShares(parameters: parameter) { account, shares, data, error in
let shareCount = shares?.count ?? 0
Logger.sharesDataSource.info("Received \(shareCount, privacy: .public) shares")
defer { continuation.resume(returning: shares ?? []) }
guard error == .success else {
self.presentError("Error fetching shares: \(error.errorDescription)")
return
}
}
}
}
private func fetchCapabilities() async -> Capabilities? {
return await withCheckedContinuation { continuation in
kit?.getCapabilities { account, capabilitiesJson, error in
guard error == .success, let capabilitiesJson = capabilitiesJson else {
self.presentError("Error getting server caps: \(error.errorDescription)")
continuation.resume(returning: nil)
return
}
Logger.sharesDataSource.info("Successfully retrieved server share capabilities")
continuation.resume(returning: Capabilities(data: capabilitiesJson))
}
}
}
private func fetchItemMetadata(itemRelativePath: String) async -> NKFile? {
guard let kit = kit else {
presentError("Could not fetch item metadata as NextcloudKit instance is unavailable")
return nil
}
func slashlessPath(_ string: String) -> String {
var strCopy = string
if strCopy.hasPrefix("/") {
strCopy.removeFirst()
}
if strCopy.hasSuffix("/") {
strCopy.removeLast()
}
return strCopy
}
let nkCommon = kit.nkCommonInstance
let urlBase = slashlessPath(nkCommon.urlBase)
let davSuffix = slashlessPath(nkCommon.dav)
let userId = nkCommon.userId
let itemRelPath = slashlessPath(itemRelativePath)
let itemFullServerPath = "\(urlBase)/\(davSuffix)/files/\(userId)/\(itemRelPath)"
return await withCheckedContinuation { continuation in
kit.readFileOrFolder(serverUrlFileName: itemFullServerPath, depth: "0") {
account, files, data, error in
guard error == .success else {
self.presentError("Error getting item metadata: \(error.errorDescription)")
continuation.resume(returning: nil)
return
}
Logger.sharesDataSource.info("Successfully retrieved item metadata")
continuation.resume(returning: files.first)
}
}
}
private func presentError(_ errorString: String) {
Logger.sharesDataSource.error("\(errorString, privacy: .public)")
Task { @MainActor in self.uiDelegate?.showError(errorString) }
}
// MARK: - NSTableViewDataSource protocol methods
@objc func numberOfRows(in tableView: NSTableView) -> Int {
shares.count
}
// MARK: - NSTableViewDelegate protocol methods
@objc func tableView(
_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int
) -> NSView? {
let share = shares[row]
guard let view = tableView.makeView(
withIdentifier: shareItemViewIdentifier, owner: self
) as? ShareTableItemView else {
Logger.sharesDataSource.error("Acquired item view from table is not a share item view!")
return nil
}
view.share = share
return view
}
@objc func tableViewSelectionDidChange(_ notification: Notification) {
guard let selectedRow = sharesTableView?.selectedRow, selectedRow >= 0 else {
Task { @MainActor in uiDelegate?.hideOptions(self) }
return
}
let share = shares[selectedRow]
Task { @MainActor in uiDelegate?.showOptions(share: share) }
}
}

View File

@ -0,0 +1,192 @@
//
// ShareViewController.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 21/2/24.
//
import AppKit
import FileProvider
import NextcloudKit
import OSLog
import QuickLookThumbnailing
class ShareViewController: NSViewController, ShareViewDataSourceUIDelegate {
let shareDataSource = ShareTableViewDataSource()
let itemIdentifiers: [NSFileProviderItemIdentifier]
@IBOutlet weak var fileNameIcon: NSImageView!
@IBOutlet weak var fileNameLabel: NSTextField!
@IBOutlet weak var descriptionLabel: NSTextField!
@IBOutlet weak var createButton: NSButton!
@IBOutlet weak var closeButton: NSButton!
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var optionsView: ShareOptionsView!
@IBOutlet weak var splitView: NSSplitView!
@IBOutlet weak var loadingEffectView: NSVisualEffectView!
@IBOutlet weak var loadingIndicator: NSProgressIndicator!
@IBOutlet weak var errorMessageStackView: NSStackView!
@IBOutlet weak var errorTextLabel: NSTextField!
@IBOutlet weak var noSharesLabel: NSTextField!
public override var nibName: NSNib.Name? {
return NSNib.Name(self.className)
}
var actionViewController: DocumentActionViewController! {
return parent as? DocumentActionViewController
}
init(_ itemIdentifiers: [NSFileProviderItemIdentifier]) {
self.itemIdentifiers = itemIdentifiers
super.init(nibName: nil, bundle: nil)
guard let firstItem = itemIdentifiers.first else {
Logger.shareViewController.error("called without items")
closeAction(self)
return
}
Logger.shareViewController.info(
"""
Instantiated with itemIdentifiers:
\(itemIdentifiers.map { $0.rawValue }, privacy: .public)
"""
)
Task {
await processItemIdentifier(firstItem)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
dismissError(self)
hideOptions(self)
}
@IBAction func closeAction(_ sender: Any) {
actionViewController.extensionContext.completeRequest()
}
private func processItemIdentifier(_ itemIdentifier: NSFileProviderItemIdentifier) async {
guard let manager = NSFileProviderManager(for: actionViewController.domain) else {
fatalError("NSFileProviderManager isn't expected to fail")
}
do {
let itemUrl = try await manager.getUserVisibleURL(for: itemIdentifier)
guard itemUrl.startAccessingSecurityScopedResource() else {
Logger.shareViewController.error("Could not access scoped resource for item url!")
return
}
await updateDisplay(itemUrl: itemUrl)
shareDataSource.uiDelegate = self
shareDataSource.sharesTableView = tableView
shareDataSource.loadItem(url: itemUrl)
optionsView.dataSource = shareDataSource
itemUrl.stopAccessingSecurityScopedResource()
} catch let error {
let errorString = "Error processing item: \(error)"
Logger.shareViewController.error("\(errorString, privacy: .public)")
fileNameLabel.stringValue = "Unknown item"
descriptionLabel.stringValue = errorString
}
}
private func updateDisplay(itemUrl: URL) async {
fileNameLabel.stringValue = itemUrl.lastPathComponent
let request = QLThumbnailGenerator.Request(
fileAt: itemUrl,
size: CGSize(width: 128, height: 128),
scale: 1.0,
representationTypes: .icon
)
let generator = QLThumbnailGenerator.shared
let fileThumbnail = await withCheckedContinuation { continuation in
generator.generateRepresentations(for: request) { thumbnail, type, error in
if thumbnail == nil || error != nil {
Logger.shareViewController.error(
"""
Could not get thumbnail: \(error, privacy: .public)
"""
)
}
continuation.resume(returning: thumbnail)
}
}
fileNameIcon.image = fileThumbnail?.nsImage
let resourceValues = try? itemUrl.resourceValues(
forKeys: [.fileSizeKey, .contentModificationDateKey]
)
var sizeDesc = "Unknown size"
var modDesc = "Unknown modification date"
if let fileSize = resourceValues?.fileSize {
sizeDesc = ByteCountFormatter().string(fromByteCount: Int64(fileSize))
}
if let modificationDate = resourceValues?.contentModificationDate {
let modDateString = DateFormatter.localizedString(
from: modificationDate, dateStyle: .short, timeStyle: .short
)
modDesc = "Last modified: \(modDateString)"
}
descriptionLabel.stringValue = "\(sizeDesc) · \(modDesc)"
}
@IBAction func dismissError(_ sender: Any) {
errorMessageStackView.isHidden = true
}
@IBAction func createShare(_ sender: Any) {
guard let kit = shareDataSource.kit else { return }
optionsView.kit = kit
optionsView.createMode = true
tableView.deselectAll(self)
if !splitView.arrangedSubviews.contains(optionsView) {
splitView.addArrangedSubview(optionsView)
optionsView.isHidden = false
}
}
func fetchStarted() {
loadingEffectView.isHidden = false
loadingIndicator.startAnimation(self)
}
func fetchFinished() {
noSharesLabel.isHidden = !shareDataSource.shares.isEmpty
loadingEffectView.isHidden = true
loadingIndicator.stopAnimation(self)
}
func hideOptions(_ sender: Any) {
if sender as? ShareTableViewDataSource == shareDataSource, optionsView.createMode {
// Do not hide options if the table view has had everything deselected when we set the
// options view to be in create mode
return
}
splitView.removeArrangedSubview(optionsView)
optionsView.isHidden = true
}
func showOptions(share: NKShare) {
guard let kit = shareDataSource.kit else { return }
optionsView.kit = kit
optionsView.controller = ShareController(share: share, kit: kit)
if !splitView.arrangedSubviews.contains(optionsView) {
splitView.addArrangedSubview(optionsView)
optionsView.isHidden = false
}
}
func showError(_ errorString: String) {
errorMessageStackView.isHidden = false
errorTextLabel.stringValue = errorString
}
}

View File

@ -0,0 +1,532 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
<capability name="Image references" minToolsVersion="12.0"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController" customModule="FileProviderUIExt" customModuleProvider="target">
<connections>
<outlet property="closeButton" destination="aDA-n9-Zly" id="niR-Ad-FEa"/>
<outlet property="createButton" destination="BMA-BP-wHc" id="HEq-bN-i9V"/>
<outlet property="descriptionLabel" destination="gX0-nE-MrU" id="RoY-u1-1on"/>
<outlet property="errorMessageStackView" destination="dFs-Gh-2WQ" id="kkQ-Uq-xk7"/>
<outlet property="errorTextLabel" destination="770-HW-oC7" id="gfn-SV-TNM"/>
<outlet property="fileNameIcon" destination="zSV-DV-Ray" id="336-e0-CEo"/>
<outlet property="fileNameLabel" destination="slV-H6-zJ3" id="DPp-sN-Yff"/>
<outlet property="loadingEffectView" destination="1ud-mC-gQV" id="HMT-Sb-Axl"/>
<outlet property="loadingIndicator" destination="acb-Yu-Zpm" id="9Jf-dE-7LE"/>
<outlet property="noSharesLabel" destination="K3D-6U-Cbr" id="zDP-E4-9bg"/>
<outlet property="optionsView" destination="EXb-m8-yzj" id="uAb-lv-EZ4"/>
<outlet property="splitView" destination="91w-SP-6sl" id="20T-gQ-SPY"/>
<outlet property="tableView" destination="vb0-a6-eeH" id="KQo-eg-dba"/>
<outlet property="view" destination="Jw6-da-U8j" id="5Ek-F1-w7C"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<view translatesAutoresizingMaskIntoConstraints="NO" id="Jw6-da-U8j">
<rect key="frame" x="0.0" y="0.0" width="500" height="829"/>
<subviews>
<splitView arrangesAllSubviews="NO" dividerStyle="thin" translatesAutoresizingMaskIntoConstraints="NO" id="91w-SP-6sl">
<rect key="frame" x="10" y="10" width="480" height="809"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wGI-UV-bB3">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9kj-aB-aJh">
<rect key="frame" x="0.0" y="437" width="480" height="43"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zSV-DV-Ray">
<rect key="frame" x="0.0" y="-3.5" width="43" height="50"/>
<constraints>
<constraint firstAttribute="width" secondItem="zSV-DV-Ray" secondAttribute="height" multiplier="1:1" id="NSH-gA-7lL"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="doc" catalog="system" id="laO-OA-5sJ"/>
</imageView>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oSY-fV-uws">
<rect key="frame" x="51" y="0.0" width="375" height="43"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="slV-H6-zJ3">
<rect key="frame" x="-2" y="24" width="78" height="19"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="File name" id="Uuo-1j-to8">
<font key="font" metaFont="systemBold" size="16"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gX0-nE-MrU">
<rect key="frame" x="-2" y="0.0" width="146" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="File size · Last modified" id="1GC-Gr-x29">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="BMA-BP-wHc">
<rect key="frame" x="434" y="-3.5" width="18.5" height="51"/>
<buttonCell key="cell" type="bevel" title="Create share" bezelStyle="regularSquare" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="cdh-AC-lt4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<imageReference key="image" image="plus" catalog="system" symbolScale="large"/>
</buttonCell>
<connections>
<action selector="createShare:" target="-2" id="NLV-ZM-y1w"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="aDA-n9-Zly">
<rect key="frame" x="460" y="-5" width="20" height="54"/>
<buttonCell key="cell" type="bevel" title="Close" bezelStyle="rounded" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="zNR-DC-3xZ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<imageReference key="image" image="xmark.circle.fill" catalog="system" symbolScale="large"/>
</buttonCell>
<connections>
<action selector="closeAction:" target="-2" id="D9k-gc-AcN"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="43" id="C79-af-CZw"/>
<constraint firstItem="oSY-fV-uws" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="I6w-n6-pgZ"/>
<constraint firstAttribute="bottom" secondItem="BMA-BP-wHc" secondAttribute="bottom" id="RE6-Rg-yv9"/>
<constraint firstItem="zSV-DV-Ray" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="X4V-Vr-Q7m"/>
<constraint firstItem="BMA-BP-wHc" firstAttribute="top" secondItem="9kj-aB-aJh" secondAttribute="top" id="hrc-la-YmY"/>
<constraint firstItem="aDA-n9-Zly" firstAttribute="height" secondItem="9kj-aB-aJh" secondAttribute="height" id="qCh-8Z-jcN"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<stackView distribution="fillProportionally" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" verticalCompressionResistancePriority="1000" detachesHiddenViews="YES" id="dFs-Gh-2WQ">
<rect key="frame" x="0.0" y="308" width="480" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="770-HW-oC7">
<rect key="frame" x="0.0" y="0.0" width="411" height="40"/>
<textFieldCell key="cell" selectable="YES" borderStyle="border" alignment="left" title="A long error message that provides detail about why some operations with the shares failed" drawsBackground="YES" id="Jvl-L9-x8K">
<font key="font" metaFont="systemSemibold" size="13"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
<color key="backgroundColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="qfq-F1-w0b">
<rect key="frame" x="416" y="-4" width="67" height="47"/>
<buttonCell key="cell" type="bevel" title="Dismiss" bezelStyle="regularSquare" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="dLP-yX-dyk">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<connections>
<action selector="dismissError:" target="-2" id="qLa-KM-cYK"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="770-HW-oC7" firstAttribute="top" secondItem="dFs-Gh-2WQ" secondAttribute="top" id="2Y8-H2-wTQ"/>
<constraint firstItem="qfq-F1-w0b" firstAttribute="centerY" secondItem="dFs-Gh-2WQ" secondAttribute="centerY" id="e6n-1Y-kUh"/>
<constraint firstAttribute="bottom" secondItem="770-HW-oC7" secondAttribute="bottom" id="fHi-My-Qyc"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<scrollView autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZzY-1Z-3xa">
<rect key="frame" x="0.0" y="0.0" width="480" height="300"/>
<clipView key="contentView" drawsBackground="NO" id="Ixg-th-Nw0">
<rect key="frame" x="1" y="1" width="478" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowSizeStyle="automatic" viewBased="YES" id="vb0-a6-eeH">
<rect key="frame" x="0.0" y="0.0" width="478" height="298"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="AutomaticTableColumnIdentifier.0" width="466" minWidth="40" maxWidth="1000" id="3Eb-aD-Ueu">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="mEe-4r-5cx">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<constraints>
<constraint firstAttribute="height" constant="300" id="FAW-Ws-zy7"/>
</constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="hQs-hA-bDq">
<rect key="frame" x="1" y="284" width="478" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="ryi-jk-XFM">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<constraints>
<constraint firstItem="ZzY-1Z-3xa" firstAttribute="leading" secondItem="wGI-UV-bB3" secondAttribute="leading" id="528-fv-ZfM"/>
<constraint firstItem="9kj-aB-aJh" firstAttribute="top" secondItem="wGI-UV-bB3" secondAttribute="top" id="EOP-84-2ZH"/>
<constraint firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="Kt7-wJ-gb5"/>
<constraint firstItem="9kj-aB-aJh" firstAttribute="leading" secondItem="wGI-UV-bB3" secondAttribute="leading" id="UyT-D1-Awv"/>
<constraint firstAttribute="trailing" secondItem="9kj-aB-aJh" secondAttribute="trailing" id="kua-OU-nbq"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<view translatesAutoresizingMaskIntoConstraints="NO" id="EXb-m8-yzj" customClass="ShareOptionsView" customModule="FileProviderUIExt" customModuleProvider="target">
<rect key="frame" x="0.0" y="481" width="480" height="328"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="T4D-9c-PyA">
<rect key="frame" x="10" y="10" width="460" height="308"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" verticalCompressionResistancePriority="749" translatesAutoresizingMaskIntoConstraints="NO" id="AWy-Qo-wHH">
<rect key="frame" x="-2" y="292" width="464" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="Share options" id="fzf-0v-uHo">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aRB-vw-b4r">
<rect key="frame" x="-3" y="263" width="467" height="25"/>
<popUpButtonCell key="cell" type="push" title="Public link share" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="JhA-rv-1xy" id="S60-Qi-URJ">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="yag-Vc-J7Y">
<items>
<menuItem title="Public link share" state="on" id="JhA-rv-1xy"/>
<menuItem title="User share" id="CpL-qc-lAA"/>
<menuItem title="Group share" id="bnp-aV-ZvE"/>
<menuItem title="Email share" id="5DB-JD-Ij0">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Federated cloud share" id="RZP-ME-baz">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Circle share" id="yDE-lS-rJZ">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Talk conversation share" id="aHo-Mr-vTn">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="shareTypePickerAction:" target="EXb-m8-yzj" id="LN7-TC-RvV"/>
</connections>
</popUpButton>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="f8k-Ae-oQc">
<rect key="frame" x="0.0" y="240" width="460" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Share recipient" bezelStyle="round" id="Ahi-gU-lmO">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CXW-ZO-B2f">
<rect key="frame" x="0.0" y="213" width="460" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Share label" bezelStyle="round" id="ZsJ-zc-mFT">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luZ-Vf-V24">
<rect key="frame" x="-2" y="191" width="175" height="18"/>
<buttonCell key="cell" type="check" title="Allow upload and editing" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="vOP-1k-c75">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2TN-96-apv">
<rect key="frame" x="-2" y="170" width="117" height="18"/>
<buttonCell key="cell" type="check" title="Hide download" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="6Eu-NS-uZ7">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ylD-hi-7Oq">
<rect key="frame" x="-2" y="149" width="132" height="18"/>
<buttonCell key="cell" type="check" title="Password protect" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="MWC-hf-0pc">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="passwordCheckboxAction:" target="EXb-m8-yzj" id="QMn-RM-jdf"/>
</connections>
</button>
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pkv-9L-nhv">
<rect key="frame" x="0.0" y="123" width="460" height="22"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Enter a new password" usesSingleLineMode="YES" bezelStyle="round" id="hcA-we-oYG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
</secureTextField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HaL-4Z-csA">
<rect key="frame" x="-2" y="101" width="117" height="18"/>
<buttonCell key="cell" type="check" title="Expiration date" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="r1a-iX-8Xo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="expirationDateCheckboxAction:" target="EXb-m8-yzj" id="KHG-U0-rsG"/>
</connections>
</button>
<datePicker verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="46m-5F-ME5">
<rect key="frame" x="0.0" y="73" width="463" height="28"/>
<datePickerCell key="cell" borderStyle="bezel" alignment="left" useCurrentDate="YES" id="EHU-Gu-Dfh">
<font key="font" metaFont="system"/>
<date key="date" timeIntervalSinceReferenceDate="734115148.45631897">
<!--2024-04-06 16:52:28 +0000-->
</date>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
</datePickerCell>
</datePicker>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="c10-ub-U31">
<rect key="frame" x="-2" y="51" width="132" height="18"/>
<buttonCell key="cell" type="check" title="Note for recipient" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="oLA-Nu-fzO">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="noteForRecipientCheckboxAction:" target="EXb-m8-yzj" id="j7a-Tc-uiu"/>
</connections>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="io6-Kg-fLl">
<rect key="frame" x="0.0" y="25" width="460" height="22"/>
<textFieldCell key="cell" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Note for the recipient" bezelStyle="round" id="z5W-BH-NnM">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<stackView distribution="fillEqually" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mWb-eX-nfh">
<rect key="frame" x="0.0" y="0.0" width="460" height="20"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Bmf-yY-Y7V">
<rect key="frame" x="-7" y="-7" width="240" height="32"/>
<buttonCell key="cell" type="push" title="Delete" bezelStyle="rounded" image="trash.fill" catalog="system" imagePosition="leading" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Kb4-Qg-9Ag">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="systemRedColor" catalog="System" colorSpace="catalog"/>
<connections>
<action selector="delete:" target="EXb-m8-yzj" id="PZq-SH-QVa"/>
</connections>
</button>
<button horizontalHuggingPriority="249" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PaV-TL-cbq">
<rect key="frame" x="227" y="-7" width="240" height="32"/>
<buttonCell key="cell" type="push" title="Save" bezelStyle="rounded" image="arrow.up.square.fill" catalog="system" imagePosition="trailing" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="O1I-T0-iRC">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<color key="bezelColor" name="AccentColor"/>
<connections>
<action selector="save:" target="EXb-m8-yzj" id="O4N-dj-SlN"/>
</connections>
</button>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="46m-5F-ME5" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="5LB-gI-S8e"/>
<constraint firstItem="io6-Kg-fLl" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="94h-0I-pw6"/>
<constraint firstAttribute="trailing" secondItem="pkv-9L-nhv" secondAttribute="trailing" id="IFt-uJ-etl"/>
<constraint firstAttribute="trailing" secondItem="CXW-ZO-B2f" secondAttribute="trailing" id="Uvk-cg-D23"/>
<constraint firstAttribute="trailing" secondItem="aRB-vw-b4r" secondAttribute="trailing" id="Zp9-vZ-oxU"/>
<constraint firstItem="pkv-9L-nhv" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="bFe-9X-gFR"/>
<constraint firstItem="AWy-Qo-wHH" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="dyr-SA-Kmy"/>
<constraint firstAttribute="trailing" secondItem="46m-5F-ME5" secondAttribute="trailing" id="gV6-Jx-VH4"/>
<constraint firstAttribute="bottom" secondItem="mWb-eX-nfh" secondAttribute="bottom" id="iLe-Zw-oO2"/>
<constraint firstAttribute="trailing" secondItem="io6-Kg-fLl" secondAttribute="trailing" id="mkY-UE-SAy"/>
<constraint firstItem="AWy-Qo-wHH" firstAttribute="top" secondItem="T4D-9c-PyA" secondAttribute="top" id="ndl-52-MnV"/>
<constraint firstItem="CXW-ZO-B2f" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="oWL-JR-HTk"/>
<constraint firstItem="mWb-eX-nfh" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="wiX-kw-Ohy"/>
<constraint firstItem="aRB-vw-b4r" firstAttribute="leading" secondItem="T4D-9c-PyA" secondAttribute="leading" id="xPY-Ll-QCz"/>
<constraint firstAttribute="trailing" secondItem="mWb-eX-nfh" secondAttribute="trailing" id="yUj-je-uXE"/>
<constraint firstAttribute="trailing" secondItem="AWy-Qo-wHH" secondAttribute="trailing" id="zxC-eX-HrE"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="T4D-9c-PyA" firstAttribute="leading" secondItem="EXb-m8-yzj" secondAttribute="leading" constant="10" id="Dhe-wL-ygv"/>
<constraint firstItem="T4D-9c-PyA" firstAttribute="top" secondItem="EXb-m8-yzj" secondAttribute="top" constant="10" id="JrC-o6-So3"/>
<constraint firstAttribute="trailing" secondItem="T4D-9c-PyA" secondAttribute="trailing" constant="10" id="gs6-zf-D6h"/>
<constraint firstAttribute="bottom" secondItem="T4D-9c-PyA" secondAttribute="bottom" constant="10" id="r5C-pm-lv4"/>
</constraints>
<connections>
<outlet property="circleShare" destination="yDE-lS-rJZ" id="yHV-25-RJd"/>
<outlet property="deleteButton" destination="Bmf-yY-Y7V" id="Wd0-LR-DV5"/>
<outlet property="emailShareMenuItem" destination="5DB-JD-Ij0" id="LD5-P8-V2l"/>
<outlet property="expirationDateCheckbox" destination="HaL-4Z-csA" id="vIf-I8-e3i"/>
<outlet property="expirationDatePicker" destination="46m-5F-ME5" id="eGk-fe-IIf"/>
<outlet property="federatedCloudShareMenuItem" destination="RZP-ME-baz" id="quL-N2-y1z"/>
<outlet property="groupShareMenuItem" destination="bnp-aV-ZvE" id="8iU-IP-YXK"/>
<outlet property="hideDownloadCheckbox" destination="2TN-96-apv" id="OPr-4x-aiK"/>
<outlet property="labelTextField" destination="CXW-ZO-B2f" id="otQ-jh-Psr"/>
<outlet property="noteForRecipientCheckbox" destination="c10-ub-U31" id="aG6-4P-cBv"/>
<outlet property="noteTextField" destination="io6-Kg-fLl" id="JKm-A1-SqR"/>
<outlet property="optionsTitleTextField" destination="AWy-Qo-wHH" id="BjX-oW-0Lp"/>
<outlet property="passwordProtectCheckbox" destination="ylD-hi-7Oq" id="qdw-aF-uh2"/>
<outlet property="passwordSecureField" destination="pkv-9L-nhv" id="992-i5-CPF"/>
<outlet property="publicLinkShareMenuItem" destination="JhA-rv-1xy" id="usv-L6-M7k"/>
<outlet property="saveButton" destination="PaV-TL-cbq" id="OvF-Le-oQj"/>
<outlet property="shareRecipientTextField" destination="f8k-Ae-oQc" id="bfc-Vn-Zu9"/>
<outlet property="shareTypePicker" destination="aRB-vw-b4r" id="Dfx-Dw-zEM"/>
<outlet property="talkConversationShare" destination="aHo-Mr-vTn" id="hxF-qw-VQO"/>
<outlet property="uploadEditPermissionCheckbox" destination="luZ-Vf-V24" id="ojW-WP-98U"/>
<outlet property="userShareMenuItem" destination="CpL-qc-lAA" id="R2q-dc-og5"/>
</connections>
</view>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="EXb-m8-yzj" secondAttribute="trailing" id="9yJ-jE-Iin"/>
<constraint firstAttribute="bottom" secondItem="EXb-m8-yzj" secondAttribute="bottom" id="IDB-la-fxl"/>
<constraint firstItem="EXb-m8-yzj" firstAttribute="leading" secondItem="91w-SP-6sl" secondAttribute="leading" id="imf-Yj-oUg"/>
</constraints>
<holdingPriorities>
<real value="250"/>
<real value="250"/>
</holdingPriorities>
</splitView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="K3D-6U-Cbr">
<rect key="frame" x="8" y="474" width="484" height="31"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="No shares" id="byC-34-Vtu">
<font key="font" textStyle="largeTitle" name=".SFNS-Regular"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<visualEffectView blendingMode="withinWindow" material="HUDWindow" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="1ud-mC-gQV">
<rect key="frame" x="10" y="339" width="480" height="300"/>
<subviews>
<progressIndicator wantsLayer="YES" maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="acb-Yu-Zpm">
<rect key="frame" x="224" y="134" width="32" height="32"/>
</progressIndicator>
</subviews>
<constraints>
<constraint firstItem="acb-Yu-Zpm" firstAttribute="centerY" secondItem="1ud-mC-gQV" secondAttribute="centerY" id="Dhf-yv-2Wr"/>
<constraint firstItem="acb-Yu-Zpm" firstAttribute="centerX" secondItem="1ud-mC-gQV" secondAttribute="centerX" id="Kzb-iw-xCx"/>
</constraints>
</visualEffectView>
</subviews>
<constraints>
<constraint firstItem="1ud-mC-gQV" firstAttribute="bottom" secondItem="ZzY-1Z-3xa" secondAttribute="bottom" id="HTR-Xt-ACu"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="top" secondItem="ZzY-1Z-3xa" secondAttribute="top" id="Hjv-Wk-Rk1"/>
<constraint firstItem="91w-SP-6sl" firstAttribute="top" secondItem="Jw6-da-U8j" secondAttribute="top" constant="10" id="JFe-jp-e9y"/>
<constraint firstAttribute="width" constant="500" id="KBX-aG-ZDU"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="Kpk-6o-ao7"/>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="380" id="LND-hR-7Co"/>
<constraint firstAttribute="trailing" secondItem="91w-SP-6sl" secondAttribute="trailing" constant="10" id="XrV-hl-kxC"/>
<constraint firstAttribute="bottom" secondItem="91w-SP-6sl" secondAttribute="bottom" constant="10" id="Z4B-85-NR5"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="height" secondItem="ZzY-1Z-3xa" secondAttribute="height" id="dfF-9z-iG9"/>
<constraint firstItem="91w-SP-6sl" firstAttribute="leading" secondItem="Jw6-da-U8j" secondAttribute="leading" constant="10" id="elm-Nc-tc0"/>
<constraint firstItem="1ud-mC-gQV" firstAttribute="leading" secondItem="ZzY-1Z-3xa" secondAttribute="leading" id="kSz-tV-EFM"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="leading" secondItem="ZzY-1Z-3xa" secondAttribute="leading" id="lut-va-B3T"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="trailing" secondItem="ZzY-1Z-3xa" secondAttribute="trailing" id="vRC-IM-ovt"/>
<constraint firstItem="K3D-6U-Cbr" firstAttribute="centerY" secondItem="ZzY-1Z-3xa" secondAttribute="centerY" id="wFD-ph-Bsp"/>
</constraints>
<point key="canvasLocation" x="-270" y="79"/>
</view>
</objects>
<resources>
<image name="arrow.up.square.fill" catalog="system" width="15" height="14"/>
<image name="doc" catalog="system" width="14" height="16"/>
<image name="plus" catalog="system" width="18" height="17"/>
<image name="trash.fill" catalog="system" width="15" height="17"/>
<image name="xmark.circle.fill" catalog="system" width="20" height="20"/>
<namedColor name="AccentColor">
<color red="0.0" green="0.46000000000000002" blue="0.89000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
</resources>
</document>

View File

@ -0,0 +1,17 @@
//
// ShareViewDataSourceUIDelegate.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 28/2/24.
//
import Foundation
import NextcloudKit
protocol ShareViewDataSourceUIDelegate {
func fetchStarted()
func fetchFinished()
func hideOptions(_ sender: Any)
func showOptions(share: NKShare)
func showError(_ errorString: String)
}

View File

@ -0,0 +1,60 @@
//
// ShareeSuggestionsDataSource.swift
// FileProviderUIExt
//
// Created by Claudio Cambra on 2/4/24.
//
import Foundation
import NextcloudKit
import OSLog
import SuggestionsTextFieldKit
class ShareeSuggestionsDataSource: SuggestionsDataSource {
let kit: NextcloudKit
var suggestions: [Suggestion] = []
var inputString: String = "" {
didSet { Task { await updateSuggestions() } }
}
init(kit: NextcloudKit) {
self.kit = kit
}
private func updateSuggestions() async {
let sharees = await fetchSharees(search: inputString)
Logger.shareeDataSource.info("Fetched \(sharees.count, privacy: .public) sharees.")
suggestions = suggestionsFromSharees(sharees)
NotificationCenter.default.post(name: SuggestionsChangedNotificationName, object: self)
}
private func fetchSharees(search: String) async -> [NKSharee] {
Logger.shareeDataSource.debug("Searching sharees with: \(search, privacy: .public)")
return await withCheckedContinuation { continuation in
kit.searchSharees(
search: inputString,
page: 1,
perPage: 20,
completion: { account, sharees, data, error in
defer { continuation.resume(returning: sharees ?? []) }
guard error == .success else {
Logger.shareeDataSource.error(
"Error fetching sharees: \(error.description, privacy: .public)"
)
return
}
}
)
}
}
private func suggestionsFromSharees(_ sharees: [NKSharee]) -> [Suggestion] {
return sharees.map {
Suggestion(
imageName: "person.fill",
displayText: $0.label.isEmpty ? $0.name : $0.label,
data: $0
)
}
}
}

View File

@ -3,29 +3,29 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E52965C6FA001E0C6A /* NextcloudKit */; };
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; };
5307A6EB2965DB8D001E0C6A /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6EA2965DB8D001E0C6A /* RealmSwift */; };
5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */; };
5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */; };
5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */; };
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */; };
5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */; };
5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */; };
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */; };
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; };
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */; };
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; };
5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */; };
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */; };
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */; };
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */; };
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */; };
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */; };
536EFC36295E3C1100F4CB13 /* NextcloudAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */; };
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5374FD432B95EE1400C78D54 /* ShareController.swift */; };
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */; };
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537630902B85F4980026BFAB /* ShareViewController.xib */; };
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630922B85F4B00026BFAB /* ShareViewController.swift */; };
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */; };
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; };
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; };
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; };
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396E27F4765000FA63D5 /* FileProviderItem.swift */; };
538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */; };
538E397627F4765000FA63D5 /* FileProviderExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
53903D1E2956164F00D0B308 /* NCDesktopClientSocketKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D0E2956164F00D0B308 /* NCDesktopClientSocketKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
53903D212956164F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */; };
@ -38,10 +38,17 @@
53903D352956184400D0B308 /* LocalSocketClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 539158B127BE891500816F56 /* LocalSocketClient.h */; settings = {ATTRIBUTES = (Public, ); }; };
53903D37295618A400D0B308 /* LineProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D36295618A400D0B308 /* LineProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
539158AC27BE71A900816F56 /* FinderSyncSocketLineProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 539158AB27BE71A900816F56 /* FinderSyncSocketLineProcessor.m */; };
53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D056302970594F00988392 /* LocalFilesUtils.swift */; };
53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */; };
53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */; };
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */; };
53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */; };
53C331B62BCD3AFF0093D38B /* NextcloudFileProviderKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */; };
53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D666602B70C9A70042C03D /* FileProviderConfig.swift */; };
53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */; };
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */; };
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53FE14532B8E1219006C4193 /* NextcloudKit */; };
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */; };
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */; };
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */; };
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */; };
C2B573BA1B1CD91E00303B36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
C2B573D21B1CD94B00303B36 /* main.m in Resources */ = {isa = PBXBuildFile; fileRef = C2B573B91B1CD91E00303B36 /* main.m */; };
C2B573DE1B1CD9CE00303B36 /* FinderSync.m in Sources */ = {isa = PBXBuildFile; fileRef = C2B573DD1B1CD9CE00303B36 /* FinderSync.m */; };
@ -140,23 +147,23 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudFilesDatabaseManager.swift; sourceTree = "<group>"; };
5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudItemMetadataTable.swift; sourceTree = "<group>"; };
5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudLocalFileMetadataTable.swift; sourceTree = "<group>"; };
5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderMaterialisedEnumerationObserver.swift; sourceTree = "<group>"; };
5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKError+Extensions.swift"; sourceTree = "<group>"; };
5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+Directories.swift"; sourceTree = "<group>"; };
5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+LocalFiles.swift"; sourceTree = "<group>"; };
531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = "<group>"; };
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = "<group>"; };
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCommunicationService.swift; sourceTree = "<group>"; };
5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FileProviderExt-Bridging-Header.h"; sourceTree = "<group>"; };
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+Thumbnailing.swift"; sourceTree = "<group>"; };
5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+Extensions.swift"; sourceTree = "<group>"; };
535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareeSuggestionsDataSource.swift; sourceTree = "<group>"; };
536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderSocketLineProcessor.swift; sourceTree = "<group>"; };
536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudAccount.swift; sourceTree = "<group>"; };
5374FD432B95EE1400C78D54 /* ShareController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareController.swift; sourceTree = "<group>"; };
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = "<group>"; };
537630902B85F4980026BFAB /* ShareViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareViewController.xib; sourceTree = "<group>"; };
537630922B85F4B00026BFAB /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionServiceSource.swift; sourceTree = "<group>"; };
537630962B860D920026BFAB /* FPUIExtensionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FPUIExtensionService.swift; sourceTree = "<group>"; };
538E396727F4765000FA63D5 /* FileProviderExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = "<group>"; };
538E396E27F4765000FA63D5 /* FileProviderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItem.swift; sourceTree = "<group>"; };
538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderEnumerator.swift; sourceTree = "<group>"; };
538E397227F4765000FA63D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
538E397327F4765000FA63D5 /* FileProviderExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExt.entitlements; sourceTree = "<group>"; };
53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NCDesktopClientSocketKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -167,10 +174,17 @@
539158AB27BE71A900816F56 /* FinderSyncSocketLineProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FinderSyncSocketLineProcessor.m; sourceTree = "<group>"; };
539158B127BE891500816F56 /* LocalSocketClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalSocketClient.h; sourceTree = "<group>"; };
539158B227BEC98A00816F56 /* LocalSocketClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalSocketClient.m; sourceTree = "<group>"; };
53D056302970594F00988392 /* LocalFilesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFilesUtils.swift; sourceTree = "<group>"; };
53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderEnumerator+SyncEngine.swift"; sourceTree = "<group>"; };
53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudItemMetadataTable+NKFile.swift"; sourceTree = "<group>"; };
53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderUIExt.appex; sourceTree = BUILT_PRODUCTS_DIR; };
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = "<group>"; };
53B979852B84C81F002DA742 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
53D666602B70C9A70042C03D /* FileProviderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderConfig.swift; sourceTree = "<group>"; };
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+ClientInterface.swift"; sourceTree = "<group>"; };
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableViewDataSource.swift; sourceTree = "<group>"; };
53FE14572B8E3A7C006C4193 /* FileProviderUIExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExt.entitlements; sourceTree = "<group>"; };
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableItemView.swift; sourceTree = "<group>"; };
53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKShare+Extensions.swift"; sourceTree = "<group>"; };
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewDataSourceUIDelegate.swift; sourceTree = "<group>"; };
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareOptionsView.swift; sourceTree = "<group>"; };
C2B573B11B1CD91E00303B36 /* desktopclient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktopclient.app; sourceTree = BUILT_PRODUCTS_DIR; };
C2B573B51B1CD91E00303B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C2B573B91B1CD91E00303B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@ -191,10 +205,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5307A6EB2965DB8D001E0C6A /* RealmSwift in Frameworks */,
5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */,
538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */,
53903D302956173F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */,
53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -205,6 +219,17 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797B2B84C81F002DA742 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */,
53C331B62BCD3AFF0093D38B /* NextcloudFileProviderKit in Frameworks */,
53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */,
53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AE1B1CD91E00303B36 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@ -225,25 +250,30 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5318AD8F29BF406500CBB71C /* Database */ = {
5350E4C72B0C368B00F276CB /* Services */ = {
isa = PBXGroup;
children = (
5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */,
5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */,
5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */,
5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */,
53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */,
5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */,
5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */,
5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */,
537630962B860D920026BFAB /* FPUIExtensionService.swift */,
537630942B860D560026BFAB /* FPUIExtensionServiceSource.swift */,
);
path = Database;
path = Services;
sourceTree = "<group>";
};
5352E85929B7BFB4002CE85C /* Extensions */ = {
isa = PBXGroup;
children = (
535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */,
5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */,
5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
5376307B2B85E2E00026BFAB /* Extensions */ = {
isa = PBXGroup;
children = (
5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */,
53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -259,20 +289,16 @@
538E396B27F4765000FA63D5 /* FileProviderExt */ = {
isa = PBXGroup;
children = (
5318AD8F29BF406500CBB71C /* Database */,
5352E85929B7BFB4002CE85C /* Extensions */,
538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */,
53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */,
5350E4C72B0C368B00F276CB /* Services */,
53D666602B70C9A70042C03D /* FileProviderConfig.swift */,
538E396C27F4765000FA63D5 /* FileProviderExtension.swift */,
53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */,
5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */,
538E396E27F4765000FA63D5 /* FileProviderItem.swift */,
5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */,
536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */,
53D056302970594F00988392 /* LocalFilesUtils.swift */,
536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */,
538E397327F4765000FA63D5 /* FileProviderExt.entitlements */,
538E397227F4765000FA63D5 /* Info.plist */,
5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */,
);
path = FileProviderExt;
sourceTree = "<group>";
@ -288,12 +314,33 @@
path = NCDesktopClientSocketKit;
sourceTree = "<group>";
};
53B9797F2B84C81F002DA742 /* FileProviderUIExt */ = {
isa = PBXGroup;
children = (
5376307B2B85E2E00026BFAB /* Extensions */,
53B979802B84C81F002DA742 /* DocumentActionViewController.swift */,
5374FD432B95EE1400C78D54 /* ShareController.swift */,
53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */,
53FE14662B8F78B6006C4193 /* ShareOptionsView.swift */,
53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */,
531522812B8E01C6002E31BE /* ShareTableItemView.xib */,
53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */,
537630922B85F4B00026BFAB /* ShareViewController.swift */,
537630902B85F4980026BFAB /* ShareViewController.xib */,
53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */,
53FE14572B8E3A7C006C4193 /* FileProviderUIExt.entitlements */,
53B979852B84C81F002DA742 /* Info.plist */,
);
path = FileProviderUIExt;
sourceTree = "<group>";
};
C2B573941B1CD88000303B36 = {
isa = PBXGroup;
children = (
C2B573B31B1CD91E00303B36 /* desktopclient */,
C2B573D81B1CD9CE00303B36 /* FinderSyncExt */,
538E396B27F4765000FA63D5 /* FileProviderExt */,
53B9797F2B84C81F002DA742 /* FileProviderUIExt */,
53903D0D2956164F00D0B308 /* NCDesktopClientSocketKit */,
538E396827F4765000FA63D5 /* Frameworks */,
C2B573B21B1CD91E00303B36 /* Products */,
@ -307,6 +354,7 @@
C2B573D71B1CD9CE00303B36 /* FinderSyncExt.appex */,
538E396727F4765000FA63D5 /* FileProviderExt.appex */,
53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */,
53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */,
);
name = Products;
sourceTree = "<group>";
@ -388,7 +436,7 @@
name = FileProviderExt;
packageProductDependencies = (
5307A6E72965DAD8001E0C6A /* NextcloudKit */,
5307A6EA2965DB8D001E0C6A /* RealmSwift */,
53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */,
);
productName = FileProviderExt;
productReference = 538E396727F4765000FA63D5 /* FileProviderExt.appex */;
@ -412,6 +460,30 @@
productReference = 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */;
productType = "com.apple.product-type.framework";
};
53B9797D2B84C81F002DA742 /* FileProviderUIExt */ = {
isa = PBXNativeTarget;
buildConfigurationList = 53B979882B84C820002DA742 /* Build configuration list for PBXNativeTarget "FileProviderUIExt" */;
buildPhases = (
53B9797A2B84C81F002DA742 /* Sources */,
53B9797B2B84C81F002DA742 /* Frameworks */,
53B9797C2B84C81F002DA742 /* Resources */,
);
buildRules = (
);
dependencies = (
53FE14522B8E1213006C4193 /* PBXTargetDependency */,
);
name = FileProviderUIExt;
packageProductDependencies = (
53FE14532B8E1219006C4193 /* NextcloudKit */,
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */,
53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */,
53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */,
);
productName = FileProviderUIExt;
productReference = 53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */;
productType = "com.apple.product-type.app-extension";
};
C2B573B01B1CD91E00303B36 /* desktopclient */ = {
isa = PBXNativeTarget;
buildConfigurationList = C2B573CC1B1CD91E00303B36 /* Build configuration list for PBXNativeTarget "desktopclient" */;
@ -464,7 +536,7 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1420;
LastSwiftUpdateCheck = 1530;
LastUpgradeCheck = 1240;
TargetAttributes = {
538E396627F4765000FA63D5 = {
@ -474,6 +546,9 @@
CreatedOnToolsVersion = 14.2;
ProvisioningStyle = Manual;
};
53B9797D2B84C81F002DA742 = {
CreatedOnToolsVersion = 15.2;
};
C2B573B01B1CD91E00303B36 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = 9B5WD74GWJ;
@ -503,7 +578,9 @@
mainGroup = C2B573941B1CD88000303B36;
packageReferences = (
5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */,
5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */,
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */,
53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */,
53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */,
);
productRefGroup = C2B573B21B1CD91E00303B36 /* Products */;
projectDirPath = "";
@ -512,6 +589,7 @@
C2B573B01B1CD91E00303B36 /* desktopclient */,
C2B573D61B1CD9CE00303B36 /* FinderSyncExt */,
538E396627F4765000FA63D5 /* FileProviderExt */,
53B9797D2B84C81F002DA742 /* FileProviderUIExt */,
53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */,
);
};
@ -532,6 +610,15 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797C2B84C81F002DA742 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */,
537630912B85F4980026BFAB /* ShareViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AF1B1CD91E00303B36 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -576,25 +663,15 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */,
536EFC36295E3C1100F4CB13 /* NextcloudAccount.swift in Sources */,
53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */,
53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */,
538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */,
536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */,
53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */,
5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */,
53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */,
5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */,
537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */,
535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */,
5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */,
53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */,
538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */,
5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */,
5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */,
5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */,
5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */,
537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */,
5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */,
5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */,
538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -606,6 +683,24 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
53B9797A2B84C81F002DA742 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
537630932B85F4B00026BFAB /* ShareViewController.swift in Sources */,
53FE14672B8F78B6006C4193 /* ShareOptionsView.swift in Sources */,
53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */,
53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */,
53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */,
5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */,
53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */,
53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */,
5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */,
53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */,
537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C2B573AD1B1CD91E00303B36 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@ -646,6 +741,10 @@
target = 53903D0B2956164F00D0B308 /* NCDesktopClientSocketKit */;
targetProxy = 53903D322956173F00D0B308 /* PBXContainerItemProxy */;
};
53FE14522B8E1213006C4193 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 53FE14512B8E1213006C4193 /* NextcloudKit */;
};
C2B573E01B1CD9CE00303B36 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C2B573D61B1CD9CE00303B36 /* FinderSyncExt */;
@ -677,6 +776,7 @@
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
@ -704,6 +804,7 @@
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "FileProviderExt/FileProviderExt-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
@ -733,6 +834,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
@ -753,6 +855,7 @@
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "FileProviderExt/FileProviderExt-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
@ -886,6 +989,128 @@
};
name = Release;
};
53B979862B84C81F002DA742 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FileProviderUIExt/FileProviderUIExt.entitlements;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FileProviderUIExt/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_OUTPUT_FORMAT = "same-as-input";
INFOPLIST_PREPROCESS = NO;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = macOS;
};
name = Debug;
};
53B979872B84C81F002DA742 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = FileProviderUIExt/FileProviderUIExt.entitlements;
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = FileProviderUIExt/Info.plist;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
INFOPLIST_OUTPUT_FORMAT = "same-as-input";
INFOPLIST_PREPROCESS = NO;
IPHONEOS_DEPLOYMENT_TARGET = 17.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 11.0;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(OC_APPLICATION_REV_DOMAIN).$(PRODUCT_NAME)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = macOS;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
C2B573991B1CD88000303B36 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -909,6 +1134,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
@ -941,6 +1167,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTING_SEARCH_PATHS = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
@ -1190,6 +1417,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
53B979882B84C820002DA742 /* Build configuration list for PBXNativeTarget "FileProviderUIExt" */ = {
isa = XCConfigurationList;
buildConfigurations = (
53B979862B84C81F002DA742 /* Debug */,
53B979872B84C81F002DA742 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C2B573981B1CD88000303B36 /* Build configuration list for PBXProject "NextcloudIntegration" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@ -1222,18 +1458,34 @@
/* Begin XCRemoteSwiftPackageReference section */
5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nextcloud/NextcloudKit";
repositoryURL = "https://github.com/nextcloud/NextcloudKit.git";
requirement = {
branch = develop;
kind = branch;
kind = upToNextMajorVersion;
minimumVersion = 2.5.9;
};
};
5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */ = {
5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/realm-swift.git";
repositoryURL = "https://github.com/claucambra/NextcloudCapabilitiesKit.git";
requirement = {
kind = exactVersion;
version = 10.33.0;
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/claucambra/SuggestionsTextFieldKit.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
};
};
53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/claucambra/NextcloudFileProviderKit.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.9.0;
};
};
/* End XCRemoteSwiftPackageReference section */
@ -1249,10 +1501,35 @@
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
productName = NextcloudKit;
};
5307A6EA2965DB8D001E0C6A /* RealmSwift */ = {
5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */;
productName = RealmSwift;
package = 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */;
productName = NextcloudCapabilitiesKit;
};
53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */ = {
isa = XCSwiftPackageProductDependency;
package = 53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */;
productName = SuggestionsTextFieldKit;
};
53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */ = {
isa = XCSwiftPackageProductDependency;
package = 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */;
productName = NextcloudFileProviderKit;
};
53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */ = {
isa = XCSwiftPackageProductDependency;
package = 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */;
productName = NextcloudFileProviderKit;
};
53FE14512B8E1213006C4193 /* NextcloudKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
productName = NextcloudKit;
};
53FE14532B8E1219006C4193 /* NextcloudKit */ = {
isa = XCSwiftPackageProductDependency;
package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */;
productName = NextcloudKit;
};
/* End XCSwiftPackageProductDependency section */
};

View File

@ -2,20 +2,28 @@ project(dolphin-owncloud)
cmake_minimum_required(VERSION 3.16)
set(QT_MIN_VERSION "5.15.0")
set(KF5_MIN_VERSION "5.16.0")
if(KF6KIO_FOUND)
set(QT_MAJOR_VERSION "6")
set(QT_MIN_VERSION "6.6.0")
set(KF_MIN_VERSION "5.240.0")
else()
set(QT_MAJOR_VERSION "5")
set(QT_MIN_VERSION "5.15.0")
set(KF_MIN_VERSION "5.16.0")
endif()
set(KDE_INSTALL_USE_QT_SYS_PATHS ON CACHE BOOL "Install the plugin in the right directory")
find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Network)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Network)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED CONFIG)
find_package(ECM ${KF_MIN_VERSION} REQUIRED CONFIG)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS CoreAddons KIO)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS CoreAddons KIO)
set(KDE_INSTALL_DIRS_NO_DEPRECATED TRUE)
include(KDEInstallDirs)
# Before KF5 5.54, kcoreaddons_add_plugin uses deprecated VAR PLUGIN_INSTALL_DIR
# Before KF${QT_MAJOR_VERSION} 5.54, kcoreaddons_add_plugin uses deprecated VAR PLUGIN_INSTALL_DIR
# when that is fixed and you want to remove this workaround,
# you need to _require_ the new enough kcoreaddons
set(PLUGIN_INSTALL_DIR "${KDE_INSTALL_PLUGINDIR}")
@ -29,20 +37,24 @@ set(OWNCLOUDDOLPHINHELPER ${APPLICATION_EXECUTABLE}dolphinpluginhelper)
add_library(${OWNCLOUDDOLPHINHELPER} SHARED
ownclouddolphinpluginhelper.h
ownclouddolphinpluginhelper.cpp)
target_link_libraries(${OWNCLOUDDOLPHINHELPER} Qt5::Network)
target_link_libraries(${OWNCLOUDDOLPHINHELPER} Qt${QT_MAJOR_VERSION}::Network)
generate_export_header(${OWNCLOUDDOLPHINHELPER} BASE_NAME ownclouddolphinpluginhelper)
install(TARGETS ${OWNCLOUDDOLPHINHELPER} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
#---OVERLAY PLUGIN---
set(OWNCLOUDDOLPHINOVERLAYPLUGIN ${APPLICATION_EXECUTABLE}dolphinoverlayplugin)
kcoreaddons_add_plugin(${OWNCLOUDDOLPHINOVERLAYPLUGIN} INSTALL_NAMESPACE "kf5/overlayicon"
JSON ownclouddolphinoverlayplugin.json SOURCES ownclouddolphinoverlayplugin.cpp)
target_link_libraries(${OWNCLOUDDOLPHINOVERLAYPLUGIN} KF5::CoreAddons KF5::KIOCore KF5::KIOWidgets ${OWNCLOUDDOLPHINHELPER})
if(KF6KIO_FOUND)
kcoreaddons_add_plugin(${OWNCLOUDDOLPHINOVERLAYPLUGIN} INSTALL_NAMESPACE "kf${QT_MAJOR_VERSION}/overlayicon"
SOURCES ownclouddolphinoverlayplugin.cpp)
else()
kcoreaddons_add_plugin(${OWNCLOUDDOLPHINOVERLAYPLUGIN} INSTALL_NAMESPACE "kf${QT_MAJOR_VERSION}/overlayicon"
JSON ownclouddolphinoverlayplugin.json SOURCES ownclouddolphinoverlayplugin.cpp)
endif()
target_link_libraries(${OWNCLOUDDOLPHINOVERLAYPLUGIN} KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::KIOCore KF${QT_MAJOR_VERSION}::KIOWidgets ${OWNCLOUDDOLPHINHELPER})
#---ACTION PLUGIN---
set(OWNCLOUDDOLPHINACTIONPLUGIN ${APPLICATION_EXECUTABLE}dolphinactionplugin)
configure_file(ownclouddolphinactionplugin.desktop.in ${OWNCLOUDDOLPHINACTIONPLUGIN}.desktop ESCAPE_QUOTES @ONLY)
kcoreaddons_add_plugin(${OWNCLOUDDOLPHINACTIONPLUGIN} INSTALL_NAMESPACE "kf5/kfileitemaction"
configure_file(ownclouddolphinactionplugin.json.in ${OWNCLOUDDOLPHINACTIONPLUGIN}.json ESCAPE_QUOTES @ONLY)
kcoreaddons_add_plugin(${OWNCLOUDDOLPHINACTIONPLUGIN} INSTALL_NAMESPACE "kf${QT_MAJOR_VERSION}/kfileitemaction"
SOURCES ownclouddolphinactionplugin.cpp)
target_link_libraries(${OWNCLOUDDOLPHINACTIONPLUGIN} KF5::CoreAddons KF5::KIOCore KF5::KIOWidgets ${OWNCLOUDDOLPHINHELPER})
kcoreaddons_desktop_to_json(${OWNCLOUDDOLPHINACTIONPLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${OWNCLOUDDOLPHINACTIONPLUGIN}.desktop)
target_link_libraries(${OWNCLOUDDOLPHINACTIONPLUGIN} KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::KIOCore KF${QT_MAJOR_VERSION}::KIOWidgets ${OWNCLOUDDOLPHINHELPER})

View File

@ -17,17 +17,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
******************************************************************************/
#include <KCoreAddons/KPluginFactory>
#include <KCoreAddons/KPluginLoader>
#include <KIOWidgets/kabstractfileitemactionplugin.h>
#include <KPluginFactory>
#include <KAbstractFileItemActionPlugin>
#include <QtNetwork/QLocalSocket>
#include <KIOCore/kfileitem.h>
#include <KIOCore/KFileItemListProperties>
#include <QtWidgets/QAction>
#include <QtWidgets/QMenu>
#include <QtCore/QDir>
#include <QtCore/QTimer>
#include <QtCore/QEventLoop>
#include <KFileItem>
#include <KFileItemListProperties>
#include <QAction>
#include <QMenu>
#include <QDir>
#include <QTimer>
#include <QEventLoop>
#include "ownclouddolphinpluginhelper.h"
class OwncloudDolphinPluginAction : public KAbstractFileItemActionPlugin
@ -78,12 +77,12 @@ public:
action->setDisabled(true);
auto call = args.value(1).toLatin1();
connect(action, &QAction::triggered, [helper, call, files] {
helper->sendCommand(QByteArray(call + ":" + files + "\n"));
helper->sendCommand(QByteArray(call + ":" + files + "\n").constData());
});
}
});
QTimer::singleShot(100, &loop, &QEventLoop::quit); // add a timeout to be sure we don't freeze dolphin
helper->sendCommand(QByteArray("GET_MENU_ITEMS:" + files + "\n"));
helper->sendCommand(QByteArray("GET_MENU_ITEMS:" + files + "\n").constData());
loop.exec(QEventLoop::ExcludeUserInputEvents);
disconnect(con);
if (menu->actions().isEmpty()) {
@ -112,20 +111,20 @@ public:
auto shareAction = menu->addAction(helper->shareActionTitle());
connect(shareAction, &QAction::triggered, this, [localFile, helper] {
helper->sendCommand(QByteArray("SHARE:" + localFile.toUtf8() + "\n"));
helper->sendCommand(QByteArray("SHARE:" + localFile.toUtf8() + "\n").constData());
});
if (!helper->copyPrivateLinkTitle().isEmpty()) {
auto copyPrivateLinkAction = menu->addAction(helper->copyPrivateLinkTitle());
connect(copyPrivateLinkAction, &QAction::triggered, this, [localFile, helper] {
helper->sendCommand(QByteArray("COPY_PRIVATE_LINK:" + localFile.toUtf8() + "\n"));
helper->sendCommand(QByteArray("COPY_PRIVATE_LINK:" + localFile.toUtf8() + "\n").constData());
});
}
if (!helper->emailPrivateLinkTitle().isEmpty()) {
auto emailPrivateLinkAction = menu->addAction(helper->emailPrivateLinkTitle());
connect(emailPrivateLinkAction, &QAction::triggered, this, [localFile, helper] {
helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n"));
helper->sendCommand(QByteArray("EMAIL_PRIVATE_LINK:" + localFile.toUtf8() + "\n").constData());
});
}
return { menuaction };

View File

@ -1,6 +0,0 @@
[Desktop Entry]
Type=Service
Name=@APPLICATION_NAME@Action
ServiceTypes=KFileItemAction/Plugin
MimeType=application/octet-stream;inode/directory;
X-KDE-Library=@APPLICATION_EXECUTABLE@dolphinactionplugin

View File

@ -0,0 +1,13 @@
{
"KPlugin": {
"MimeTypes": [
"application/octet-stream",
"inode/directory"
],
"Name": "@APPLICATION_NAME@Action",
"ServiceTypes": [
"KFileItemAction/Plugin"
]
},
"MimeType": "application/octet-stream;inode/directory;"
}

View File

@ -20,7 +20,7 @@
#include <KOverlayIconPlugin>
#include <KPluginFactory>
#include <QtNetwork/QLocalSocket>
#include <KIOCore/kfileitem.h>
#include <KFileItem>
#include <QDir>
#include <QTimer>
#include "ownclouddolphinpluginhelper.h"
@ -50,7 +50,7 @@ public:
QDir localPath(url.toLocalFile());
const QByteArray localFile = localPath.canonicalPath().toUtf8();
helper->sendCommand(QByteArray("RETRIEVE_FILE_STATUS:" + localFile + "\n"));
helper->sendCommand(QByteArray("RETRIEVE_FILE_STATUS:" + localFile + "\n").constData());
StatusMap::iterator it = m_status.find(localFile);
if (it != m_status.constEnd()) {
@ -66,16 +66,16 @@ private:
return r;
if (status.startsWith("OK"))
r << "vcs-normal";
r << QStringLiteral("vcs-normal");
if (status.startsWith("SYNC") || status.startsWith("NEW"))
r << "vcs-update-required";
r << QStringLiteral("vcs-update-required");
if (status.startsWith("IGNORE") || status.startsWith("WARN"))
r << "vcs-locally-modified-unstaged";
r << QStringLiteral("vcs-locally-modified-unstaged");
if (status.startsWith("ERROR"))
r << "vcs-conflicting";
r << QStringLiteral("vcs-conflicting");
if (status.contains("+SWM"))
r << "document-share";
r << QStringLiteral("document-share");
return r;
}
@ -98,7 +98,7 @@ private:
return;
status = tokens[1];
emit overlaysChanged(QUrl::fromLocalFile(QString::fromUtf8(name)), overlaysForString(status));
Q_EMIT overlaysChanged(QUrl::fromLocalFile(QString::fromUtf8(name)), overlaysForString(status));
}
};

View File

@ -70,7 +70,7 @@ void OwncloudDolphinPluginHelper::tryConnect()
}
QString socketPath = QStandardPaths::locate(QStandardPaths::RuntimeLocation,
APPLICATION_SHORTNAME,
QStringLiteral(APPLICATION_SHORTNAME),
QStandardPaths::LocateDirectory);
if(socketPath.isEmpty())
return;
@ -112,6 +112,6 @@ void OwncloudDolphinPluginHelper::slotReadyRead()
return;
}
}
emit commandRecieved(line);
Q_EMIT commandRecieved(line);
}
}

View File

@ -36,23 +36,23 @@ public:
[[nodiscard]] QString contextMenuTitle() const
{
return _strings.value("CONTEXT_MENU_TITLE", APPLICATION_NAME);
return _strings.value(QStringLiteral("CONTEXT_MENU_TITLE"), QStringLiteral(APPLICATION_NAME));
}
[[nodiscard]] QString shareActionTitle() const
{
return _strings.value("SHARE_MENU_TITLE", "Share …");
return _strings.value(QStringLiteral("SHARE_MENU_TITLE"), QStringLiteral("Share …"));
}
[[nodiscard]] QString contextMenuIconName() const
{
return _strings.value("CONTEXT_MENU_ICON", APPLICATION_ICON_NAME);
return _strings.value(QStringLiteral("CONTEXT_MENU_ICON"), QStringLiteral(APPLICATION_ICON_NAME));
}
[[nodiscard]] QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; }
[[nodiscard]] QString emailPrivateLinkTitle() const { return _strings["EMAIL_PRIVATE_LINK_MENU_TITLE"]; }
[[nodiscard]] QString copyPrivateLinkTitle() const { return _strings[QStringLiteral("COPY_PRIVATE_LINK_MENU_TITLE")]; }
[[nodiscard]] QString emailPrivateLinkTitle() const { return _strings[QStringLiteral("EMAIL_PRIVATE_LINK_MENU_TITLE")]; }
QByteArray version() { return _version; }
signals:
Q_SIGNALS:
void commandRecieved(const QByteArray &cmd);
protected:

View File

@ -1,5 +1,9 @@
macro(dbus_add_activation_service _sources)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0")
pkg_get_variable(_install_dir dbus-1 session_bus_services_dir DEFINE_VARIABLES datadir=${CMAKE_INSTALL_DATADIR})
else()
pkg_get_variable(_install_dir dbus-1 session_bus_services_dir)
endif()
foreach (_i ${_sources})
get_filename_component(_service_file ${_i} ABSOLUTE)
string(REGEX REPLACE "\\.service.*$" ".service" _output_file ${_i})
@ -21,8 +25,13 @@ macro(libcloudproviders_add_config _sources)
endmacro(libcloudproviders_add_config _sources)
find_package(Qt5 5.15 COMPONENTS DBus)
IF (Qt5DBus_FOUND)
if (Qt6_FOUND)
find_package(Qt6 COMPONENTS COMPONENTS DBus)
else()
set(REQUIRED_QT_VERSION "5.15.0")
find_package(Qt5 ${REQUIRED_QT_VERSION} COMPONENTS DBus)
endif()
IF (Qt5DBus_FOUND OR Qt6DBus_FOUND)
STRING(TOLOWER "${APPLICATION_VENDOR}" DBUS_VENDOR)
STRING(REGEX REPLACE "[^A-z0-9]" "" DBUS_VENDOR "${DBUS_VENDOR}")
STRING(REGEX REPLACE "[^A-z0-9]" "" DBUS_APPLICATION_NAME "${APPLICATION_SHORTNAME}")
@ -34,5 +43,8 @@ IF (Qt5DBus_FOUND)
set(LIBCLOUDPROVIDERS_DBUS_OBJECT_PATH "/${DBUS_PREFIX}/${DBUS_VENDOR}/${DBUS_APPLICATION_NAME}")
dbus_add_activation_service(org.freedesktop.CloudProviders.service.in)
libcloudproviders_add_config(org.freedesktop.CloudProviders.ini.in)
# The .ini file has been replaced by a declaration in the .desktop file in 0.3.3+
if (${CLOUDPROVIDERS_VERSION} VERSION_LESS "0.3.3")
libcloudproviders_add_config(org.freedesktop.CloudProviders.ini.in)
endif ()
ENDIF ()

View File

@ -48,7 +48,10 @@ NCClientInterface::ContextMenuInfo NCClientInterface::FetchInfo(const std::wstri
ContextMenuInfo info;
std::wstring response;
int sleptCount = 0;
while (sleptCount < 5) {
constexpr auto noReplyTimeout = 20;
constexpr auto replyTimeout = 200;
bool receivedReplyFromDesktopClient = false;
while ((!receivedReplyFromDesktopClient && sleptCount < noReplyTimeout) || (receivedReplyFromDesktopClient && sleptCount < replyTimeout)) {
if (socket.ReadLine(&response)) {
if (StringUtil::begins_with(response, wstring(L"REGISTER_PATH:"))) {
wstring responsePath = response.substr(14); // length of REGISTER_PATH
@ -65,6 +68,9 @@ NCClientInterface::ContextMenuInfo NCClientInterface::FetchInfo(const std::wstri
if (!StringUtil::extractChunks(response, commandName, flags, title))
continue;
info.menuItems.push_back({ commandName, flags, title });
} else if (StringUtil::begins_with(response, wstring(L"GET_MENU_ITEMS:BEGIN"))) {
receivedReplyFromDesktopClient = true;
continue;
} else if (StringUtil::begins_with(response, wstring(L"GET_MENU_ITEMS:END"))) {
break; // Stop once we completely received the last sent request
}

View File

@ -1,261 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2014 Daniel Molkentin <daniel@molkentin.de>
** Contact: http://www.qt-project.org/legal
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef TOKENIZER_H
#define TOKENIZER_H
#include <QString>
#include <QByteArray>
#include <QSharedPointer>
QT_BEGIN_NAMESPACE
template <class T, class const_iterator>
struct QTokenizerPrivate {
using char_type = typename T::value_type;
struct State {
bool inQuote = false;
bool inEscape = false;
char_type quoteChar = '\0';
};
QTokenizerPrivate(const T& _string, const T& _delims) :
string(_string)
, begin(string.begin())
, end(string.end())
, tokenBegin(end)
, tokenEnd(begin)
, delimiters(_delims)
{
}
[[nodiscard]] bool isDelimiter(char_type c) const {
return delimiters.contains(c);
}
[[nodiscard]] bool isQuote(char_type c) const {
return quotes.contains(c);
}
// Returns true if a delimiter was not hit
bool nextChar(State* state, char_type c) {
if (state->inQuote) {
if (state->inEscape) {
state->inEscape = false;
} else if (c == '\\') {
state->inEscape = true;
} else if (c == state->quoteChar) {
state->inQuote = false;
}
} else {
if (isDelimiter(c))
return false;
state->inQuote = isQuote(state->quoteChar = c);
}
return true;
}
T string;
// ### copies begin and end for performance, premature optimization?
const_iterator begin;
const_iterator end;
const_iterator tokenBegin;
const_iterator tokenEnd;
T delimiters;
T quotes;
bool isDelim = false;
bool returnDelimiters = false;
bool returnQuotes = false;
};
template <class T, class const_iterator = typename T::const_iterator>
class QTokenizer {
public:
using char_type = typename T::value_type;
/*!
\class QTokenizer
\inmodule QtNetwork
\brief QTokenizer tokenizes Strings on QString, QByteArray,
std::string or std::wstring
Example Usage:
\code
QString str = ...;
QByteArrayTokenizer tokenizer(str, "; ");
tokenizer.setQuoteCharacters("\"'");
tokenizer.setReturnDelimiters(true);
while (tokenizer.hasNext()) {
QByteArray token = tokenizer.next();
bool isDelimiter = tokenizer.isDelimiter();
...
}
\endcode
\param string The string to tokenize
\param delimiters A string containing delimiters
\sa QStringTokenizer, QByteArrayTokenizer, StringTokenizer, WStringTokenizer
*/
QTokenizer(const T& string, const T& delimiters)
: d(new QTokenizerPrivate<T, const_iterator>(string, delimiters))
{ }
/*!
Whether or not to return delimiters as tokens
\see setQuoteCharacters
*/
void setReturnDelimiters(bool enable) { d->returnDelimiters = enable; }
/*!
Sets characters that are considered to start and end quotes.
When between two characters considered a quote, delimiters will
be ignored.
When between quotes, blackslash characters will cause the QTokenizer
to skip the next character.
\param quotes Characters that delimit quotes.
*/
void setQuoteCharacters(const T& quotes) { d->quotes = quotes; }
/*!
Whether or not to return delimiters as tokens
\see setQuoteCharacters
*/
void setReturnQuoteCharacters(bool enable) { d->returnQuotes = enable; }
/*!
Retrieve next token.
Returns true if there are more tokens, false otherwise.
\sa next()
*/
bool hasNext()
{
typename QTokenizerPrivate<T, const_iterator>::State state;
d->isDelim = false;
for (;;) {
d->tokenBegin = d->tokenEnd;
if (d->tokenEnd == d->end)
return false;
d->tokenEnd++;
if (d->nextChar(&state, *d->tokenBegin))
break;
if (d->returnDelimiters) {
d->isDelim = true;
return true;
}
}
while (d->tokenEnd != d->end && d->nextChar(&state, *d->tokenEnd)) {
d->tokenEnd++;
}
return true;
}
/*!
Resets the tokenizer to the starting position.
*/
void reset() {
d->tokenEnd = d->begin;
}
/*!
Returns true if the current token is a delimiter,
if one more more delimiting characters have been set.
*/
[[nodiscard]] bool isDelimiter() const { return d->isDelim; }
/*!
Returns the current token.
Use \c hasNext() to fetch the next token.
*/
[[nodiscard]] T next() const {
int len = std::distance(d->tokenBegin, d->tokenEnd);
const_iterator tmpStart = d->tokenBegin;
if (!d->returnQuotes && len > 1 && d->isQuote(*d->tokenBegin)) {
tmpStart++;
len -= 2;
}
return T(tmpStart, len);
}
private:
friend class QStringTokenizer;
QSharedPointer<QTokenizerPrivate<T, const_iterator> > d;
};
class QStringTokenizer : public QTokenizer<QString> {
public:
QStringTokenizer(const QString &string, const QString &delim) :
QTokenizer<QString, QString::const_iterator>(string, delim) {}
/**
* @brief Like \see next(), but returns a lightweight string reference
* @return A reference to the token within the string
*/
QStringRef stringRef() {
// If those differences overflow an int we'd have a veeeeeery long string in memory
int begin = std::distance(d->begin, d->tokenBegin);
int end = std::distance(d->tokenBegin, d->tokenEnd);
if (!d->returnQuotes && d->isQuote(*d->tokenBegin)) {
begin++;
end -= 2;
}
return QStringRef(&d->string, begin, end);
}
};
using QByteArrayTokenizer = QTokenizer<QByteArray>;
using StringTokenizer = QTokenizer<std::string>;
using WStringTokenizer = QTokenizer<std::wstring>;
QT_END_NAMESPACE
#endif // TOKENIZER_H

View File

@ -1,2 +0,0 @@
TEMPLATE = subdirs
SUBDIRS = test

View File

@ -1,8 +0,0 @@
TEMPLATE = app
QT += testlib
CONFIG += testlib
TARGET = test
INCLUDEPATH += . ..
# Input
SOURCES += tst_qtokenizer.cpp

View File

@ -1,139 +0,0 @@
#include <QtTest>
#include "qtokenizer.h"
namespace {
const QString simple = QLatin1String("A simple tokenizer test");
const QString quoted = QLatin1String("\"Wait for me!\" he shouted");
}
class TestTokenizer : public QObject
{
Q_OBJECT
private slots:
void tokenizeQStringSimple() {
QStringTokenizer tokenizer(simple, " ");
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("A"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("simple"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("tokenizer"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("test"));
QCOMPARE(tokenizer.hasNext(), false);
}
void tokenizeQStringSimpleRef() {
QStringTokenizer tokenizer(simple, " ");
QCOMPARE(tokenizer.hasNext(), true);
QVERIFY(tokenizer.stringRef() == QLatin1String("A"));
QCOMPARE(tokenizer.hasNext(), true);
QVERIFY(tokenizer.stringRef() == QLatin1String("simple"));
QCOMPARE(tokenizer.hasNext(), true);
QVERIFY(tokenizer.stringRef() == QLatin1String("tokenizer"));
QCOMPARE(tokenizer.hasNext(), true);
QVERIFY(tokenizer.stringRef() == QLatin1String("test"));
QCOMPARE(tokenizer.hasNext(), false);
}
void tokenizeQStringQuoted() {
const QString multiquote(QLatin1String("\"'Billy - the Kid' is dead!\""));
QStringTokenizer tokenizer(multiquote, " -");
tokenizer.setQuoteCharacters("\"");
tokenizer.setReturnQuoteCharacters(true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("\"'Billy - the Kid' is dead!\""));
QCOMPARE(tokenizer.hasNext(), false);
}
void tokenizeQStringSkipQuotes() {
const QString multiquote(QLatin1String("\"'Billy - the Kid' is dead!\""));
QStringTokenizer tokenizer(multiquote, " ");
tokenizer.setQuoteCharacters("\"");
tokenizer.setReturnQuoteCharacters(false);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("'Billy - the Kid' is dead!"));
QCOMPARE(tokenizer.stringRef().toString(), QLatin1String("'Billy - the Kid' is dead!"));
QCOMPARE(tokenizer.hasNext(), false);
}
void tokenizeQStringWithDelims() {
const QString delims(QLatin1String("I;Insist,On/a-Delimiter"));
QStringTokenizer tokenizer(delims, ";,/-");
tokenizer.setReturnDelimiters(true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), false);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), false);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), false);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), false);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), true);
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.isDelimiter(), false);
QCOMPARE(tokenizer.hasNext(), false);
}
void resetTokenizer() {
for (int i = 0; i < 2; i++) {
QStringTokenizer tokenizer(simple, " ");
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("A"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("simple"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("tokenizer"));
QCOMPARE(tokenizer.hasNext(), true);
QCOMPARE(tokenizer.next(), QLatin1String("test"));
QCOMPARE(tokenizer.hasNext(), false);
tokenizer.reset();
}
}
// ### QByteArray, other types
};
QTEST_APPLESS_MAIN(TestTokenizer)
#include "tst_qtokenizer.moc"

View File

@ -1,70 +1,78 @@
# TODO: OSX and LIB_ONLY seem to require this to go to binary dir only
if(NOT TOKEN_AUTH_ONLY)
endif()
include(ECMEnableSanitizers)
set(REQUIRED_QT_VERSION "5.15.0")
find_package(Qt5Core ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt5Core PROPERTIES
DESCRIPTION "Qt5 Core component."
find_package(Qt${QT_MAJOR_VERSION}Core ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_MAJOR_VERSION}Core PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} Core component."
TYPE REQUIRED
)
find_package(Qt5Network ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt5Network PROPERTIES
DESCRIPTION "Qt5 Network component."
find_package(Qt${QT_MAJOR_VERSION}Network ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_MAJOR_VERSION}Network PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} Network component."
TYPE REQUIRED
)
find_package(Qt5Xml ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt5Xml PROPERTIES
DESCRIPTION "Qt5 Xml component."
find_package(Qt${QT_MAJOR_VERSION}Xml ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_MAJOR_VERSION}Xml PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} Xml component."
TYPE REQUIRED
)
find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt5Concurrent PROPERTIES
DESCRIPTION "Qt5 Concurrent component."
find_package(Qt${QT_MAJOR_VERSION}Concurrent ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_MAJOR_VERSION}Concurrent PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} Concurrent component."
TYPE REQUIRED
)
find_package(Qt5WebEngineWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET)
find_package(Qt${QT_MAJOR_VERSION}QuickWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_MAJOR_VERSION}QuickWidgets PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} QuickWidgets component."
TYPE REQUIRED
)
find_package(Qt${QT_VERSION_MAJOR}Core5Compat ${REQUIRED_QT_VERSION} CONFIG QUIET)
set_package_properties(Qt${QT_VERSION_MAJOR}Core5Compat PROPERTIES
DESCRIPTION "Qt${QT_VERSION_MAJOR} Core5Compat component."
TYPE REQUIRED
)
find_package(Qt${QT_MAJOR_VERSION}WebEngineWidgets ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT BUILD_WITH_WEBENGINE)
set_package_properties(Qt5WebEngineWidgets PROPERTIES
DESCRIPTION "Qt5 WebEngineWidgets component."
set_package_properties(Qt${QT_MAJOR_VERSION}WebEngineWidgets PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} WebEngineWidgets component."
TYPE RECOMMENDED
)
else()
set_package_properties(Qt5WebEngineWidgets PROPERTIES
DESCRIPTION "Qt5 WebEngineWidgets component."
set_package_properties(Qt${QT_MAJOR_VERSION}WebEngineWidgets PROPERTIES
DESCRIPTION "Qt${QT_MAJOR_VERSION} WebEngineWidgets component."
TYPE REQUIRED
)
endif()
find_package(Qt5WebEngine ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(NOT BUILD_WITH_WEBENGINE)
set_package_properties(Qt5WebEngine PROPERTIES
DESCRIPTION "Qt5 WebEngine component."
find_package(Qt${QT_VERSION_MAJOR}WebEngineCore ${REQUIRED_QT_VERSION} CONFIG QUIET)
if(APPLE)
set_package_properties(Qt${QT_VERSION_MAJOR}WebEngineCore PROPERTIES
DESCRIPTION "Qt${QT_VERSION_MAJOR} WebEngineCore component."
TYPE RECOMMENDED
)
else()
set_package_properties(Qt5WebEngine PROPERTIES
DESCRIPTION "Qt5 WebEngine component."
set_package_properties(Qt${QT_VERSION_MAJOR}WebEngine PROPERTIES
DESCRIPTION "Qt${QT_VERSION_MAJOR} WebEngine component."
TYPE REQUIRED
)
endif()
if(BUILD_WITH_WEBENGINE AND Qt5WebEngine_FOUND AND Qt5WebEngineWidgets_FOUND)
if(Qt${QT_MAJOR_VERSION}WebEngine_FOUND AND Qt${QT_MAJOR_VERSION}WebEngineWidgets_FOUND)
add_compile_definitions(WITH_WEBENGINE=1)
endif()
get_target_property (QT_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION)
message(STATUS "Using Qt ${Qt5Core_VERSION} (${QT_QMAKE_EXECUTABLE})")
get_target_property (QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION)
message(STATUS "Using Qt ${Qt${QT_MAJOR_VERSION}Core_VERSION} (${QT_QMAKE_EXECUTABLE})")
if(NOT TOKEN_AUTH_ONLY)
find_package(Qt5Keychain REQUIRED)
find_package(Qt${QT_MAJOR_VERSION}Keychain REQUIRED)
endif()
# TODO: Mingw64 7.3 might also need to be excluded here as it seems to not automatically link libssp
@ -106,6 +114,9 @@ if(WIN32)
elseif(UNIX AND NOT APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
elseif(APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-ld_classic")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-ld_classic")
endif()
set(QML_IMPORT_PATH ${CMAKE_SOURCE_DIR}/theme CACHE STRING "" FORCE)

View File

@ -10,13 +10,10 @@ add_library(cmdCore STATIC
target_link_libraries(cmdCore
PUBLIC
Nextcloud::sync
Qt5::Core
Qt5::Network
Qt::Core
Qt::Network
)
# Need tokenizer for netrc parser
target_include_directories(cmdCore PRIVATE ${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer)
if(UNIX AND NOT APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE")

View File

@ -15,8 +15,7 @@
#include <QDir>
#include <QFile>
#include <QTextStream>
#include <qtokenizer.h>
#include <QStringTokenizer>
#include <QDebug>
@ -59,33 +58,32 @@ bool NetrcParser::parse()
}
QString content = netrc.readAll();
QStringTokenizer tokenizer(content, " \n\t");
tokenizer.setQuoteCharacters("\"'");
auto tokenizer = QStringTokenizer{content, u" \n\t"};
LoginPair pair;
QString machine;
bool isDefault = false;
while (tokenizer.hasNext()) {
QString key = tokenizer.next();
for(auto itToken = tokenizer.cbegin(); itToken != tokenizer.cend(); ++itToken) {
const auto key = *itToken;
if (key == defaultKeyword) {
tryAddEntryAndClear(machine, pair, isDefault);
isDefault = true;
continue; // don't read a value
}
if (!tokenizer.hasNext()) {
if (itToken != tokenizer.cend()) {
qDebug() << "error fetching value for" << key;
return false;
}
QString value = tokenizer.next();
auto value = *(++itToken);
if (key == machineKeyword) {
tryAddEntryAndClear(machine, pair, isDefault);
machine = value;
machine = value.toString();
} else if (key == loginKeyword) {
pair.first = value;
pair.first = value.toString();
} else if (key == passwordKeyword) {
pair.second = value;
pair.second = value.toString();
} // ignore unsupported tokens
}
tryAddEntryAndClear(machine, pair, isDefault);

View File

@ -91,6 +91,21 @@ Q_LOGGING_CATEGORY(lcChecksums, "nextcloud.sync.checksums", QtInfoMsg)
#define BUFSIZE qint64(500 * 1024) // 500 KiB
static QByteArray calcCryptoHash(const QByteArray &data, QCryptographicHash::Algorithm algo)
{
if (data.isEmpty()) {
return {};
}
QCryptographicHash crypto(algo);
crypto.addData(data);
return crypto.result().toHex();
}
QByteArray calcSha256(const QByteArray &data)
{
return calcCryptoHash(data, QCryptographicHash::Sha256);
}
QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum)
{
if (checksumType.isEmpty() || checksum.isEmpty())

View File

@ -56,6 +56,8 @@ OCSYNC_EXPORT QByteArray parseChecksumHeaderType(const QByteArray &header);
/// Checks OWNCLOUD_DISABLE_CHECKSUM_UPLOAD
OCSYNC_EXPORT bool uploadChecksumEnabled();
OCSYNC_EXPORT QByteArray calcSha256(const QByteArray &data);
/**
* Computes the checksum of a file.
* \ingroup libsync

View File

@ -17,4 +17,18 @@ set(common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/syncfilestatus.cpp
)
if(WIN32)
list(APPEND common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/utility_win.cpp
)
elseif(APPLE)
list(APPEND common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/utility_mac.mm
)
elseif(UNIX AND NOT APPLE)
list(APPEND common_SOURCES
${CMAKE_CURRENT_LIST_DIR}/utility_unix.cpp
)
endif()
configure_file(${CMAKE_CURRENT_LIST_DIR}/vfspluginmetadata.json.in ${CMAKE_CURRENT_BINARY_DIR}/vfspluginmetadata.json)

View File

@ -20,12 +20,13 @@
#include "config.h"
#include "csync/ocsynclib.h"
#include <QString>
#include <ctime>
#include <QFileInfo>
#include <QLoggingCategory>
#include <csync/ocsynclib.h>
#include <ctime>
class QFile;
@ -42,6 +43,10 @@ OCSYNC_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcFileSystem)
* @brief This file contains file system helper
*/
namespace FileSystem {
enum class FolderPermissions {
ReadOnly,
ReadWrite,
};
/**
* @brief Mark the file as hidden (only has effects on windows)

View File

@ -107,6 +107,7 @@ public:
GetE2EeLockedFolderQuery,
GetE2EeLockedFoldersQuery,
DeleteE2EeLockedFolderQuery,
ListAllTopLevelE2eeFoldersStatusLessThanQuery,
PreparedQueryCount
};

View File

@ -17,10 +17,16 @@
*/
#include "remotepermissions.h"
#include <QVariant>
#include <QLoggingCategory>
#include <cstring>
namespace OCC {
Q_LOGGING_CATEGORY(lcRemotePermissions, "nextcloud.sync.remotepermissions", QtInfoMsg)
static const char letters[] = " WDNVCKRSMm";
@ -68,11 +74,43 @@ RemotePermissions RemotePermissions::fromDbValue(const QByteArray &value)
return perm;
}
RemotePermissions RemotePermissions::fromServerString(const QString &value)
template <typename T>
RemotePermissions RemotePermissions::internalFromServerString(const QString &value,
const T&otherProperties,
MountedPermissionAlgorithm algorithm)
{
RemotePermissions perm;
perm.fromArray(value.utf16());
if (algorithm == MountedPermissionAlgorithm::WildGuessMountedSubProperty) {
return perm;
}
if ((otherProperties.contains(QStringLiteral("is-mount-root")) && otherProperties.value(QStringLiteral("is-mount-root")) == QStringLiteral("false") && perm.hasPermission(RemotePermissions::IsMounted)) ||
(!otherProperties.contains(QStringLiteral("is-mount-root")) && perm.hasPermission(RemotePermissions::IsMounted))) {
/* All the entries in a external storage have 'M' in their permission. However, for all
purposes in the desktop client, we only need to know about the mount points.
So replace the 'M' by a 'm' for every sub entries in an external storage */
perm.unsetPermission(RemotePermissions::IsMounted);
perm.setPermission(RemotePermissions::IsMountedSub);
qCInfo(lcRemotePermissions()) << otherProperties.value(QStringLiteral("permissions")) << "replacing M permissions by m for subfolders inside a group folder";
}
return perm;
}
RemotePermissions RemotePermissions::fromServerString(const QString &value,
MountedPermissionAlgorithm algorithm,
const QMap<QString, QString> &otherProperties)
{
return internalFromServerString(value, otherProperties, algorithm);
}
RemotePermissions RemotePermissions::fromServerString(const QString &value,
MountedPermissionAlgorithm algorithm,
const QVariantMap &otherProperties)
{
return internalFromServerString(value, otherProperties, algorithm);
}
} // namespace OCC

View File

@ -59,6 +59,11 @@ public:
PermissionsCount = IsMountedSub
};
enum class MountedPermissionAlgorithm {
UseMountRootProperty,
WildGuessMountedSubProperty,
};
/// null permissions
RemotePermissions() = default;
@ -72,7 +77,14 @@ public:
static RemotePermissions fromDbValue(const QByteArray &);
/// read a permissions string received from the server, never null
static RemotePermissions fromServerString(const QString &);
static RemotePermissions fromServerString(const QString &value,
MountedPermissionAlgorithm algorithm = MountedPermissionAlgorithm::WildGuessMountedSubProperty,
const QMap<QString, QString> &otherProperties = {});
/// read a permissions string received from the server, never null
static RemotePermissions fromServerString(const QString &value,
MountedPermissionAlgorithm algorithm,
const QVariantMap &otherProperties = {});
[[nodiscard]] bool hasPermission(Permissions p) const
{
@ -101,6 +113,13 @@ public:
{
return dbg << p.toString();
}
private:
template <typename T>
static RemotePermissions internalFromServerString(const QString &value,
const T&otherProperties,
MountedPermissionAlgorithm algorithm);
};

View File

@ -586,7 +586,7 @@ bool SyncJournalDb::checkConnect()
createQuery.bindValue(1, MIRALL_VERSION_MAJOR);
createQuery.bindValue(2, MIRALL_VERSION_MINOR);
createQuery.bindValue(3, MIRALL_VERSION_PATCH);
createQuery.bindValue(4, MIRALL_VERSION_BUILD);
createQuery.bindValue(4, static_cast<qulonglong>(MIRALL_VERSION_BUILD));
if (!createQuery.exec()) {
return sqlFail(QStringLiteral("Update version"), createQuery);
}
@ -616,7 +616,7 @@ bool SyncJournalDb::checkConnect()
createQuery.bindValue(1, MIRALL_VERSION_MAJOR);
createQuery.bindValue(2, MIRALL_VERSION_MINOR);
createQuery.bindValue(3, MIRALL_VERSION_PATCH);
createQuery.bindValue(4, MIRALL_VERSION_BUILD);
createQuery.bindValue(4, static_cast<qulonglong>(MIRALL_VERSION_BUILD));
createQuery.bindValue(5, major);
createQuery.bindValue(6, minor);
createQuery.bindValue(7, patch);
@ -1030,6 +1030,108 @@ Result<void, QString> SyncJournalDb::setFileRecord(const SyncJournalFileRecord &
return {};
}
bool SyncJournalDb::getRootE2eFolderRecord(const QString &remoteFolderPath, SyncJournalFileRecord *rec)
{
Q_ASSERT(rec);
rec->_path.clear();
Q_ASSERT(!rec->isValid());
Q_ASSERT(!remoteFolderPath.isEmpty());
Q_ASSERT(!remoteFolderPath.isEmpty() && remoteFolderPath != QStringLiteral("/"));
if (remoteFolderPath.isEmpty() || remoteFolderPath == QStringLiteral("/")) {
qCWarning(lcDb) << "Invalid folder path!";
return false;
}
auto remoteFolderPathSplit = remoteFolderPath.split(QLatin1Char('/'), Qt::SkipEmptyParts);
if (remoteFolderPathSplit.isEmpty()) {
qCWarning(lcDb) << "Invalid folder path!";
return false;
}
while (!remoteFolderPathSplit.isEmpty()) {
const auto result = getFileRecord(remoteFolderPathSplit.join(QLatin1Char('/')), rec);
if (!result) {
return false;
}
if (rec->isE2eEncrypted() && rec->_e2eMangledName.isEmpty()) {
// it's a toplevel folder record
return true;
}
remoteFolderPathSplit.removeLast();
}
return true;
}
bool SyncJournalDb::listAllE2eeFoldersWithEncryptionStatusLessThan(const int status, const std::function<void(const SyncJournalFileRecord &)> &rowCallback)
{
QMutexLocker locker(&_mutex);
if (_metadataTableIsEmpty)
return true;
if (!checkConnect())
return false;
const auto query = _queryManager.get(PreparedSqlQueryManager::ListAllTopLevelE2eeFoldersStatusLessThanQuery,
QByteArrayLiteral(GET_FILE_RECORD_QUERY " WHERE type == 2 AND isE2eEncrypted >= ?1 AND isE2eEncrypted < ?2 ORDER BY path||'/' ASC"),
_db);
if (!query) {
return false;
}
query->bindValue(1, SyncJournalFileRecord::EncryptionStatus::Encrypted);
query->bindValue(2, status);
if (!query->exec())
return false;
forever {
auto next = query->next();
if (!next.ok)
return false;
if (!next.hasData)
break;
SyncJournalFileRecord rec;
fillFileRecordFromGetQuery(rec, *query);
if (rec._type == ItemTypeSkip) {
continue;
}
rowCallback(rec);
}
return true;
}
bool SyncJournalDb::findEncryptedAncestorForRecord(const QString &filename, SyncJournalFileRecord *rec)
{
Q_ASSERT(rec);
rec->_path.clear();
Q_ASSERT(!rec->isValid());
const auto slashPosition = filename.lastIndexOf(QLatin1Char('/'));
const auto parentPath = slashPosition >= 0 ? filename.left(slashPosition) : QString();
auto pathComponents = parentPath.split(QLatin1Char('/'));
while (!pathComponents.isEmpty()) {
const auto pathCompontentsJointed = pathComponents.join(QLatin1Char('/'));
if (!getFileRecord(pathCompontentsJointed, rec)) {
qCDebug(lcDb) << "could not get file from local DB" << pathCompontentsJointed;
return false;
}
if (rec->isValid() && rec->isE2eEncrypted()) {
break;
}
pathComponents.removeLast();
}
return true;
}
void SyncJournalDb::keyValueStoreSet(const QString &key, QVariant value)
{
QMutexLocker locker(&_mutex);

View File

@ -70,6 +70,9 @@ public:
[[nodiscard]] bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
[[nodiscard]] bool listFilesInPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
[[nodiscard]] Result<void, QString> setFileRecord(const SyncJournalFileRecord &record);
[[nodiscard]] bool getRootE2eFolderRecord(const QString &remoteFolderPath, SyncJournalFileRecord *rec);
[[nodiscard]] bool listAllE2eeFoldersWithEncryptionStatusLessThan(const int status, const std::function<void(const SyncJournalFileRecord &)> &rowCallback);
[[nodiscard]] bool findEncryptedAncestorForRecord(const QString &filename, SyncJournalFileRecord *rec);
void keyValueStoreSet(const QString &key, QVariant value);
[[nodiscard]] qint64 keyValueStoreGetInt(const QString &key, qint64 defaultValue);

Some files were not shown because too many files have changed in this diff Show More