Merge branch 'master' into icons

This commit is contained in:
Dominique Fuchs 2019-09-10 14:36:40 +02:00 committed by GitHub
commit b1db012094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
195 changed files with 30141 additions and 17486 deletions

View File

@ -328,7 +328,26 @@ trigger:
event: event:
- pull_request - pull_request
- push - push
---
kind: pipeline
name: Debian
steps:
- name: build
image: nextcloudci/client-debian-ci:client-debian-ci-2
commands:
- /bin/bash -c "./admin/linux/debian/drone-build.sh"
environment:
DEBIAN_SECRET_KEY:
from_secret: DEBIAN_SECRET_KEY
DEBIAN_SECRET_IV:
from_secret: DEBIAN_SECRET_IV
trigger:
branch:
- master
event:
- pull_request
- push
--- ---
kind: pipeline kind: pipeline
name: Documentation name: Documentation

View File

@ -1,9 +1,14 @@
<!--- <!--
Please try to only report a bug if it happens with the latest version Dear user,
The latest version can be seen by checking https://download.nextcloud.com/desktop/ Please understand that at the moment, we are very busy with customer issues
For support try our forums: https://help.nextcloud.com and some high priority development work. A lot of issues are getting reported.
---> Right now we can't keep up and timely respond to all of them.
We're sorry for that and are expanding our team, if you're looking for a C++
job or know somebody who is, please point them to https://nextcloud.com/jobs
Don't forget that Github is not a support system or a place to ask for
features but only a place to report verified bugs - see nextcloud.com/support
for support options!
-->
### Expected behaviour ### Expected behaviour
Tell us what should happen Tell us what should happen
@ -18,6 +23,11 @@ Tell us what happens instead
### Client configuration ### Client configuration
Client version: Client version:
<!---
Please try to only report a bug if it happens with the latest version
The latest version can be seen by checking https://download.nextcloud.com/desktop/
For support try our forums: https://help.nextcloud.com
--->
Operating system: Operating system:
@ -34,15 +44,6 @@ Installation path of client:
<!--- <!---
Optional section. It depends on the issue. Optional section. It depends on the issue.
---> --->
Operating system:
Web server:
Database:
PHP version:
Nextcloud version: Nextcloud version:
Storage backend (external storage): Storage backend (external storage):
@ -52,8 +53,6 @@ Storage backend (external storage):
Please use Gist (https://gist.github.com/) or a similar code paster for longer Please use Gist (https://gist.github.com/) or a similar code paster for longer
logs. logs.
```Template for output < 10 lines```
1. Client logfile: Output of `nextcloud --logwindow` or `nextcloud --logfile log.txt` 1. Client logfile: Output of `nextcloud --logwindow` or `nextcloud --logfile log.txt`
(On Windows using `cmd.exe`, you might need to first `cd` into the Nextcloud directory) (On Windows using `cmd.exe`, you might need to first `cd` into the Nextcloud directory)
(See also https://docs.nextcloud.com/desktop/2.3/troubleshooting.html#log-files) (See also https://docs.nextcloud.com/desktop/2.3/troubleshooting.html#log-files)

10
.gitignore vendored
View File

@ -15,9 +15,18 @@ cscope.*
tags tags
t1.cfg t1.cfg
## Ignore Visual Studio Code config & environment files
.vs/
.vscode/
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
# CMake integration on VS2019+
CMakeSettings.json
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
@ -171,3 +180,4 @@ CPackConfig.cmake
CPackOptions.cmake CPackOptions.cmake
CPackSourceConfig.cmake CPackSourceConfig.cmake
compile_commands.json

View File

@ -198,6 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations # Translations
Name[hr]=sinkronizacija računala Icon[hr]=@APPLICATION_ICON_NAME@
Comment[hr]=klijent za sinkronizaciju računala Name[hr]=@APPLICATION_NAME@ klijent za sink. računala
Comment[hr]=@APPLICATION_NAME@ klijent za sinkronizaciju računala
GenericName[hr]=Sinkronizacija mapa GenericName[hr]=Sinkronizacija mapa

View File

@ -0,0 +1,204 @@
[Desktop Entry]
Categories=Utility;X-SuSE-SyncUtility;
Type=Application
Exec=@APPLICATION_EXECUTABLE@
Name=@APPLICATION_NAME@ desktop sync client
Comment=@APPLICATION_NAME@ desktop synchronization client
GenericName=Folder Sync
Icon=@APPLICATION_ICON_NAME@
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
X-GNOME-Autostart-Delay=3
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
Icon[mk]=@APPLICATION_ICON_NAME@
Name[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
Comment[mk]=@APPLICATION_NAME@ клиент за синхронизација на компјутер
GenericName[mk]=Папка за синхронизација

View File

@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
Icon[pt_BR]=@APPLICATION_ICON_NAME@ Icon[pt_BR]=@APPLICATION_ICON_NAME@
Name[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop Name[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop
Comment[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop Comment[pt_BR]=@APPLICATION_NAME@ cliente de sincronização desktop
GenericName[pt_BR]=Sincronizar Pasta GenericName[pt_BR]=Sincronizar pasta

350
ChangeLog
View File

@ -1,5 +1,138 @@
ChangeLog 2.5 Series ChangeLog
========= ====================
version 2.5.3 (release 2019-07-22)
* Fix empty file wording in error log (small)
* Add Qt-5.12 to CI
* Fix a minor typo
* Libcloudproviders: Add missing check for Qt5DBus
* Fix several memory leaks in cloudproviders and add translation support
* Share link fixing
* New drone config
* Uses configuraion to determine if it should show empty folder popup.
* Simplify cmake command to make copy-pastable
* Updated default remote poll to 5 seconds #1115
* Fix memory leak with device pointer
* Added a nice UI for the E2E-enabled account first connect
* This should fix issue #1000
* Adds parameter to retrieve shares with its reshares.
* Fixed typo
* Fixed typo in "certificate"
* WebView: Properly handle usernames with spaces and plus signs in it
* Add error category for http file lock error status 423.
* Displays the uid_owner of a shared file.
* Minor text change in the link to help in the tab 'General'.
version 2.5.2 (release 2019-04-11)
* Handle spaces in username properly in login flow
* Wizard: show an error message if there is no enough free space in the local folder
* Removed whitespace from string
* Do not add double slash to login flow url
* Fix login flow with system proxy
* Start with easier theming
* Do not display dismissed notifications
* Fixed l18n issue. Added space for separating string
* Add invalid certiticate messagebox
* Correct app passwords link
* Be less verbose with logging
* Fix typo in translation string
* Add a command line option to launch the client in the background
* Support Ubuntu Disco Dingo
* Added missing Include
* Make sure _profile and _page are deleted in the correct order
* Fix KDEInstallDirs deprecation warnings
* Removed Stylesheet
version 2.5.1 (release 2019-01-06)
* Fixup the port in server notification URLs
* GUI: let Clang-Tidy modernize nullptr & override usage
* Improve the slide show
* Libsync: let Clang-Tidy modernize nullptr & override usage
* SettingsDialog: fix a little glitch in the account tool button size
* SettingsDialog: tweak color aware icons
* More verbose error and proper app name on configuration read error
* Fix cmake build using WITH_PROVIDERS=OFF
* Debian/Ubuntu target repository update
* Change man page names and contents for nextcloud
* Share dialog alignment
* Fixed typo
* Change link to docs for NC 15
* Do not fetch activities if they are not enabled
* Do not read system exclude list if user exclude is present
* Fix the activity loop
* Write the actual folder to the log
* Fix appname for Nautilus integration script
version 2.5.0 (release 2018-11-14)
* End to end encryption
* New Web login flow
* UI improvements: Notifications
* UI improvements: refactoring of Activities
* SyncJournal: Clear etag filter before sync
* Partial local discovery: Fix scheduling logic
* Sync hidden files by default
* Larger Windows App Icon
* Show a tray message when a folder watcher becomes unreliable #6119
* Create symlinks for the small-letter application icon file names
* In setup wizard put link to nextcloud installation
* Web view scales vertically
* Add a WebFlowCredentialsAccessManager
* Mac Application Icon
* Ensure GETFileJob notices finishing #6581
* OAuth2: Try to refresh the token even if the credentials weren't ready.
* Tray workarounds #6545
* UpdateInfo: Remove unused code
* OAuth: Remove the timeout
* TestOAuth: Don't have global static QObject
* Log: Adjust update/reconcile log verbosity
* Reconcile: When detecting a local move, keep the local mtime
* Wizard enhancement
* FolderMan::checkPathValidityForNewFolder: make sure to work when fold…
* Update: Report on readdir() errors #6610
* Use encode()/decode() with Python 3 only
* Sqlite: Update bundled version to 3.24.0
* Do not require server replies to contain an mtime
* Settings: Attempt to fix rename issue on old macOS
* Support higher resolution theme icons
* OAuth: Fix infinite loop when the refresh token is expired
* Windows: Don't ignore files with FILE_ATTRIBUTE_TEMPORARY
* Data-Fingerprint: Fix backup detection when fingerprint is empty
* Nautilus: Fix GET_MENU_ITEMS with utf8 filenames #6643
* Windows: Release handle/fd when file open fails #6699
* SettingsDialog: Show the page for the newly created account
* Updates submodule qtmacgoodies.
* Fixes #665 Adds slot for confirmShare button.
* Rename INSTALL to INSTALL.md for Preview :)
* Add cmake temporary stuff
* Inform user that configuration is not writable
* Uses QByteArray to store private key.
* Fix cmake command for linux in README too
* Build fix: remove an unused QtSvg/QSvgRenderer include
* Qtkeychain: 0.8.0 -> 0.9.1
* Setup wizard: implement an animated and interactive slide show
* Theming for general settings ui
* Make the "Add Folder Sync Connection" button act like a button
* Allow to use the login flow with a self signed certificate
* Fix warning in ShareUserGroupWidget
* Copy over config file to new location on windows
* Update to translate strings
* Migrate http auth to webflow
* Margins
* Qt 5.5 compatibility patch for Xenial
* Fix cmake build of documentation
* Use Nextcloud
* Update isntaller background for OSX
* Fix ActivityWidget palette
* SettingsDialog: disable unnecessary wrapping for the about label
* Added default scheme when server returns just a host
* Removed explicit initialization; Fixed RAND_bytes not found
* Actually open the activity view on a click for more info
* Use a format that supports alpha channels for avatars
* L10n. Added space for correct grammar.
2.4 Series ChangeLog
====================
version 2.4.1 (2017-02-xx) version 2.4.1 (2017-02-xx)
* Ignore files with file names that can't be encoded for the filesystem (#6287, #5676, #5719) * Ignore files with file names that can't be encoded for the filesystem (#6287, #5676, #5719)
@ -114,6 +247,10 @@ version 2.4.0 (2017-12-21)
* Compile with stack-smashing protection * Compile with stack-smashing protection
* Updater: Rudimentary support for beta channel (#6048) * Updater: Rudimentary support for beta channel (#6048)
2.3 Series ChangeLog
====================
version 2.3.4 (2017-11-02) version 2.3.4 (2017-11-02)
* Checksums: Use addData function to avoid endless loop CPU load issues with Office files * Checksums: Use addData function to avoid endless loop CPU load issues with Office files
* Packaging: Require ZLIB * Packaging: Require ZLIB
@ -184,6 +321,10 @@ version 2.3.0 (2017-03-03)
* Improved documentation * Improved documentation
* Crash fixes * Crash fixes
2.2 Series ChangeLog
====================
version 2.2.4 (release 2016-09-27) version 2.2.4 (release 2016-09-27)
* Dolphin Plugin: Use the Application name for the socket path (#5172) * Dolphin Plugin: Use the Application name for the socket path (#5172)
* SyncEngine: Fix renaming of folder when file are changed (#5195) * SyncEngine: Fix renaming of folder when file are changed (#5195)
@ -248,6 +389,10 @@ version 2.2.0 (release 2016-05-12)
* Update of QtKeyChain to support Windows credential store * Update of QtKeyChain to support Windows credential store
* Packaging of dolphin overlay icon module for bleeding edge distros * Packaging of dolphin overlay icon module for bleeding edge distros
2.1 Series ChangeLog
====================
version 2.1.1 (release 2016-02-10) version 2.1.1 (release 2016-02-10)
* UI improvements for HiDPI screens, error messages, RTL languages * UI improvements for HiDPI screens, error messages, RTL languages
* Fix occurences of "Connection Closed" when a new unauthenticated TCP socket is used * Fix occurences of "Connection Closed" when a new unauthenticated TCP socket is used
@ -281,8 +426,8 @@ version 2.1 (release 2015-12-03)
* Improved reconnecting after network change/disconnect (#4167 #3969 ...) * Improved reconnecting after network change/disconnect (#4167 #3969 ...)
* Improved performance in Windows file system discovery * Improved performance in Windows file system discovery
* Removed libneon-based propagator. As a consequence, The client can no * Removed libneon-based propagator. As a consequence, The client can no
longer provide bandwith limiting on Linux-distributions where it is * longer provide bandwith limiting on Linux-distributions where it is
using Qt < 5.4 * using Qt < 5.4
* Performance improvements in the logging functions * Performance improvements in the logging functions
* Ensured that local disk space problems are handled gracefully (#2939) * Ensured that local disk space problems are handled gracefully (#2939)
* Improved handling of checksums: transport validation, db (#3735) * Improved handling of checksums: transport validation, db (#3735)
@ -318,6 +463,10 @@ version 2.1 (release 2015-12-03)
* Organized patches to our base Qt version into admin/qt/patches * Organized patches to our base Qt version into admin/qt/patches
* Plus: A lot of unmentioned improvements and fixes * Plus: A lot of unmentioned improvements and fixes
2.0 Series ChangeLog
====================
version 2.0.2 (release 2015-10-21) version 2.0.2 (release 2015-10-21)
* csync_file_stat_s: Save a bit of memory * csync_file_stat_s: Save a bit of memory
* Shibboleth: Add our base user agent to WebKit * Shibboleth: Add our base user agent to WebKit
@ -393,6 +542,10 @@ version 2.0.0 (release 2015-08-25)
* Bandwidth Throttling: Provide automatic limit setting for downloads (#3084) * Bandwidth Throttling: Provide automatic limit setting for downloads (#3084)
* Systray: Workaround for issue with Qt 5.5.0 #3656 * Systray: Workaround for issue with Qt 5.5.0 #3656
1.8 Series ChangeLog
====================
version 1.8.4 (release 2015-07-13) version 1.8.4 (release 2015-07-13)
* Release to ship a security release of openSSL. No source changes of the ownCloud Client code. * Release to ship a security release of openSSL. No source changes of the ownCloud Client code.
@ -402,7 +555,7 @@ version 1.8.3 (release 2015-06-23)
* Ignores: Force a remote discovery after ignore list change (#3172) * Ignores: Force a remote discovery after ignore list change (#3172)
* Shibboleth: Avoid crash by letting the webview use its own QNAM (#3359) * Shibboleth: Avoid crash by letting the webview use its own QNAM (#3359)
* System Ignores: Removed *.tmp from system ignore again. If a user * System Ignores: Removed *.tmp from system ignore again. If a user
wants to ignore *.tmp, it needs to be added to the user ignore list. * wants to ignore *.tmp, it needs to be added to the user ignore list.
version 1.8.2 (release 2015-06-08) version 1.8.2 (release 2015-06-08)
* Improve reporting of server error messages (#3220) * Improve reporting of server error messages (#3220)
@ -415,16 +568,16 @@ version 1.8.2 (release 2015-06-08)
* HTTP: Add the branding name to the UserAgent string * HTTP: Add the branding name to the UserAgent string
* ConnectonValidator: Always run with new credentials (#3266) * ConnectonValidator: Always run with new credentials (#3266)
* Recall Feature: Admins can trigger an upload of a file from * Recall Feature: Admins can trigger an upload of a file from
client to server again (#3246) * client to server again (#3246)
* Propagator: Add 'Content-Length: 0' header to MKCOL request (#3256) * Propagator: Add 'Content-Length: 0' header to MKCOL request (#3256)
* Switch on checksum verification through branding or config * Switch on checksum verification through branding or config
* Add ability for checksum verification of up and download * Add ability for checksum verification of up and download
* Fix opening external links for some labels (#3135) * Fix opening external links for some labels (#3135)
* AccountState: Run only a single validator, allow error message * AccountState: Run only a single validator, allow error message
overriding (#3236, #3153) * overriding (#3236, #3153)
* SyncJournalDB: Minor fixes and simplificatons * SyncJournalDB: Minor fixes and simplificatons
* SyncEngine: Force re-read of folder Etags for upgrades from * SyncEngine: Force re-read of folder Etags for upgrades from
1.8.0 and 1.8.1 * 1.8.0 and 1.8.1
* Propagator: Limit length of temporary file name (#2789) * Propagator: Limit length of temporary file name (#2789)
* ShareDialog: Password ui fixes (#3189) * ShareDialog: Password ui fixes (#3189)
* Fix startup hang by removing QSettings lock file (#3175) * Fix startup hang by removing QSettings lock file (#3175)
@ -445,12 +598,12 @@ version 1.8.2 (release 2015-06-08)
version 1.8.1 (release 2015-05-07) version 1.8.1 (release 2015-05-07)
* Make "operation canceled" error a soft error * Make "operation canceled" error a soft error
* Do not throw an error for files that are scheduled to be removed, * Do not throw an error for files that are scheduled to be removed,
but can not be found on the server. #2919 * but can not be found on the server. #2919
* Windows: Reset QNAM to proper function after hibernation. #2899 #2895 #2973 * Windows: Reset QNAM to proper function after hibernation. #2899 #2895 #2973
* Fix argument verification of --confdir #2453 * Fix argument verification of --confdir #2453
* Fix a crash when accessing a dangling UploadDevice pointer #2984 * Fix a crash when accessing a dangling UploadDevice pointer #2984
* Add-folder wizard: Make sure there is a scrollbar if folder names * Add-folder wizard: Make sure there is a scrollbar if folder names
are too long #2962 * are too long #2962
* Add-folder Wizard: Select the newly created folder * Add-folder Wizard: Select the newly created folder
* Activity: Correctly restore column sizes #3005 * Activity: Correctly restore column sizes #3005
* SSL Button: do not crash on empty certificate chain * SSL Button: do not crash on empty certificate chain
@ -458,8 +611,8 @@ version 1.8.1 (release 2015-05-07)
* Lookup system proxy async to avoid hangs #2993 #2802 * Lookup system proxy async to avoid hangs #2993 #2802
* ShareDialog: Some GUI refinements * ShareDialog: Some GUI refinements
* ShareDialog: On creation of a share always retrieve the share * ShareDialog: On creation of a share always retrieve the share
This makes sure that if a default expiration date is set this is reflected * This makes sure that if a default expiration date is set this is reflected
in the dialog. #2889 * in the dialog. #2889
* ShareDialog: Only show share dialog if we are connected. * ShareDialog: Only show share dialog if we are connected.
* HttpCreds: Fill pw dialog with previous password. #2848 #2879 * HttpCreds: Fill pw dialog with previous password. #2848 #2879
* HttpCreds: Delete password from old location. #2186 * HttpCreds: Delete password from old location. #2186
@ -468,7 +621,7 @@ version 1.8.1 (release 2015-05-07)
* ProtocolWidget: Always add seconds to the DateTime locale. #2535 * ProtocolWidget: Always add seconds to the DateTime locale. #2535
* Updater: Give context as to which app is about to be updated #3040 * Updater: Give context as to which app is about to be updated #3040
* Windows: Add version information for owncloud.exe. This should help us know * Windows: Add version information for owncloud.exe. This should help us know
what version or build number a crash report was generated with. * what version or build number a crash report was generated with.
* Fix a crash on shutdown in ~SocketApi #3057 * Fix a crash on shutdown in ~SocketApi #3057
* SyncEngine: Show more timing measurements #3064 * SyncEngine: Show more timing measurements #3064
* Discovery: Add warning if returned etag is 0 * Discovery: Add warning if returned etag is 0
@ -491,8 +644,8 @@ version 1.8.1 (release 2015-05-07)
version 1.8.0 (release 2015-03-17) version 1.8.0 (release 2015-03-17)
* Mac OS: HIDPI support * Mac OS: HIDPI support
* Support Sharing from desktop: Added a share dialog that can be * Support Sharing from desktop: Added a share dialog that can be
opened by context menu in the file managers (Win, Mac, Nautilus) * opened by context menu in the file managers (Win, Mac, Nautilus)
Supports public links with password enforcement * Supports public links with password enforcement
* Enhanced usage of parallel HTTP requests for ownCloud 8 servers * Enhanced usage of parallel HTTP requests for ownCloud 8 servers
* Renamed github repository from mirall to client. * Renamed github repository from mirall to client.
* Mac OS: Use native notification support * Mac OS: Use native notification support
@ -505,7 +658,7 @@ version 1.8.0 (release 2015-03-17)
* Build with Qt 5.4 * Build with Qt 5.4
* Dropped libneon dependency if Qt 5.4 is available * Dropped libneon dependency if Qt 5.4 is available
* Keep files open very short, that avoid lock problems on Windows * Keep files open very short, that avoid lock problems on Windows
especially with office software but also others. * especially with office software but also others.
* Merged some NetBSD patches * Merged some NetBSD patches
* Selective sync support for owncloudcmd * Selective sync support for owncloudcmd
* Reorganize the source repository * Reorganize the source repository
@ -514,13 +667,17 @@ version 1.8.0 (release 2015-03-17)
* A huge amount of bug fixes in all areas of the client. * A huge amount of bug fixes in all areas of the client.
* almost 700 commits since 1.7.1 * almost 700 commits since 1.7.1
1.7 Series ChangeLog
====================
version 1.7.1 (release 2014-12-18) version 1.7.1 (release 2014-12-18)
* Documentation fixes and updates * Documentation fixes and updates
* Nautilus Python plugin fixed for Python 3 * Nautilus Python plugin fixed for Python 3
* GUI wording fixes plus improved log messages * GUI wording fixes plus improved log messages
* Fix hidning of the database files in the sync directories * Fix hidning of the database files in the sync directories
* Compare http download size with the header value to avoid broken * Compare http download size with the header value to avoid broken
downloads, bug #2528 * downloads, bug #2528
* Avoid initial ETag fetch job at startup, which is not needed. * Avoid initial ETag fetch job at startup, which is not needed.
* Add chunk size http header to PUT requests * Add chunk size http header to PUT requests
* Fixed deteteCookie method of our CookieJar, fix for Shibboleth * Fixed deteteCookie method of our CookieJar, fix for Shibboleth
@ -543,21 +700,20 @@ version 1.7.1 (release 2014-12-18)
* Win32: Improve reliability of Installer, fix removal of Shell Extensions * Win32: Improve reliability of Installer, fix removal of Shell Extensions
version 1.7.0 (release 2014-11-07) version 1.7.0 (release 2014-11-07)
* oC7 Sharing: Handle new sharing options of ownCloud 7 correctly. * oC7 Sharing: Handle new sharing options of ownCloud 7 correctly.
* Added Selective sync: Ability to unselect server folders which are * Added Selective sync: Ability to unselect server folders which are
excluded from syncing, plus GUI and setup GUI * excluded from syncing, plus GUI and setup GUI
* Added overlay icons for Windows Explorer, Mac OS Finder and GNOME Nautilus. * Added overlay icons for Windows Explorer, Mac OS Finder and GNOME Nautilus.
Information is provided by the client via a local socket / named pipe API * Information is provided by the client via a local socket / named pipe API
which provides information about the sync status of files. * which provides information about the sync status of files.
* Improved local change detection: consider file size, detect files * Improved local change detection: consider file size, detect files
with ongoing changes and do not upload immediately * with ongoing changes and do not upload immediately
* Improved HTTP request timeout handler: all successful requests reset * Improved HTTP request timeout handler: all successful requests reset
the timeout counter * the timeout counter
* Improvements for syncing command line tool: netrc support, improved * Improvements for syncing command line tool: netrc support, improved
SSL support, non interactive mode * SSL support, non interactive mode
* Permission system: ownCloud 7 delivers file and folder permissions, * Permission system: ownCloud 7 delivers file and folder permissions,
added ability to deal with it for shared folders and more. * added ability to deal with it for shared folders and more.
* Ignore handling: Do not recurse into ignored or excluded directories * Ignore handling: Do not recurse into ignored or excluded directories
* Major sync journal database improvements for more stability and performance * Major sync journal database improvements for more stability and performance
* New library interface to sqlite3 * New library interface to sqlite3
@ -566,35 +722,40 @@ version 1.7.0 (release 2014-11-07)
* Improved logging: more useful meta info, removed noise * Improved logging: more useful meta info, removed noise
* Updated to latest Qt5 versions on Windows and OS X * Updated to latest Qt5 versions on Windows and OS X
* Fixed data loss when renaming a download temporary fails and there was * Fixed data loss when renaming a download temporary fails and there was
a conflict at the same time. * a conflict at the same time.
* Fixed missing warnings about reusing a sync folder when the back button * Fixed missing warnings about reusing a sync folder when the back button
was used in the advanced folder setup wizard. * was used in the advanced folder setup wizard.
* The 'Retry Sync' button now also restarts all downloads. * The 'Retry Sync' button now also restarts all downloads.
* Clean up temporary downloads and some extra database files when wiping a * Clean up temporary downloads and some extra database files when wiping a
folder. * folder.
* OS X: Sparkle update to provide pkg format properly * OS X: Sparkle update to provide pkg format properly
* OS X: Change distribution format from dmg to pkg with new installer. * OS X: Change distribution format from dmg to pkg with new installer.
* Windows: Fix handling of filenames with trailing dot or space * Windows: Fix handling of filenames with trailing dot or space
* Windows: Don't use the wrong way to get file mtimes in the legacy propagator. * Windows: Don't use the wrong way to get file mtimes in the legacy propagator.
1.6 Series ChangeLog
====================
version 1.6.4 (release 2014-10-22) version 1.6.4 (release 2014-10-22)
* Fix startup logic, fixes bug #1989 * Fix startup logic, fixes bug #1989
* Fix raise dialog on X11 * Fix raise dialog on X11
* Win32: fix overflow when computing the size of file > 4GiB * Win32: fix overflow when computing the size of file > 4GiB
* Use a fixed function to get files modification time, the * Use a fixed function to get files modification time, the
original one was broken for certain timezone issues, see * original one was broken for certain timezone issues, see
core bug #9781 for details * core bug #9781 for details
* Added some missing copyright headers * Added some missing copyright headers
* Avoid data corruption due to wrong error handling, bug #2280 * Avoid data corruption due to wrong error handling, bug #2280
* Do improved request timeout handling to reduce the number of * Do improved request timeout handling to reduce the number of
timed out jobs, bug #2155 * timed out jobs, bug #2155
version 1.6.3 (release 2014-09-03) * version 1.6.3 (release 2014-09-03)
* Fixed updater on OS X * Fixed updater on OS X
* Fixed memory leak in SSL button that could lead to quick memory draining * Fixed memory leak in SSL button that could lead to quick memory draining
* Fixed upload problem with files >4 GB * Fixed upload problem with files >4 GB
* MacOSX, Linux: Bring Settings window to front properly * MacOSX, Linux: Bring Settings window to front properly
* Branded clients: If no configuration is detected, try to import the data * Branded clients: If no configuration is detected, try to import the data
from a previously configured community edition. * from a previously configured community edition.
version 1.6.2 (release 2014-07-28 ) version 1.6.2 (release 2014-07-28 )
* Limit the HTTP buffer size when downloading to limit memory consumption. * Limit the HTTP buffer size when downloading to limit memory consumption.
@ -602,7 +763,7 @@ version 1.6.2 (release 2014-07-28 )
* Fix local file name clash detection for MacOSX. * Fix local file name clash detection for MacOSX.
* Limit maximum wait time to ten seconds in network limiting. * Limit maximum wait time to ten seconds in network limiting.
* Fix data corruption while trying to resume and the server does * Fix data corruption while trying to resume and the server does
not support it. * not support it.
* HTTP Credentials: Read password from legacy place if not found. * HTTP Credentials: Read password from legacy place if not found.
* Shibboleth: Fix the waiting curser that would not disapear (#1915) * Shibboleth: Fix the waiting curser that would not disapear (#1915)
* Limit memory usage to avoid mem wasting and crashes * Limit memory usage to avoid mem wasting and crashes
@ -616,18 +777,18 @@ version 1.6.1 (release 2014-06-26 )
* Fix openSSL problems for windows deployment * Fix openSSL problems for windows deployment
* Fix syncing a folder with '#' in the name * Fix syncing a folder with '#' in the name
* Fix #1845: do not update parent directory etag before sub * Fix #1845: do not update parent directory etag before sub
directories are removed * directories are removed
* Fix reappearing directories if dirs are removed during its * Fix reappearing directories if dirs are removed during its
upload * upload
* Fix app version in settings dialog, General tab * Fix app version in settings dialog, General tab
* Fix crash in FolderWizard when going offline * Fix crash in FolderWizard when going offline
* Shibboleth fixes * Shibboleth fixes
* More specific error messages (file remove during upload, open * More specific error messages (file remove during upload, open
local sync file) * local sync file)
* Use QSet rather than QHash in SyncEngine (save memory) * Use QSet rather than QHash in SyncEngine (save memory)
* Fix some memory leaks * Fix some memory leaks
* Fix some thread race problems, ie. wait for neon thread to finish * Fix some thread race problems, ie. wait for neon thread to finish
before the propagator is shut down * before the propagator is shut down
* Fix a lot of issues and warnings found by Coverity * Fix a lot of issues and warnings found by Coverity
* Fix Mac some settings dialog problems * Fix Mac some settings dialog problems
@ -650,16 +811,16 @@ version 1.6.0 (release 2014-05-30 )
* Introduce a general timeout of 300s for network operations * Introduce a general timeout of 300s for network operations
* Improve error handling, blacklisting * Improve error handling, blacklisting
* Job-based change propagation, enables faster parallel up/downloads * Job-based change propagation, enables faster parallel up/downloads
(right now only if no bandwidth limit is set and no proxy is used) * (right now only if no bandwidth limit is set and no proxy is used)
* Significantly reduced CPU load when checking for local and remote changes * Significantly reduced CPU load when checking for local and remote changes
* Speed up file stat code on Windows * Speed up file stat code on Windows
* Enforce Qt5 for Windows and Mac OS X builds * Enforce Qt5 for Windows and Mac OS X builds
* Improved owncloudcmd: SSL support, documentation * Improved owncloudcmd: SSL support, documentation
* Added advanced logging of operations (file .???.log in sync * Added advanced logging of operations (file .???.log in sync
directory) * directory)
* Avoid creating a temporary copy of the sync database (.ctmp) * Avoid creating a temporary copy of the sync database (.ctmp)
* Enable support for TLS 1.2 negotiation on platforms that use * Enable support for TLS 1.2 negotiation on platforms that use
Qt 5.2 or later * Qt 5.2 or later
* Forward server exception messages to client error messages * Forward server exception messages to client error messages
* Mac OS X: Support Notification Center in OS X 10.8+ * Mac OS X: Support Notification Center in OS X 10.8+
* Mac OS X: Use native settings dialog * Mac OS X: Use native settings dialog
@ -668,11 +829,15 @@ version 1.6.0 (release 2014-05-30 )
* Remove vio abstraction in csync * Remove vio abstraction in csync
* Avoid data loss when a client file system is not case sensitive * Avoid data loss when a client file system is not case sensitive
1.5 Series ChangeLog
====================
version 1.5.3 (release 2014-03-10 ) version 1.5.3 (release 2014-03-10 )
* Fix usage of proxies after first sync run (#1502, #1524, #1459, #1521) * Fix usage of proxies after first sync run (#1502, #1524, #1459, #1521)
* Do not wipe the credentials from config for reconnect (#1499, #1503) * Do not wipe the credentials from config for reconnect (#1499, #1503)
* Do not erase the full account config if an old version of the client stored * Do not erase the full account config if an old version of the client stored
the password (related to above) * the password (related to above)
* Fix layout of the network tab (fixes #1491) * Fix layout of the network tab (fixes #1491)
* Handle authentication requests by a Shibboleth IdP * Handle authentication requests by a Shibboleth IdP
* Shibboleth: If no connection is available, don't open the login window * Shibboleth: If no connection is available, don't open the login window
@ -701,34 +866,34 @@ version 1.5.2 (release 2014-02-26 )
version 1.5.1 (release 2014-02-13 ) version 1.5.1 (release 2014-02-13 )
* Added an auto updater that updates the client if a * Added an auto updater that updates the client if a
more recent version was found automatically (Windows, Mac OS X) * more recent version was found automatically (Windows, Mac OS X)
* Added a button to the account dialog that gives information * Added a button to the account dialog that gives information
about the encryption layer used for communication, plus a * about the encryption layer used for communication, plus a
certificate information widget * certificate information widget
* Preserve the permission settings of local files rather than * Preserve the permission settings of local files rather than
setting them to a default (Bug #820) * setting them to a default (Bug #820)
* Handle windows lnk files correctly (Bug #1307) * Handle windows lnk files correctly (Bug #1307)
* Detect removes and renames in read only shares and * Detect removes and renames in read only shares and
restore the gone away files. (Bug #1386) * restore the gone away files. (Bug #1386)
* Fixes sign in/sign out and password dialog. (Bug #1353) * Fixes sign in/sign out and password dialog. (Bug #1353)
* Fixed error messages (Bug #1394) * Fixed error messages (Bug #1394)
* Lots of fixes for building with Qt5 * Lots of fixes for building with Qt5
* Changes to network limits are now also applied during a * Changes to network limits are now also applied during a
sync run * sync run
* Fixed mem leak after via valgrind on Mac * Fixed mem leak after via valgrind on Mac
* Imported the ocsync library into miralls repository. * Imported the ocsync library into miralls repository.
Adopted all build systems and packaging to that. * Adopted all build systems and packaging to that.
* Introduce a new linux packaging scheme following the * Introduce a new linux packaging scheme following the
debian upstream scheme * debian upstream scheme
* Use a refactored Linux file system watcher based on * Use a refactored Linux file system watcher based on
inotify, incl. unit tests * inotify, incl. unit tests
* Wizard: Gracefully fall back to HTTP if HTTPS connection * Wizard: Gracefully fall back to HTTP if HTTPS connection
fails, issuing a warning * fails, issuing a warning
* Fixed translation misses in the propagator * Fixed translation misses in the propagator
* Fixes in proxy configuration * Fixes in proxy configuration
* Fixes in sync journal handling * Fixes in sync journal handling
* Fix the upload progress if the local source is still * Fix the upload progress if the local source is still
changing when the upload begins. * changing when the upload begins.
* Add proxy support to owncloud commandline client * Add proxy support to owncloud commandline client
* NSIS fixes * NSIS fixes
* A lot of other fixes and minor improvements * A lot of other fixes and minor improvements
@ -765,6 +930,10 @@ version 1.5.0 (release 2013-12-12 ), csync 0.91.4 required
* Windows: Fix rename of temporary files * Windows: Fix rename of temporary files
* Windows: Fix move file operation * Windows: Fix move file operation
1.4 Series ChangeLog
====================
version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required
* Do not show the warning icon in the tray (#944) * Do not show the warning icon in the tray (#944)
* Fix manual proxy support when switching (#1016) * Fix manual proxy support when switching (#1016)
@ -780,12 +949,11 @@ version 1.4.2 (release 2013-10-18 ), csync 0.90.4 required
* Progress: Show number of deletes. * Progress: Show number of deletes.
version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required
* Translation and documentation fixes. * Translation and documentation fixes.
* Fixed error display in settings/status dialog, displays multi * Fixed error display in settings/status dialog, displays multi
line error messages now correctly. * line error messages now correctly.
* Wait up to 30 secs before complaining about missing systray * Wait up to 30 secs before complaining about missing systray
Fixes bug #949 * Fixes bug #949
* Fixed utf8 issues with basic auth authentication, fixes bug #941 * Fixed utf8 issues with basic auth authentication, fixes bug #941
* Fixed remote folder selector, avoid recursive syncing, fixes bug #962 * Fixed remote folder selector, avoid recursive syncing, fixes bug #962
* Handle and display network problems at startup correctly. * Handle and display network problems at startup correctly.
@ -802,7 +970,6 @@ version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required
* Various minor code fixes * Various minor code fixes
version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
* New Scheduler: Only sync when there are actual changes in the server * New Scheduler: Only sync when there are actual changes in the server
* Add a Settings Dialog, move Proxy Settings there * Add a Settings Dialog, move Proxy Settings there
* Transform folder Status Dialog into Account Settings, provide feedback via context menu * Transform folder Status Dialog into Account Settings, provide feedback via context menu
@ -813,7 +980,7 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
* Move ability to switch to mono icons from a switch to a Settings option * Move ability to switch to mono icons from a switch to a Settings option
* Add "Launch on System Startup" GUI option * Add "Launch on System Startup" GUI option
* Add "Show Desktop Nofications"GUI option (enabled by default) * Add "Show Desktop Nofications"GUI option (enabled by default)
top optionally disable sync notifications * top optionally disable sync notifications
* Add Help item, pointing to online reference * Add Help item, pointing to online reference
* Implement graphical selection of remote folders in FolderWizard * Implement graphical selection of remote folders in FolderWizard
* Allow custom ignore patterns * Allow custom ignore patterns
@ -832,11 +999,14 @@ version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
* Require Qt 4.7 * Require Qt 4.7
* Known issue: Under certain conditions, a file will only get uploaded after up to five minutes * Known issue: Under certain conditions, a file will only get uploaded after up to five minutes
version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
1.3 Series ChangeLog
====================
version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
* Default proxy port to 8080 * Default proxy port to 8080
* Don't lose proxy settings when changing passwords * Don't lose proxy settings when changing passwords
* Support SOCKS5 proxy (useful in combination with ssh *D) * Support SOCKS5 proxy (useful in combination with ssh* *D)
* Propagate proxy changes to csync at runtime * Propagate proxy changes to csync at runtime
* Improve proxy wizard * Improve proxy wizard
* Display proxy errors * Display proxy errors
@ -852,7 +1022,7 @@ version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
* Remove journal when reusing a directory that used to have a journal before * Remove journal when reusing a directory that used to have a journal before
* Visual clean up of status dialog items * Visual clean up of status dialog items
* Wizard: When changing the URL or user name, allow the user to push his data * Wizard: When changing the URL or user name, allow the user to push his data
to the new location or wipe the folder and start from scratch * to the new location or wipe the folder and start from scratch
* Wizard: Make setting a custom folder as a sync target work again * Wizard: Make setting a custom folder as a sync target work again
* Fix application icon * Fix application icon
* User-Agent now contains "Mozilla/5.0" and the Platform name (for firewall/proxy compat) * User-Agent now contains "Mozilla/5.0" and the Platform name (for firewall/proxy compat)
@ -860,6 +1030,10 @@ version 1.3.0 (release 2013-06-25 ), csync 0.80.0 required
* New setup wizard, defaulting to root syncing (only for new setups) * New setup wizard, defaulting to root syncing (only for new setups)
* Improved thread stop/termination * Improved thread stop/termination
1.2 Series ChangeLog
====================
version 1.2.5 (release 2013-04-23 ), csync 0.70.7 required version 1.2.5 (release 2013-04-23 ), csync 0.70.7 required
* [Fixes] NSIS installer fixes * [Fixes] NSIS installer fixes
* [Fixes] Fix crash race by making certificateChain() thread safe * [Fixes] Fix crash race by making certificateChain() thread safe
@ -925,6 +1099,10 @@ version 1.2.0 (release 2013-01-24 ), csync 0.70.2 required
* [Platform] cmake fixes. * [Platform] cmake fixes.
* [Platform] Improved, more detailed error reporting. * [Platform] Improved, more detailed error reporting.
1.1 Series ChangeLog
====================
version 1.1.4 (release 2012-12-19 ), csync 0.60.4 required version 1.1.4 (release 2012-12-19 ), csync 0.60.4 required
* No changes to mirall, only csync fixes. * No changes to mirall, only csync fixes.
@ -934,7 +1112,7 @@ version 1.1.3 (release 2012-11-30 ), csync 0.60.3 required
version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required
* [Fixes] Allow to properly cancel the password dialog. * [Fixes] Allow to properly cancel the password dialog.
* [Fixes] Share folder name correctly percent encoded with old Qt * [Fixes] Share folder name correctly percent encoded with old Qt
4.6 builds ie. Debian. * * * * 4.6 builds ie. Debian.
* [Fixes] If local sync dir is not existing, create it. * [Fixes] If local sync dir is not existing, create it.
* [Fixes] lots of other minor fixes. * [Fixes] lots of other minor fixes.
* [GUI] Display error messages in status dialog. * [GUI] Display error messages in status dialog.
@ -942,30 +1120,30 @@ version 1.1.2 (release 2012-11-26 ), csync 0.60.2 required
* [GUI] Show username for connection in statusdialog. * [GUI] Show username for connection in statusdialog.
* [GUI] Show intro wizard on new connection setup. * [GUI] Show intro wizard on new connection setup.
* [APP] Use CredentialStore to better support various credential * [APP] Use CredentialStore to better support various credential
backends. * * * backends.
* [APP] Handle missing local folder more robust: Create it if * [APP] Handle missing local folder more robust: Create it if
missing instead of ignoring. * * * missing instead of ignoring.
* [APP] Simplify treewalk code. * [APP] Simplify treewalk code.
* [Platform] Fix Mac building * [Platform] Fix Mac building
version 1.1.1 (release 2012-10-18), csync 0.60.1 required version 1.1.1 (release 2012-10-18), csync 0.60.1 required
* [GUI] Allow changing folder name in single folder mode * [GUI]* Allow changing folder name in single folder mode
* [GUI] Windows: Add license to installer * [GUI]* Windows: Add license to installer
* [GUI] owncloud --logwindow will bring up the log window * [GUI]* owncloud --logwindow will bring up the log window
in an already running instance * * * * in an already running instance
* [Fixes] Make sure SSL errors are always handled * [Fixes] Make sure SSL errors are always handled
* [Fixes] Allow special characters in folder alias * [Fixes] Allow special characters in folder alias
* [Fixes] Proper workaround for Menu bug in Ubuntu * [Fixes] Proper workaround for Menu bug in Ubuntu
* [Fixes] csync: Fix improper memory cleanup which could * [Fixes] csync: Fix improper memory cleanup which could
cause memory leaks and crashes * * * * cause memory leaks and crashes
* [Fixes] csync: Fix memory leak * [Fixes] csync: Fix memory leak
* [Fixes] csync: Allow single quote (') in file names * [Fixes] csync: Allow single quote (') in file names
* [Fixes] csync: Remove stray temporary files * [Fixes] csync: Remove stray temporary files
* [GUI] Reworked tray context menu. * [GUI]* Reworked tray context menu.
* [GUI] Users can now sync the server root folder. * [GUI]* Users can now sync the server root folder.
* [Fixes] Proxy support: now supports Proxy Auto-Configuration (PAC) * [Fixes] Proxy support: now supports Proxy Auto-Configuration (PAC)
on Windows, reliability fixes across all OSes. * * * * on Windows, reliability fixes across all OSes.
* [Fixes] Url entry field in setup assistant handles http/https correctly. * [Fixes] Url entry field in setup assistant handles http/https correctly.
* [Fixes] Button enable state in status dialog. * [Fixes] Button enable state in status dialog.
* [Fixes] Crash fixed on ending the client, tray icon related. * [Fixes] Crash fixed on ending the client, tray icon related.
@ -978,11 +1156,15 @@ version 1.1.1 (release 2012-10-18), csync 0.60.1 required
* [Platform] Windows: ownCloud gets added to autorun by default. * [Platform] Windows: ownCloud gets added to autorun by default.
* [Platform] insert correct version info from cmake. * [Platform] insert correct version info from cmake.
* [Platform] csync conf file and database were moved to the users app data * [Platform] csync conf file and database were moved to the users app data
directory, away from the .csync dir. * * * * * directory, away from the .csync dir.
* Renamed exclude.lst to sync-exclude.lst and moved it to ** * * Renamed exclude.lst to sync-exclude.lst and moved it to
/etc/appName()/ for more clean packaging. From the user path, * * * * /etc/appName()/ for more clean packaging. From the user path,
still exclude.lst is read if sync-exclude.lst is not existing. * * * * still exclude.lst is read if sync-exclude.lst is not existing.
* Placed custom.ini with customization options to /etc/appName() ** * * Placed custom.ini with customization options to /etc/appName()
1.0 Series ChangeLog
====================
version 1.0.5 (release 2012-08-14), csync 0.50.8 required version 1.0.5 (release 2012-08-14), csync 0.50.8 required
* [Fixes] Fixed setup dialog: Really use https if checkbox is activated. * [Fixes] Fixed setup dialog: Really use https if checkbox is activated.
@ -1000,23 +1182,23 @@ version 1.0.4 (release 2012-08-10), csync 0.50.8 required
* [GUI] Removed Log Window Button, log available through command line. * [GUI] Removed Log Window Button, log available through command line.
* [GUI] Proxy configuration dialog added. * [GUI] Proxy configuration dialog added.
* [GUI] Added Translations to languages Slovenian, Polish, Catalan, * [GUI] Added Translations to languages Slovenian, Polish, Catalan,
Portuguese (Brazil), German, Greek, Spanish, Czech, Italian, Slovak, * * * Portuguese (Brazil), German, Greek, Spanish, Czech, Italian, Slovak,
French, Russian, Japanese, Swedish, Portuguese (Portugal) * * * French, Russian, Japanese, Swedish, Portuguese (Portugal)
all with translation rate >90%. * * * all with translation rate >90%.
* [Fixes] Loading of self signed certs into Networkmanager (#oc-843) * [Fixes] Loading of self signed certs into Networkmanager (#oc-843)
* [Fixes] Win32: Handle SSL dll loading correctly. * [Fixes] Win32: Handle SSL dll loading correctly.
* [Fixes] Many other small fixes and improvements. * [Fixes] Many other small fixes and improvements.
version 1.0.3 (release 2012-06-19), csync 0.50.7 required version 1.0.3 (release 2012-06-19), csync 0.50.7 required
* [GUI] Added a log window which catches the logging if required and * [GUI] Added a log window which catches the logging if required and
allows to save for information. * * * allows to save for information.
* [CMI] Added options --help, --logfile and --logflush * [CMI] Added options --help, --logfile and --logflush
* [APP] Allow to specify sync frequency in the config file. * [APP] Allow to specify sync frequency in the config file.
* [Fixes] Do not use csync database files from a sync before. * [Fixes] Do not use csync database files from a sync before.
* [Fixes] In Connection wizard, write the final config onyl if * [Fixes] In Connection wizard, write the final config onyl if
the user really accepted. Also remove the former database. * * * * the user really accepted. Also remove the former database.
* [Fixes] More user expected behaviour deletion of sync folder local * [Fixes] More user expected behaviour deletion of sync folder local
and remote. * * * * and remote.
* [Fixes] Allow special characters in the sync directory names * [Fixes] Allow special characters in the sync directory names
* [Fixes] Win32: Fixed directory removal with special character dirs. * [Fixes] Win32: Fixed directory removal with special character dirs.
* [Fixes] MacOS: Do not flood the system log any more * [Fixes] MacOS: Do not flood the system log any more
@ -1035,7 +1217,7 @@ version 1.0.2 (release 2012-05-18), csync 0.50.6 required
* [Fixes] Dialogs comes to front on click * [Fixes] Dialogs comes to front on click
* [Fixes] Open local sync folder from tray and status for win32 * [Fixes] Open local sync folder from tray and status for win32
* [Fixes] Load exclude.lst correctly on MacOSX * [Fixes] Load exclude.lst correctly on MacOSX
+ csync fixes. * + csync fixes.
version 1.0.1 (release 2012-04-18), csync 0.50.5 required version 1.0.1 (release 2012-04-18), csync 0.50.5 required
* [Security] Support SSL Connections * [Security] Support SSL Connections

View File

@ -3,10 +3,10 @@ set( APPLICATION_SHORTNAME "Nextcloud" )
set( APPLICATION_EXECUTABLE "nextcloud" ) set( APPLICATION_EXECUTABLE "nextcloud" )
set( APPLICATION_DOMAIN "nextcloud.com" ) set( APPLICATION_DOMAIN "nextcloud.com" )
set( APPLICATION_VENDOR "Nextcloud GmbH" ) set( APPLICATION_VENDOR "Nextcloud GmbH" )
set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE string "URL for updater" ) set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING "URL for updater" )
set( APPLICATION_HELP_URL "" CACHE string "URL for the help menu" ) set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
set( APPLICATION_ICON_NAME "Nextcloud" ) set( APPLICATION_ICON_NAME "Nextcloud" )
set( APPLICATION_SERVER_URL "" CACHE string "URL for the server to use. If entered the server can only connect to this instance" ) set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
set( LINUX_PACKAGE_SHORTNAME "nextcloud" ) set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
@ -20,14 +20,14 @@ set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-back
# set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt ) # set( APPLICATION_LICENSE "${OEM_THEME_DIR}/license.txt )
option( WITH_CRASHREPORTER "Build crashreporter" OFF ) option( WITH_CRASHREPORTER "Build crashreporter" OFF )
#set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE string "URL for crash reporter" ) #set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE STRING "URL for crash reporter" )
#set( CRASHREPORTER_ICON ":/owncloud-icon.png" ) #set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
option( WITH_PROVIDERS "Build with providers list" ON ) option( WITH_PROVIDERS "Build with providers list" ON )
## Theming options ## Theming options
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE string "Hex color of the wizard header background") set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "#0082c9" CACHE STRING "Hex color of the wizard header background")
set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE string "Hex color of the text in the wizard header") set( APPLICATION_WIZARD_HEADER_TITLE_COLOR "#ffffff" CACHE STRING "Hex color of the text in the wizard header")
option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.png' else the default application icon is used" ON ) option( APPLICATION_WIZARD_USE_CUSTOM_LOGO "Use the logo from ':/client/theme/colored/wizard_logo.png' else the default application icon is used" ON )

View File

@ -1,6 +1,6 @@
set( MIRALL_VERSION_MAJOR 2 ) set( MIRALL_VERSION_MAJOR 2 )
set( MIRALL_VERSION_MINOR 5 ) set( MIRALL_VERSION_MINOR 7 )
set( MIRALL_VERSION_PATCH 3 ) set( MIRALL_VERSION_PATCH 0 )
set( MIRALL_VERSION_YEAR 2019 ) set( MIRALL_VERSION_YEAR 2019 )
set( MIRALL_SOVERSION 0 ) set( MIRALL_SOVERSION 0 )

View File

@ -1,22 +1,22 @@
nextcloud-client (2.3.3-1.0~cosmic1) cosmic; urgency=medium nextcloud-client (2.3.3-1.0~eoan1) eoan; urgency=medium
* Debian build support for the forked client. * Debian build support for the forked client.
-- István Váradi <ivaradi@varadiistvan.hu> Mon, 6 Nov 2017 20:20:04 +0100 -- István Váradi <ivaradi@varadiistvan.hu> Mon, 6 Nov 2017 20:20:04 +0100
nextcloud-client (2.3.1-1.0~cosmic1) cosmic; urgency=medium nextcloud-client (2.3.1-1.0~eoan1) eoan; urgency=medium
* New upstream version * New upstream version
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 23 Mar 2017 19:07:36 +0100 -- István Váradi <ivaradi@varadiistvan.hu> Thu, 23 Mar 2017 19:07:36 +0100
nextcloud-client (2.3.0-1.0~cosmic1) cosmic; urgency=medium nextcloud-client (2.3.0-1.0~eoan1) eoan; urgency=medium
* New upstream version * New upstream version
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 21 Mar 2017 19:34:13 +0100 -- István Váradi <ivaradi@varadiistvan.hu> Tue, 21 Mar 2017 19:34:13 +0100
nextcloud-client (2.2.4-1.4~cosmic1) cosmic; urgency=medium nextcloud-client (2.2.4-1.4~eoan1) eoan; urgency=medium
* The locale-specific icon names are correct too * The locale-specific icon names are correct too

View File

@ -0,0 +1,89 @@
Source: nextcloud-client
Section: contrib/devel
Priority: optional
Maintainer: István Váradi <ivaradi@varadiistvan.hu>
Build-Depends: cmake,
debhelper,
cdbs,
dh-python,
extra-cmake-modules (>= 5.16),
kdelibs5-dev,
libkf5kio-dev,
libcmocka-dev,
libhttp-dav-perl,
libinotify-dev [kfreebsd-any],
libqt5svg5-dev,
libqt5webkit5-dev,
libsqlite3-dev,
libssl-dev (>= 1.1.0),
zlib1g-dev,
optipng,
pkg-kde-tools,
python-sphinx | python3-sphinx,
python3-all,
qt5keychain-dev,
qtwebengine5-dev,
qtdeclarative5-dev,
qttools5-dev,
qttools5-dev-tools,
xvfb
Standards-Version: 3.9.8
Homepage: https://github.com/nextcloud/client_theming
#Vcs-Git: git://anonscm.debian.org/collab-maint/nextcloud-client.git
#Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/nextcloud-client.git
Package: nextcloud-client
Architecture: any
Depends: libnextcloudsync0 (=${binary:Version}), ${shlibs:Depends}, ${misc:Depends}, nextcloud-client-l10n
Description: Nextcloud desktop sync client
Use the desktop client to keep your files synchronized
between your Nextcloud server and your desktop. Select
one or more directories on your local machine and always
have access to your latest files wherever you are.
Package: libnextcloudsync0
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Nextcloud sync library
Used by the Nextcloud desktop client as the synchronization engine.
Package: libnextcloudsync-dev
Architecture: any
Section: contrib/libdevel
Depends: libnextcloudsync0 (=${binary:Version}), ${misc:Depends}
Description: Nextcloud sync library development files
The headers and development library for the Nextcloud sync library.
Package: nextcloud-client-l10n
Architecture: all
Depends: ${misc:Depends}
Description: Nextcloud client internatialization files
The translation files.
Package: nextcloud-client-nautilus
Architecture: all
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nautilus, nautilus, ${misc:Depends}
Description: Nautilus plugin for Nextcloud
This package contains a Nautilus plugin to display
synchronization status icons for Nextcloud files.
Package: nextcloud-client-nemo
Architecture: all
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-nemo | nemo-python, nemo, ${misc:Depends}
Description: Nemo plugin for Nextcloud
This package contains a Nemo plugin to display
synchronization status icons for Nextcloud files.
Package: nextcloud-client-caja
Architecture: all
Depends: nextcloud-client (>=${binary:Version}), libnextcloudsync0, python-caja, caja, ${misc:Depends}
Description: Caja plugin for Nextcloud
This package contains a Caja plugin to display
synchronization status icons for Nextcloud files.
Package: nextcloud-client-dolphin
Architecture: any
Depends: dolphin (>= 4:15.12.1), libnextcloudsync0 (= ${binary:Version}), nextcloud-client, ${misc:Depends}, ${shlibs:Depends}
Description: Dolphin plugin for Nextcloud
This package contains a Dolphin plugin to display
synchronization status icons for Nextcloud files.

View File

@ -1,40 +1,12 @@
--- nextcloud-client-2.4.0.orig/src/gui/wizard/owncloudoauthcredspage.cpp --- nextcloud-client-2.5.3.orig/src/3rdparty/kmessagewidget/kmessagewidget.cpp 2019-07-26 18:40:34.949349387 +0000
+++ nextcloud-client-2.4.0/src/gui/wizard/owncloudoauthcredspage.cpp +++ nextcloud-client-2.5.3/src/3rdparty/kmessagewidget/kmessagewidget.cpp 2019-07-26 18:41:39.866478051 +0000
@@ -53,10 +53,8 @@ OwncloudOAuthCredsPage::OwncloudOAuthCredsPage() @@ -105,6 +105,9 @@
_ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu); q->setMessageType(KMessageWidget::Information);
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
auto menu = new QMenu(_ui.openLinkButton);
- menu->addAction(tr("Copy link to clipboard"), this, [this] {
- if (_asyncAuth)
- QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
- });
+ auto action = menu->addAction(tr("Copy link to clipboard"));
+ connect(action, &QAction::triggered, this, &OwncloudOAuthCredsPage::copyLinkToClipboard);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
});
@@ -131,4 +129,11 @@ bool OwncloudOAuthCredsPage::isComplete() const
return false; /* We can never go forward manually */
} }
+void OwncloudOAuthCredsPage::copyLinkToClipboard() +template <typename T>
+{ +constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
+ if (_asyncAuth)
+ QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
+}
+ +
+ void KMessageWidgetPrivate::createLayout()
} // namespace OCC {
--- nextcloud-client-2.4.0.orig/src/gui/wizard/owncloudoauthcredspage.h delete content->layout();
+++ nextcloud-client-2.4.0/src/gui/wizard/owncloudoauthcredspage.h
@@ -57,6 +57,10 @@ public:
QString _refreshToken;
QScopedPointer<OAuth> _asyncAuth;
Ui_OwncloudOAuthCredsPage _ui;
+
+protected slots:
+ void copyLinkToClipboard();
+
};
} // namespace OCC

View File

@ -51,7 +51,7 @@ if ! wget http://ppa.launchpad.net/${repo}/ubuntu/pool/main/n/nextcloud-client/n
origsourceopt="-sa" origsourceopt="-sa"
fi fi
for distribution in xenial bionic cosmic disco stable; do for distribution in xenial bionic disco eoan stable; do
rm -rf nextcloud-client_${basever} rm -rf nextcloud-client_${basever}
cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever} cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever}

View File

@ -70,7 +70,9 @@ def collectEntries(baseCommit, baseVersion, kind):
lastVersionTag = None lastVersionTag = None
lastCMAKEVersion = None lastCMAKEVersion = None
for line in output.splitlines(): for line in output.splitlines():
(commit, name, email, date, revdate, subject) = line.split("\t") words = line.split("\t")
(commit, name, email, date, revdate) = words[0:5]
subject = "\t".join(words[5:])
revdate = datetime.datetime.utcfromtimestamp(long(revdate)).strftime("%Y%m%d.%H%M%S") revdate = datetime.datetime.utcfromtimestamp(long(revdate)).strftime("%Y%m%d.%H%M%S")
kind = "beta" kind = "beta"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

@ -28,6 +28,7 @@
<file>resources/copy.svg</file> <file>resources/copy.svg</file>
<file>resources/state-sync.svg</file> <file>resources/state-sync.svg</file>
<file>resources/add.png</file> <file>resources/add.png</file>
<file>resources/state-info.svg</file>
</qresource> </qresource>
<qresource prefix="/"/> <qresource prefix="/"/>
</RCC> </RCC>

View File

@ -47,7 +47,13 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
endif (fn MATCHES ".*128.*") endif (fn MATCHES ".*128.*")
if (fn MATCHES ".*256.*" ) if (fn MATCHES ".*256.*" )
list (APPEND _icons ${it}) list (APPEND _icons ${it})
endif (fn MATCHES ".*256.*") endif (fn MATCHES ".*256.*")
if (fn MATCHES ".*512.*" )
list (APPEND _icons ${it})
endif (fn MATCHES ".*512.*")
if (fn MATCHES ".*1024.*" )
list (APPEND _icons ${it})
endif (fn MATCHES ".*1024.*")
endforeach (it) endforeach (it)
if (_icons) if (_icons)
add_custom_command(OUTPUT ${_outfilename}.ico ${_outfilename}.rc add_custom_command(OUTPUT ${_outfilename}.ico ${_outfilename}.rc
@ -104,14 +110,14 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
foreach (it ${files}) foreach (it ${files})
if (it MATCHES ".*sidebar-16.*") if (it MATCHES ".*sidebar-16.*")
configure_file(${it} ${appsources}.iconset/sidebar_16x16.png COPYONLY) configure_file(${it} ${appsources}.iconset/sidebar_16x16.png COPYONLY)
elseif (it MATCHES ".*sidebar-18.*")
configure_file(${it} ${appsources}.iconset/sidebar_18x18.png COPYONLY)
elseif (it MATCHES ".*sidebar-32.*") elseif (it MATCHES ".*sidebar-32.*")
configure_file(${it} ${appsources}.iconset/sidebar_18x18.png COPYONLY)
configure_file(${it} ${appsources}.iconset/sidebar_16x16@2x.png COPYONLY) configure_file(${it} ${appsources}.iconset/sidebar_16x16@2x.png COPYONLY)
configure_file(${it} ${appsources}.iconset/sidebar_32x32.png COPYONLY)
elseif (it MATCHES ".*sidebar-36.*")
configure_file(${it} ${appsources}.iconset/sidebar_18x18@2x.png COPYONLY)
elseif (it MATCHES ".*sidebar-64.*") elseif (it MATCHES ".*sidebar-64.*")
configure_file(${it} ${appsources}.iconset/sidebar_18x18@2x.png COPYONLY)
elseif (it MATCHES ".*sidebar-128.*")
configure_file(${it} ${appsources}.iconset/sidebar_32x32.png COPYONLY)
elseif (it MATCHES ".*sidebar-256.*")
configure_file(${it} ${appsources}.iconset/sidebar_32x32@2x.png COPYONLY) configure_file(${it} ${appsources}.iconset/sidebar_32x32@2x.png COPYONLY)
endif() endif()
endforeach (it) endforeach (it)

View File

@ -8,8 +8,8 @@
# #
# ecm_add_app_icon(<sources_var> # ecm_add_app_icon(<sources_var>
# ICONS <icon> [<icon> [...]] # ICONS <icon> [<icon> [...]]
# [SIDEBAR_ICONS <icon> [<icon> [...]] # Since 5.4x # [SIDEBAR_ICONS <icon> [<icon> [...]] # Since 5.49
# [OUTFILE_BASE <name>]) # Since 5.4x # [OUTFILE_BASENAME <name>]) # Since 5.49
# ) # )
# #
# The given icons, whose names must match the pattern:: # The given icons, whose names must match the pattern::
@ -27,20 +27,21 @@
# #
# ``SIDEBAR_ICONS`` can be used to add Mac OS X sidebar # ``SIDEBAR_ICONS`` can be used to add Mac OS X sidebar
# icons to the generated iconset. They are used when a folder monitored by the # icons to the generated iconset. They are used when a folder monitored by the
# application is dragged into Finder's sidebar. Since 5.4x. # application is dragged into Finder's sidebar. Since 5.49.
# #
# ``OUTFILE_BASE`` will be used as the basename for the icon file. If # ``OUTFILE_BASENAME`` will be used as the basename for the icon file. If
# you specify it, the icon file will be called ``<OUTFILE_BASE>.icns`` on Mac OS X # you specify it, the icon file will be called ``<OUTFILE_BASENAME>.icns`` on Mac OS X
# and ``<OUTFILE_BASE>.ico`` on Windows. If you don't specify it, it defaults # and ``<OUTFILE_BASENAME>.ico`` on Windows. If you don't specify it, it defaults
# to ``<sources_var>.<ext>``. Since 5.4x. # to ``<sources_var>.<ext>``. Since 5.49.
# #
# #
# Windows notes # Windows notes
# * Icons are compiled into the executable using a resource file. # * Icons are compiled into the executable using a resource file.
# * Icons may not show up in Windows Explorer if the executable # * Icons may not show up in Windows Explorer if the executable
# target does not have the ``WIN32_EXECUTABLE`` property set. # target does not have the ``WIN32_EXECUTABLE`` property set.
# * The tool png2ico is required. See :find-module:`FindPng2Ico`. # * One of the tools png2ico (See :find-module:`FindPng2Ico`) or
# * Supported sizes: 16, 32, 48, 64, 128. # icotool (see :find-module:`FindIcoTool`) is required.
# * Supported sizes: 16, 24, 32, 48, 64, 128, 256, 512 and 1024.
# #
# Mac OS X notes # Mac OS X notes
# * The executable target must have the ``MACOSX_BUNDLE`` property set. # * The executable target must have the ``MACOSX_BUNDLE`` property set.
@ -101,7 +102,7 @@ include(CMakeParseArguments)
function(ecm_add_app_icon appsources) function(ecm_add_app_icon appsources)
set(options) set(options)
set(oneValueArgs OUTFILE_BASE) set(oneValueArgs OUTFILE_BASENAME)
set(multiValueArgs ICONS SIDEBAR_ICONS) set(multiValueArgs ICONS SIDEBAR_ICONS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
@ -138,9 +139,9 @@ function(ecm_add_app_icon appsources)
endif() endif()
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;32;48;64;128;256;512;1024") _ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;24;32;48;64;128;256;512;1024")
if(ARG_SIDEBAR_ICONS) if(ARG_SIDEBAR_ICONS)
_ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;18;32;36;64") _ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;32;64;128;256")
endif() endif()
set(mac_icons set(mac_icons
@ -151,31 +152,37 @@ function(ecm_add_app_icon appsources)
${icons_at_128px} ${icons_at_128px}
${icons_at_256px} ${icons_at_256px}
${icons_at_512px} ${icons_at_512px}
${icons_at_1024px} ${icons_at_1024px})
set(mac_sidebar_icons
# Sidebar Icons: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15 # Sidebar Icons: https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15
${sidebar_icons_at_16px} ${sidebar_icons_at_16px}
${sidebar_icons_at_18px}
${sidebar_icons_at_32px} ${sidebar_icons_at_32px}
${sidebar_icons_at_36px} ${sidebar_icons_at_64px}
${sidebar_icons_at_64px}) ${sidebar_icons_at_128px}
if (NOT icons_at_128px) ${sidebar_icons_at_256px})
message(AUTHOR_WARNING "No 128px icon provided; this will not work on Mac OS X")
if (NOT (mac_icons OR mac_sidebar_icons))
message(AUTHOR_WARNING "No icons suitable for use on macOS provided")
endif() endif()
set(windows_icons ${icons_at_16px} set(windows_icons ${icons_at_16px}
${icons_at_32px} ${icons_at_24px}
${icons_at_48px} ${icons_at_32px}
${icons_at_64px} ${icons_at_48px}
${icons_at_128px} ${icons_at_64px}
${icons_at_256px}) ${icons_at_128px}
if (NOT windows_icons) ${icons_at_256px}
${icons_at_512px}
${icons_at_1024px})
if (NOT (windows_icons))
message(AUTHOR_WARNING "No icons suitable for use on Windows provided") message(AUTHOR_WARNING "No icons suitable for use on Windows provided")
endif() endif()
if (ARG_OUTFILE_BASE) if (ARG_OUTFILE_BASENAME)
set (_outfilebasename "${ARG_OUTFILE_BASE}") set (_outfilebasename "${ARG_OUTFILE_BASENAME}")
else() else()
set (_outfilebasename "${appsources}") set (_outfilebasename "${appsources}")
endif() endif()
@ -185,26 +192,15 @@ function(ecm_add_app_icon appsources)
set(saved_CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}") set(saved_CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_FIND_MODULE_DIR}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_FIND_MODULE_DIR})
find_package(Png2Ico) find_package(Png2Ico)
find_package(IcoTool)
set(CMAKE_MODULE_PATH "${saved_CMAKE_MODULE_PATH}") set(CMAKE_MODULE_PATH "${saved_CMAKE_MODULE_PATH}")
if (Png2Ico_FOUND) function(create_windows_icon_and_rc command args deps)
if (Png2Ico_HAS_RCFILE_ARGUMENT)
add_custom_command(
OUTPUT "${_outfilename}.rc" "${_outfilename}.ico"
COMMAND Png2Ico::Png2Ico
ARGS
--rcfile "${_outfilename}.rc"
"${_outfilename}.ico"
${windows_icons}
DEPENDS ${windows_icons}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
else()
add_custom_command( add_custom_command(
OUTPUT "${_outfilename}.ico" OUTPUT "${_outfilename}.ico"
COMMAND Png2Ico::Png2Ico COMMAND ${command}
ARGS "${_outfilename}.ico" ${windows_icons} ARGS ${args}
DEPENDS ${windows_icons} DEPENDS ${deps}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
) )
# this bit's a little hacky to make the dependency stuff work # this bit's a little hacky to make the dependency stuff work
@ -216,12 +212,72 @@ function(ecm_add_app_icon appsources)
DEPENDS "${_outfilename}.ico" DEPENDS "${_outfilename}.ico"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
) )
endif() endfunction()
if (IcoTool_FOUND)
list(APPEND icotool_args "-c" "-o" "${_outfilename}.ico")
# According to https://stackoverflow.com/a/40851713/2886832
# Windows always chooses the first icon above 255px, all other ones will be ignored
set(maxSize 0)
foreach(size 256 512 1024)
if(icons_at_${size}px)
set(maxSize "${size}")
endif()
endforeach()
foreach(size 16 24 32 48 64 128 ${maxSize})
if(NOT icons_at_${size}px)
continue()
endif()
set(icotool_icon_arg "")
if(size STREQUAL "${maxSize}")
# maxSize icon needs to be included as raw png
list(APPEND icotool_args "-r")
endif()
foreach(icon ${icons_at_${size}px})
list(APPEND icotool_args "${icons_at_${size}px}")
endforeach()
endforeach()
create_windows_icon_and_rc(IcoTool::IcoTool "${icotool_args}" "${windows_icons_modern}")
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE) set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
# standard png2ico has no rcfile argument
# NOTE: We generally use https://github.com/hiiamok/png2ImageMagickICO
# or similar on windows, which is why we provide resolutions >= 256px here.
# Standard png2ico will fail with this.
elseif(Png2Ico_FOUND AND NOT Png2Ico_HAS_RCFILE_ARGUMENT AND windows_icons)
set(png2ico_args)
list(APPEND png2ico_args "${_outfilename}.ico")
list(APPEND png2ico_args "${windows_icons}")
create_windows_icon_and_rc(Png2Ico::Png2Ico "${png2ico_args}" "${windows_icons}")
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
# png2ico from kdewin provides rcfile argument
elseif(Png2Ico_FOUND AND windows_icons)
add_custom_command(
OUTPUT "${_outfilename}.rc" "${_outfilename}.ico"
COMMAND Png2Ico::Png2Ico
ARGS
--rcfile "${_outfilename}.rc"
"${_outfilename}.ico"
${windows_icons}
DEPENDS ${windows_icons}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
set(${appsources} "${${appsources}};${_outfilename}.rc" PARENT_SCOPE)
# else none of the supported tools was found
else() else()
message(WARNING "Unable to find the png2ico utility - application will not have an application icon!") message(WARNING "Unable to find the png2ico or icotool utilities or icons in matching sizes - application will not have an application icon!")
endif() endif()
elseif (APPLE AND mac_icons)
elseif (APPLE AND (mac_icons OR mac_sidebar_icons))
# first generate .iconset directory structure, then convert to .icns format using the Mac OS X "iconutil" utility, # first generate .iconset directory structure, then convert to .icns format using the Mac OS X "iconutil" utility,
# to create retina compatible icon, you need png source files in pixel resolution 16x16, 32x32, 64x64, 128x128, # to create retina compatible icon, you need png source files in pixel resolution 16x16, 32x32, 64x64, 128x128,
# 256x256, 512x512, 1024x1024 # 256x256, 512x512, 1024x1024
@ -246,8 +302,11 @@ function(ecm_add_app_icon appsources)
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
) )
list(APPEND iconset_icons list(APPEND iconset_icons
"${_outfilename}.iconset/${type}_${sizename}.png") "${_outfilename}.iconset/${type}_${sizename}.png")
endmacro() endmacro()
# List of supported sizes and filenames taken from:
# https://developer.apple.com/library/content/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html#//apple_ref/doc/uid/TP40012302-CH7-SW4
foreach(size 16 32 128 256 512) foreach(size 16 32 128 256 512)
math(EXPR double_size "2 * ${size}") math(EXPR double_size "2 * ${size}")
foreach(file ${icons_at_${size}px}) foreach(file ${icons_at_${size}px})
@ -258,14 +317,25 @@ function(ecm_add_app_icon appsources)
endforeach() endforeach()
endforeach() endforeach()
foreach(size 16 18 32) # List of supported sizes and filenames taken from:
math(EXPR double_size "2 * ${size}") # https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Finder.html#//apple_ref/doc/uid/TP40014214-CH15-SW15
foreach(file ${sidebar_icons_at_${size}px}) foreach(file ${sidebar_icons_at_16px})
copy_icon("${file}" "${size}x${size}" "sidebar") copy_icon("${file}" "16x16" "sidebar")
endforeach() endforeach()
foreach(file ${sidebar_icons_at_${double_size}px}) foreach(file ${sidebar_icons_at_32px})
copy_icon("${file}" "${size}x${size}@2x" "sidebar") copy_icon("${file}" "16x16@2x" "sidebar")
endforeach() endforeach()
foreach(file ${sidebar_icons_at_32px})
copy_icon("${file}" "18x18" "sidebar")
endforeach()
foreach(file ${sidebar_icons_at_64px})
copy_icon("${file}" "18x18@2x" "sidebar")
endforeach()
foreach(file ${sidebar_icons_at_128px})
copy_icon("${file}" "32x32" "sidebar")
endforeach()
foreach(file ${sidebar_icons_at_256px})
copy_icon("${file}" "32x32@2x" "sidebar")
endforeach() endforeach()
# generate .icns icon file # generate .icns icon file

View File

@ -99,7 +99,7 @@ Activity
.. index:: activity, recent changes, sync activity .. index:: activity, recent changes, sync activity
The Activity window, which can be invoked either from the main menu (``Recent The Activity window, which can be invoked either from the main menu (``Recent
Changes -> Details``) or the Activity tab on the left side of the settings Changes -> View more activity``) or the Activity tab on the left side of the settings
window, provides an in-depth account of the recent sync activity. It will show window, provides an in-depth account of the recent sync activity. It will show
files that have not been synced because they are on the ignored files list, or files that have not been synced because they are on the ignored files list, or
because they cannot be synced in a cross-platform manner due to containing because they cannot be synced in a cross-platform manner due to containing

1
resources/state-info.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4.2333 4.2333" version="1.1" height="16" width="16"><g id="g3830" transform="matrix(.87498 0 0 .87498 .26458 -255.9)"><circle id="circle3818" stroke-width=".25066" fill="#2268ab" r="2.1167" cy="294.88" cx="2.1167" /><g id="g3828" stroke-linejoin="round" stroke-linecap="round" fill="none" /></g><path style="fill:#ffffff;stroke-width:0.17859235" d="m 1.6076619,2.1122981 c 0.027682,0.068222 0.058043,0.1232286 0.115014,0.043934 0.072686,-0.047862 0.314322,-0.2548509 0.29682,-0.061078 C 1.953774,2.4553739 1.8705497,2.8125586 1.8105428,3.1738508 1.7403561,3.3728027 1.9237704,3.5430012 2.1028984,3.4078068 2.295421,3.3181535 2.4582973,3.1779584 2.6256382,3.0488362 2.599921,2.9911507 2.5809903,2.9077482 2.5191973,2.9868644 2.4356161,3.0297263 2.2566665,3.2222491 2.2163047,3.07116 2.2725613,2.681829 2.3904322,2.3041062 2.4600833,1.9170966 2.5309844,1.7376113 2.3950755,1.5200858 2.210054,1.6736753 1.985742,1.7836882 1.8010774,1.9562083 1.6076619,2.1122981 Z M 2.4041839,0.77839186 C 2.1702279,0.77446305 2.0636081,1.1609366 2.2889917,1.2561264 2.4716917,1.3236342 2.659928,1.1286114 2.6086721,0.94358974 2.5911701,0.8467927 2.5018738,0.77035521 2.4038266,0.77749894 Z" /></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,512 @@
/* This file is part of the KDE libraries
*
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "kmessagewidget.h"
#include <QAction>
#include <QApplication>
#include <QEvent>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QShowEvent>
#include <QTimeLine>
#include <QToolButton>
#include <QStyle>
//---------------------------------------------------------------------
// KMessageWidgetPrivate
//---------------------------------------------------------------------
class KMessageWidgetPrivate
{
public:
void init(KMessageWidget *);
KMessageWidget *q;
QFrame *content = nullptr;
QLabel *iconLabel = nullptr;
QLabel *textLabel = nullptr;
QToolButton *closeButton = nullptr;
QTimeLine *timeLine = nullptr;
QIcon icon;
bool ignoreShowEventDoingAnimatedShow = false;
KMessageWidget::MessageType messageType;
bool wordWrap;
QList<QToolButton *> buttons;
QPixmap contentSnapShot;
void createLayout();
void applyStyleSheet();
void updateSnapShot();
void updateLayout();
void slotTimeLineChanged(qreal);
void slotTimeLineFinished();
int bestContentHeight() const;
};
void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
{
q = q_ptr;
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
// Note: when changing the value 500, also update KMessageWidgetTest
timeLine = new QTimeLine(500, q);
QObject::connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(slotTimeLineChanged(qreal)));
QObject::connect(timeLine, SIGNAL(finished()), q, SLOT(slotTimeLineFinished()));
content = new QFrame(q);
content->setObjectName(QStringLiteral("contentWidget"));
content->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
wordWrap = false;
iconLabel = new QLabel(content);
iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
iconLabel->hide();
textLabel = new QLabel(content);
textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QObject::connect(textLabel, &QLabel::linkActivated, q, &KMessageWidget::linkActivated);
QObject::connect(textLabel, &QLabel::linkHovered, q, &KMessageWidget::linkHovered);
QAction *closeAction = new QAction(q);
closeAction->setText(KMessageWidget::tr("&Close"));
closeAction->setToolTip(KMessageWidget::tr("Close message"));
closeAction->setIcon(QIcon(":/client/resources/close.svg")); // ivan: NC customization
QObject::connect(closeAction, &QAction::triggered, q, &KMessageWidget::animatedHide);
closeButton = new QToolButton(content);
closeButton->setAutoRaise(true);
closeButton->setDefaultAction(closeAction);
q->setMessageType(KMessageWidget::Information);
}
void KMessageWidgetPrivate::createLayout()
{
delete content->layout();
content->resize(q->size());
qDeleteAll(buttons);
buttons.clear();
Q_FOREACH (QAction *action, q->actions()) {
QToolButton *button = new QToolButton(content);
button->setDefaultAction(action);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
buttons.append(button);
}
// AutoRaise reduces visual clutter, but we don't want to turn it on if
// there are other buttons, otherwise the close button will look different
// from the others.
closeButton->setAutoRaise(buttons.isEmpty());
if (wordWrap) {
QGridLayout *layout = new QGridLayout(content);
// Set alignment to make sure icon does not move down if text wraps
layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
layout->addWidget(textLabel, 0, 1);
if (buttons.isEmpty()) {
// Use top-vertical alignment like the icon does.
layout->addWidget(closeButton, 0, 2, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
} else {
// Use an additional layout in row 1 for the buttons.
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addStretch();
Q_FOREACH (QToolButton *button, buttons) {
// For some reason, calling show() is necessary if wordwrap is true,
// otherwise the buttons do not show up. It is not needed if
// wordwrap is false.
button->show();
buttonLayout->addWidget(button);
}
buttonLayout->addWidget(closeButton);
layout->addItem(buttonLayout, 1, 0, 1, 2);
}
} else {
QHBoxLayout *layout = new QHBoxLayout(content);
layout->addWidget(iconLabel);
layout->addWidget(textLabel);
for (QToolButton *button : qAsConst(buttons)) {
layout->addWidget(button);
}
layout->addWidget(closeButton);
};
if (q->isVisible()) {
q->setFixedHeight(content->sizeHint().height());
}
q->updateGeometry();
}
void KMessageWidgetPrivate::applyStyleSheet()
{
QColor bgBaseColor;
// We have to hardcode colors here because KWidgetsAddons is a tier 1 framework
// and therefore can't depend on any other KDE Frameworks
// The following RGB color values come from the "default" scheme in kcolorscheme.cpp
switch (messageType) {
case KMessageWidget::Positive:
bgBaseColor.setRgb(39, 174, 96); // Window: ForegroundPositive
break;
case KMessageWidget::Information:
bgBaseColor.setRgb(61, 174, 233); // Window: ForegroundActive
break;
case KMessageWidget::Warning:
bgBaseColor.setRgb(246, 116, 0); // Window: ForegroundNeutral
break;
case KMessageWidget::Error:
bgBaseColor.setRgb(218, 68, 83); // Window: ForegroundNegative
break;
}
const qreal bgBaseColorAlpha = 0.2;
bgBaseColor.setAlphaF(bgBaseColorAlpha);
const QPalette palette = QGuiApplication::palette();
const QColor windowColor = palette.window().color();
const QColor textColor = palette.text().color();
const QColor border = bgBaseColor;
// Generate a final background color from overlaying bgBaseColor over windowColor
const int newRed = (bgBaseColor.red() * bgBaseColorAlpha) + (windowColor.red() * (1 - bgBaseColorAlpha));
const int newGreen = (bgBaseColor.green() * bgBaseColorAlpha) + (windowColor.green() * (1 - bgBaseColorAlpha));
const int newBlue = (bgBaseColor.blue() * bgBaseColorAlpha) + (windowColor.blue() * (1 - bgBaseColorAlpha));
const QColor bgFinalColor = QColor(newRed, newGreen, newBlue);
content->setStyleSheet(
QString::fromLatin1(".QFrame {"
"background-color: %1;"
"border-radius: 4px;"
"border: 2px solid %2;"
"margin: %3px;"
"}"
".QLabel { color: %4; }"
)
.arg(bgFinalColor.name())
.arg(border.name())
// DefaultFrameWidth returns the size of the external margin + border width. We know our border is 1px, so we subtract this from the frame normal QStyle FrameWidth to get our margin
.arg(q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, q) - 1)
.arg(textColor.name())
);
}
void KMessageWidgetPrivate::updateLayout()
{
if (content->layout()) {
createLayout();
}
}
void KMessageWidgetPrivate::updateSnapShot()
{
// Attention: updateSnapShot calls QWidget::render(), which causes the whole
// window layouts to be activated. Calling this method from resizeEvent()
// can lead to infinite recursion, see:
// https://bugs.kde.org/show_bug.cgi?id=311336
contentSnapShot = QPixmap(content->size() * q->devicePixelRatio());
contentSnapShot.setDevicePixelRatio(q->devicePixelRatio());
contentSnapShot.fill(Qt::transparent);
content->render(&contentSnapShot, QPoint(), QRegion(), QWidget::DrawChildren);
}
void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
{
q->setFixedHeight(qMin(value * 2, qreal(1.0)) * content->height());
q->update();
}
void KMessageWidgetPrivate::slotTimeLineFinished()
{
if (timeLine->direction() == QTimeLine::Forward) {
// Show
// We set the whole geometry here, because it may be wrong if a
// KMessageWidget is shown right when the toplevel window is created.
content->setGeometry(0, 0, q->width(), bestContentHeight());
// notify about finished animation
emit q->showAnimationFinished();
} else {
// hide and notify about finished animation
q->hide();
emit q->hideAnimationFinished();
}
}
int KMessageWidgetPrivate::bestContentHeight() const
{
int height = content->heightForWidth(q->width());
if (height == -1) {
height = content->sizeHint().height();
}
return height;
}
//---------------------------------------------------------------------
// KMessageWidget
//---------------------------------------------------------------------
KMessageWidget::KMessageWidget(QWidget *parent)
: QFrame(parent)
, d(new KMessageWidgetPrivate)
{
d->init(this);
}
KMessageWidget::KMessageWidget(const QString &text, QWidget *parent)
: QFrame(parent)
, d(new KMessageWidgetPrivate)
{
d->init(this);
setText(text);
}
KMessageWidget::~KMessageWidget()
{
delete d;
}
QString KMessageWidget::text() const
{
return d->textLabel->text();
}
void KMessageWidget::setText(const QString &text)
{
d->textLabel->setText(text);
updateGeometry();
}
KMessageWidget::MessageType KMessageWidget::messageType() const
{
return d->messageType;
}
void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
{
d->messageType = type;
d->applyStyleSheet();
}
QSize KMessageWidget::sizeHint() const
{
ensurePolished();
return d->content->sizeHint();
}
QSize KMessageWidget::minimumSizeHint() const
{
ensurePolished();
return d->content->minimumSizeHint();
}
bool KMessageWidget::event(QEvent *event)
{
if (event->type() == QEvent::Polish && !d->content->layout()) {
d->createLayout();
} else if (event->type() == QEvent::PaletteChange) {
d->applyStyleSheet();
} else if (event->type() == QEvent::Show && !d->ignoreShowEventDoingAnimatedShow) {
if ((height() != d->content->height()) || (d->content->pos().y() != 0)) {
d->content->move(0, 0);
setFixedHeight(d->content->height());
}
}
return QFrame::event(event);
}
void KMessageWidget::resizeEvent(QResizeEvent *event)
{
QFrame::resizeEvent(event);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->content->resize(width(), d->bestContentHeight());
}
}
int KMessageWidget::heightForWidth(int width) const
{
ensurePolished();
return d->content->heightForWidth(width);
}
void KMessageWidget::paintEvent(QPaintEvent *event)
{
QFrame::paintEvent(event);
if (d->timeLine->state() == QTimeLine::Running) {
QPainter painter(this);
painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
painter.drawPixmap(0, 0, d->contentSnapShot);
}
}
bool KMessageWidget::wordWrap() const
{
return d->wordWrap;
}
void KMessageWidget::setWordWrap(bool wordWrap)
{
d->wordWrap = wordWrap;
d->textLabel->setWordWrap(wordWrap);
QSizePolicy policy = sizePolicy();
policy.setHeightForWidth(wordWrap);
setSizePolicy(policy);
d->updateLayout();
// Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
// height is set, causing the widget to be too high.
// Mostly visible in test programs.
if (wordWrap) {
setMinimumHeight(0);
}
}
bool KMessageWidget::isCloseButtonVisible() const
{
return d->closeButton->isVisible();
}
void KMessageWidget::setCloseButtonVisible(bool show)
{
d->closeButton->setVisible(show);
updateGeometry();
}
void KMessageWidget::addAction(QAction *action)
{
QFrame::addAction(action);
d->updateLayout();
}
void KMessageWidget::removeAction(QAction *action)
{
QFrame::removeAction(action);
d->updateLayout();
}
void KMessageWidget::animatedShow()
{
// Test before styleHint, as there might have been a style change while animation was running
if (isHideAnimationRunning()) {
d->timeLine->stop();
emit hideAnimationFinished();
}
if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)
|| (parentWidget() && !parentWidget()->isVisible())) {
show();
emit showAnimationFinished();
return;
}
if (isVisible() && (d->timeLine->state() == QTimeLine::NotRunning) && (height() == d->bestContentHeight()) && (d->content->pos().y() == 0)) {
emit showAnimationFinished();
return;
}
d->ignoreShowEventDoingAnimatedShow = true;
show();
d->ignoreShowEventDoingAnimatedShow = false;
setFixedHeight(0);
int wantedHeight = d->bestContentHeight();
d->content->setGeometry(0, -wantedHeight, width(), wantedHeight);
d->updateSnapShot();
d->timeLine->setDirection(QTimeLine::Forward);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->timeLine->start();
}
}
void KMessageWidget::animatedHide()
{
// test this before isVisible, as animatedShow might have been called directly before,
// so the first timeline event is not yet done and the widget is still hidden
// And before styleHint, as there might have been a style change while animation was running
if (isShowAnimationRunning()) {
d->timeLine->stop();
emit showAnimationFinished();
}
if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) {
hide();
emit hideAnimationFinished();
return;
}
if (!isVisible()) {
// explicitly hide it, so it stays hidden in case it is only not visible due to the parents
hide();
emit hideAnimationFinished();
return;
}
d->content->move(0, -d->content->height());
d->updateSnapShot();
d->timeLine->setDirection(QTimeLine::Backward);
if (d->timeLine->state() == QTimeLine::NotRunning) {
d->timeLine->start();
}
}
bool KMessageWidget::isHideAnimationRunning() const
{
return (d->timeLine->direction() == QTimeLine::Backward)
&& (d->timeLine->state() == QTimeLine::Running);
}
bool KMessageWidget::isShowAnimationRunning() const
{
return (d->timeLine->direction() == QTimeLine::Forward)
&& (d->timeLine->state() == QTimeLine::Running);
}
QIcon KMessageWidget::icon() const
{
return d->icon;
}
void KMessageWidget::setIcon(const QIcon &icon)
{
d->icon = icon;
if (d->icon.isNull()) {
d->iconLabel->hide();
} else {
const int size = style()->pixelMetric(QStyle::PM_ToolBarIconSize);
d->iconLabel->setPixmap(d->icon.pixmap(size));
d->iconLabel->show();
}
}
#include "moc_kmessagewidget.cpp"

View File

@ -0,0 +1,346 @@
/* This file is part of the KDE libraries
*
* Copyright (c) 2011 Aurélien Gâteau <agateau@kde.org>
* Copyright (c) 2014 Dominik Haumann <dhaumann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef KMESSAGEWIDGET_H
#define KMESSAGEWIDGET_H
#include <QFrame>
class KMessageWidgetPrivate;
/**
* @class KMessageWidget kmessagewidget.h KMessageWidget
*
* @short A widget to provide feedback or propose opportunistic interactions.
*
* KMessageWidget can be used to provide inline positive or negative
* feedback, or to implement opportunistic interactions.
*
* As a feedback widget, KMessageWidget provides a less intrusive alternative
* to "OK Only" message boxes. If you want to avoid a modal KMessageBox,
* consider using KMessageWidget instead.
*
* Examples of KMessageWidget look as follows, all of them having an icon set
* with setIcon(), and the first three show a close button:
*
* \image html kmessagewidget.png "KMessageWidget with different message types"
*
* <b>Negative feedback</b>
*
* The KMessageWidget can be used as a secondary indicator of failure: the
* first indicator is usually the fact the action the user expected to happen
* did not happen.
*
* Example: User fills a form, clicks "Submit".
*
* @li Expected feedback: form closes
* @li First indicator of failure: form stays there
* @li Second indicator of failure: a KMessageWidget appears on top of the
* form, explaining the error condition
*
* When used to provide negative feedback, KMessageWidget should be placed
* close to its context. In the case of a form, it should appear on top of the
* form entries.
*
* KMessageWidget should get inserted in the existing layout. Space should not
* be reserved for it, otherwise it becomes "dead space", ignored by the user.
* KMessageWidget should also not appear as an overlay to prevent blocking
* access to elements the user needs to interact with to fix the failure.
*
* <b>Positive feedback</b>
*
* KMessageWidget can be used for positive feedback but it shouldn't be
* overused. It is often enough to provide feedback by simply showing the
* results of an action.
*
* Examples of acceptable uses:
*
* @li Confirm success of "critical" transactions
* @li Indicate completion of background tasks
*
* Example of unadapted uses:
*
* @li Indicate successful saving of a file
* @li Indicate a file has been successfully removed
*
* <b>Opportunistic interaction</b>
*
* Opportunistic interaction is the situation where the application suggests to
* the user an action he could be interested in perform, either based on an
* action the user just triggered or an event which the application noticed.
*
* Example of acceptable uses:
*
* @li A browser can propose remembering a recently entered password
* @li A music collection can propose ripping a CD which just got inserted
* @li A chat application may notify the user a "special friend" just connected
*
* @author Aurélien Gâteau <agateau@kde.org>
* @since 4.7
*/
class KMessageWidget : public QFrame
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
Q_PROPERTY(bool wordWrap READ wordWrap WRITE setWordWrap)
Q_PROPERTY(bool closeButtonVisible READ isCloseButtonVisible WRITE setCloseButtonVisible)
Q_PROPERTY(MessageType messageType READ messageType WRITE setMessageType)
Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
public:
/**
* Available message types.
* The background colors are chosen depending on the message type.
*/
enum MessageType {
Positive,
Information,
Warning,
Error
};
Q_ENUM(MessageType)
/**
* Constructs a KMessageWidget with the specified @p parent.
*/
explicit KMessageWidget(QWidget *parent = nullptr);
/**
* Constructs a KMessageWidget with the specified @p parent and
* contents @p text.
*/
explicit KMessageWidget(const QString &text, QWidget *parent = nullptr);
/**
* Destructor.
*/
~KMessageWidget() override;
/**
* Get the text of this message widget.
* @see setText()
*/
QString text() const;
/**
* Check whether word wrap is enabled.
*
* If word wrap is enabled, the message widget wraps the displayed text
* as required to the available width of the widget. This is useful to
* avoid breaking widget layouts.
*
* @see setWordWrap()
*/
bool wordWrap() const;
/**
* Check whether the close button is visible.
*
* @see setCloseButtonVisible()
*/
bool isCloseButtonVisible() const;
/**
* Get the type of this message.
* By default, the type is set to KMessageWidget::Information.
*
* @see KMessageWidget::MessageType, setMessageType()
*/
MessageType messageType() const;
/**
* Add @p action to the message widget.
* For each action a button is added to the message widget in the
* order the actions were added.
*
* @param action the action to add
* @see removeAction(), QWidget::actions()
*/
void addAction(QAction *action);
/**
* Remove @p action from the message widget.
*
* @param action the action to remove
* @see KMessageWidget::MessageType, addAction(), setMessageType()
*/
void removeAction(QAction *action);
/**
* Returns the preferred size of the message widget.
*/
QSize sizeHint() const override;
/**
* Returns the minimum size of the message widget.
*/
QSize minimumSizeHint() const override;
/**
* Returns the required height for @p width.
* @param width the width in pixels
*/
int heightForWidth(int width) const override;
/**
* The icon shown on the left of the text. By default, no icon is shown.
* @since 4.11
*/
QIcon icon() const;
/**
* Check whether the hide animation started by calling animatedHide()
* is still running. If animations are disabled, this function always
* returns @e false.
*
* @see animatedHide(), hideAnimationFinished()
* @since 5.0
*/
bool isHideAnimationRunning() const;
/**
* Check whether the show animation started by calling animatedShow()
* is still running. If animations are disabled, this function always
* returns @e false.
*
* @see animatedShow(), showAnimationFinished()
* @since 5.0
*/
bool isShowAnimationRunning() const;
public Q_SLOTS:
/**
* Set the text of the message widget to @p text.
* If the message widget is already visible, the text changes on the fly.
*
* @param text the text to display, rich text is allowed
* @see text()
*/
void setText(const QString &text);
/**
* Set word wrap to @p wordWrap. If word wrap is enabled, the text()
* of the message widget is wrapped to fit the available width.
* If word wrap is disabled, the message widget's minimum size is
* such that the entire text fits.
*
* @param wordWrap disable/enable word wrap
* @see wordWrap()
*/
void setWordWrap(bool wordWrap);
/**
* Set the visibility of the close button. If @p visible is @e true,
* a close button is shown that calls animatedHide() if clicked.
*
* @see closeButtonVisible(), animatedHide()
*/
void setCloseButtonVisible(bool visible);
/**
* Set the message type to @p type.
* By default, the message type is set to KMessageWidget::Information.
* Appropriate colors are chosen to mimic the appearance of Kirigami's
* InlineMessage.
*
* @see messageType(), KMessageWidget::MessageType
*/
void setMessageType(KMessageWidget::MessageType type);
/**
* Show the widget using an animation.
*/
void animatedShow();
/**
* Hide the widget using an animation.
*/
void animatedHide();
/**
* Define an icon to be shown on the left of the text
* @since 4.11
*/
void setIcon(const QIcon &icon);
Q_SIGNALS:
/**
* This signal is emitted when the user clicks a link in the text label.
* The URL referred to by the href anchor is passed in contents.
* @param contents text of the href anchor
* @see QLabel::linkActivated()
* @since 4.10
*/
void linkActivated(const QString &contents);
/**
* This signal is emitted when the user hovers over a link in the text label.
* The URL referred to by the href anchor is passed in contents.
* @param contents text of the href anchor
* @see QLabel::linkHovered()
* @since 4.11
*/
void linkHovered(const QString &contents);
/**
* This signal is emitted when the hide animation is finished, started by
* calling animatedHide(). If animations are disabled, this signal is
* emitted immediately after the message widget got hidden.
*
* @note This signal is @e not emitted if the widget was hidden by
* calling hide(), so this signal is only useful in conjunction
* with animatedHide().
*
* @see animatedHide()
* @since 5.0
*/
void hideAnimationFinished();
/**
* This signal is emitted when the show animation is finished, started by
* calling animatedShow(). If animations are disabled, this signal is
* emitted immediately after the message widget got shown.
*
* @note This signal is @e not emitted if the widget was shown by
* calling show(), so this signal is only useful in conjunction
* with animatedShow().
*
* @see animatedShow()
* @since 5.0
*/
void showAnimationFinished();
protected:
void paintEvent(QPaintEvent *event) override;
bool event(QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
KMessageWidgetPrivate *const d;
friend class KMessageWidgetPrivate;
Q_PRIVATE_SLOT(d, void slotTimeLineChanged(qreal))
Q_PRIVATE_SLOT(d, void slotTimeLineFinished())
};
#endif /* KMESSAGEWIDGET_H */

View File

@ -87,9 +87,36 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
} }
} }
// TODO: Right now only detection on toggle/startup, not when windows theme is switched while nextcloud is running
static inline bool hasDarkSystray_private() static inline bool hasDarkSystray_private()
{ {
return true; bool hasDarkSystray = true;
// Open registry key first, continue only on success (may be legitimately absent in earlier windows versions)
HKEY hKey;
LONG lRes = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", 0, KEY_READ, &hKey);
// classical windows function - preserve buff size for DWORD, call ExW version, store regkey value in nResult
if (lRes == ERROR_SUCCESS) {
DWORD dwBufferSize(sizeof(DWORD));
DWORD nResult(0);
// https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexw
LONG nError = ::RegQueryValueExW(hKey,
L"SystemUsesLightTheme",
NULL,
NULL,
reinterpret_cast<LPBYTE>(&nResult),
&dwBufferSize);
// if RegQuery returned no error and light theme was found, change systray return value
if (nError == ERROR_SUCCESS && nResult == 1)
hasDarkSystray = false;
return hasDarkSystray;
} else {
// fallback to true if regkey could not be determined
return hasDarkSystray;
}
} }
QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName) QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)

View File

@ -236,13 +236,29 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
return match; return match;
} }
static QByteArray leftIncludeLast(const QByteArray & arr, char c)
{
// left up to and including `c`
return arr.left(arr.lastIndexOf(c, arr.size() - 2) + 1);
}
using namespace OCC; using namespace OCC;
ExcludedFiles::ExcludedFiles() ExcludedFiles::ExcludedFiles(QString localPath)
: _localPath(std::move(localPath))
{ {
Q_ASSERT(_localPath.endsWith("/"));
// Windows used to use PathMatchSpec which allows *foo to match abc/deffoo. // Windows used to use PathMatchSpec which allows *foo to match abc/deffoo.
_wildcardsMatchSlash = Utility::isWindows(); _wildcardsMatchSlash = Utility::isWindows();
// We're in a detached exclude probably coming from a partial sync or test
if (_localPath.isEmpty())
return;
// Load exclude file from base dir
QFileInfo fi(_localPath + ".sync-exclude.lst");
if (fi.isReadable())
addInTreeExcludeFilePath(fi.absoluteFilePath());
} }
ExcludedFiles::~ExcludedFiles() ExcludedFiles::~ExcludedFiles()
@ -251,7 +267,13 @@ ExcludedFiles::~ExcludedFiles()
void ExcludedFiles::addExcludeFilePath(const QString &path) void ExcludedFiles::addExcludeFilePath(const QString &path)
{ {
_excludeFiles.insert(path); _excludeFiles[_localPath.toUtf8()].append(path);
}
void ExcludedFiles::addInTreeExcludeFilePath(const QString &path)
{
BasePathByteArray basePath = leftIncludeLast(path.toUtf8(), '/');
_excludeFiles[basePath].append(path);
} }
void ExcludedFiles::setExcludeConflictFiles(bool onoff) void ExcludedFiles::setExcludeConflictFiles(bool onoff)
@ -261,9 +283,18 @@ void ExcludedFiles::setExcludeConflictFiles(bool onoff)
void ExcludedFiles::addManualExclude(const QByteArray &expr) void ExcludedFiles::addManualExclude(const QByteArray &expr)
{ {
_manualExcludes.append(expr); addManualExclude(expr, _localPath.toUtf8());
_allExcludes.append(expr); }
prepare();
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
{
Q_ASSERT(basePath.startsWith('/'));
Q_ASSERT(basePath.endsWith('/'));
auto key = basePath;
_manualExcludes[key].append(expr);
_allExcludes[key].append(expr);
prepare(key);
} }
void ExcludedFiles::clearManualExcludes() void ExcludedFiles::clearManualExcludes()
@ -278,26 +309,47 @@ void ExcludedFiles::setWildcardsMatchSlash(bool onoff)
prepare(); prepare();
} }
bool ExcludedFiles::loadExcludeFile(const QByteArray & basePath, const QString & file)
{
QFile f(file);
if (!f.open(QIODevice::ReadOnly))
return false;
while (!f.atEnd()) {
QByteArray line = f.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue;
csync_exclude_expand_escapes(line);
_allExcludes[basePath].append(line);
}
prepare(basePath);
return true;
}
bool ExcludedFiles::reloadExcludeFiles() bool ExcludedFiles::reloadExcludeFiles()
{ {
_allExcludes.clear(); _allExcludes.clear();
// clear all regex
_bnameTraversalRegexFile.clear();
_bnameTraversalRegexDir.clear();
_fullTraversalRegexFile.clear();
_fullTraversalRegexDir.clear();
_fullRegexFile.clear();
_fullRegexDir.clear();
bool success = true; bool success = true;
foreach (const QString &file, _excludeFiles) { for (auto basePath : _excludeFiles.keys()) {
QFile f(file); for (auto file : _excludeFiles.value(basePath)) {
if (!f.open(QIODevice::ReadOnly)) { success = loadExcludeFile(basePath, file);
success = false;
continue;
}
while (!f.atEnd()) {
QByteArray line = f.readLine().trimmed();
if (line.isEmpty() || line.startsWith('#'))
continue;
csync_exclude_expand_escapes(line);
_allExcludes.append(line);
} }
} }
_allExcludes.append(_manualExcludes);
prepare(); auto endManual = _manualExcludes.cend();
for (auto kv = _manualExcludes.cbegin(); kv != endManual; ++kv) {
_allExcludes[kv.key()].append(kv.value());
prepare(kv.key());
}
return success; return success;
} }
@ -311,13 +363,15 @@ bool ExcludedFiles::isExcluded(
return true; return true;
} }
//TODO this seems a waste, hidden files are ignored before hitting this function it seems
if (excludeHidden) { if (excludeHidden) {
QString path = filePath; QString path = filePath;
// Check all path subcomponents, but to *not* check the base path: // Check all path subcomponents, but to *not* check the base path:
// We do want to be able to sync with a hidden folder as the target. // We do want to be able to sync with a hidden folder as the target.
while (path.size() > basePath.size()) { while (path.size() > basePath.size()) {
QFileInfo fi(path); QFileInfo fi(path);
if (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.'))) { if (fi.fileName() != ".sync-exclude.lst"
&& (fi.isHidden() || fi.fileName().startsWith(QLatin1Char('.')))) {
return true; return true;
} }
@ -340,7 +394,7 @@ bool ExcludedFiles::isExcluded(
return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED; return fullPatternMatch(relativePath.toUtf8(), type) != CSYNC_NOT_EXCLUDED;
} }
CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype) const CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemType filetype)
{ {
auto match = _csync_excluded_common(path, _excludeConflictFiles); auto match = _csync_excluded_common(path, _excludeConflictFiles);
if (match != CSYNC_NOT_EXCLUDED) if (match != CSYNC_NOT_EXCLUDED)
@ -348,6 +402,15 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
if (_allExcludes.isEmpty()) if (_allExcludes.isEmpty())
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
// Directories are guaranteed to be visited before their files
if (filetype == ItemTypeDirectory) {
QFileInfo fi = QFileInfo(_localPath + path + "/.sync-exclude.lst");
if (fi.isReadable()) {
addInTreeExcludeFilePath(fi.absoluteFilePath());
loadExcludeFile(fi.absolutePath().toUtf8(), fi.absoluteFilePath());
}
}
// Check the bname part of the path to see whether the full // Check the bname part of the path to see whether the full
// regex should be run. // regex should be run.
@ -359,35 +422,53 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::traversalPatternMatch(const char *path, ItemTy
} }
QString bnameStr = QString::fromUtf8(bname); QString bnameStr = QString::fromUtf8(bname);
QRegularExpressionMatch m; QByteArray basePath(_localPath.toUtf8() + path);
if (filetype == ItemTypeDirectory) { while (basePath.size() > _localPath.size()) {
m = _bnameTraversalRegexDir.match(bnameStr); basePath = leftIncludeLast(basePath, '/');
} else { QRegularExpressionMatch m;
m = _bnameTraversalRegexFile.match(bnameStr); if (filetype == ItemTypeDirectory
} && _bnameTraversalRegexDir.contains(basePath)) {
if (!m.hasMatch()) m = _bnameTraversalRegexDir[basePath].match(bnameStr);
return CSYNC_NOT_EXCLUDED; } else if (filetype == ItemTypeFile
if (m.capturedStart(QStringLiteral("exclude")) != -1) { && _bnameTraversalRegexFile.contains(basePath)) {
return CSYNC_FILE_EXCLUDE_LIST; m = _bnameTraversalRegexFile[basePath].match(bnameStr);
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { } else {
return CSYNC_FILE_EXCLUDE_AND_REMOVE; continue;
} }
// third capture: full path matching is triggered if (!m.hasMatch())
QString pathStr = QString::fromUtf8(path); return CSYNC_NOT_EXCLUDED;
if (filetype == ItemTypeDirectory) {
m = _fullTraversalRegexDir.match(pathStr);
} else {
m = _fullTraversalRegexFile.match(pathStr);
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) { if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST; return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { } else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE; return CSYNC_FILE_EXCLUDE_AND_REMOVE;
} }
} }
// third capture: full path matching is triggered
QString pathStr = QString::fromUtf8(path);
basePath = _localPath.toUtf8() + path;
while (basePath.size() > _localPath.size()) {
basePath = leftIncludeLast(basePath, '/');
QRegularExpressionMatch m;
if (filetype == ItemTypeDirectory
&& _fullTraversalRegexDir.contains(basePath)) {
m = _fullTraversalRegexDir[basePath].match(pathStr);
} else if (filetype == ItemTypeFile
&& _fullTraversalRegexFile.contains(basePath)) {
m = _fullTraversalRegexFile[basePath].match(pathStr);
} else {
continue;
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
}
}
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
} }
@ -400,23 +481,38 @@ CSYNC_EXCLUDE_TYPE ExcludedFiles::fullPatternMatch(const char *path, ItemType fi
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
QString p = QString::fromUtf8(path); QString p = QString::fromUtf8(path);
QRegularExpressionMatch m; // `path` seems to always be relative to `_localPath`, the tests however have not been
if (filetype == ItemTypeDirectory) { // written that way... this makes the tests happy for now. TODO Fix the tests at some point
m = _fullRegexDir.match(p); if (path[0] == '/')
} else { ++path;
m = _fullRegexFile.match(p);
} QByteArray basePath(_localPath.toUtf8() + path);
if (m.hasMatch()) { while (basePath.size() > _localPath.size()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) { basePath = leftIncludeLast(basePath, '/');
return CSYNC_FILE_EXCLUDE_LIST; QRegularExpressionMatch m;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) { if (filetype == ItemTypeDirectory
return CSYNC_FILE_EXCLUDE_AND_REMOVE; && _fullRegexDir.contains(basePath)) {
m = _fullRegexDir[basePath].match(p);
} else if (filetype == ItemTypeFile
&& _fullRegexFile.contains(basePath)) {
m = _fullRegexFile[basePath].match(p);
} else {
continue;
}
if (m.hasMatch()) {
if (m.capturedStart(QStringLiteral("exclude")) != -1) {
return CSYNC_FILE_EXCLUDE_LIST;
} else if (m.capturedStart(QStringLiteral("excluderemove")) != -1) {
return CSYNC_FILE_EXCLUDE_AND_REMOVE;
}
} }
} }
return CSYNC_NOT_EXCLUDED; return CSYNC_NOT_EXCLUDED;
} }
auto ExcludedFiles::csyncTraversalMatchFun() const auto ExcludedFiles::csyncTraversalMatchFun()
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)> -> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>
{ {
return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); }; return [this](const char *path, ItemType filetype) { return this->traversalPatternMatch(path, filetype); };
@ -555,6 +651,22 @@ static QString extractBnameTrigger(const QString &exclude, bool wildcardsMatchSl
void ExcludedFiles::prepare() void ExcludedFiles::prepare()
{ {
// clear all regex
_bnameTraversalRegexFile.clear();
_bnameTraversalRegexDir.clear();
_fullTraversalRegexFile.clear();
_fullTraversalRegexDir.clear();
_fullRegexFile.clear();
_fullRegexDir.clear();
for (auto const & basePath : _allExcludes.keys())
prepare(basePath);
}
void ExcludedFiles::prepare(const BasePathByteArray & basePath)
{
Q_ASSERT(_allExcludes.contains(basePath));
// Build regular expressions for the different cases. // Build regular expressions for the different cases.
// //
// To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex // To compose the _bnameTraversalRegex, _fullTraversalRegex and _fullRegex
@ -596,7 +708,7 @@ void ExcludedFiles::prepare()
pattern.append(appendMe); pattern.append(appendMe);
}; };
for (auto exclude : _allExcludes) { for (auto exclude : _allExcludes.value(basePath)) {
if (exclude[0] == '\n') if (exclude[0] == '\n')
continue; // empty line continue; // empty line
if (exclude[0] == '\r') if (exclude[0] == '\r')
@ -618,6 +730,15 @@ void ExcludedFiles::prepare()
auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep; auto &fullFileDir = removeExcluded ? fullFileDirRemove : fullFileDirKeep;
auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep; auto &fullDir = removeExcluded ? fullDirRemove : fullDirKeep;
if (fullPath) {
// The full pattern is matched against a path relative to _localPath, however exclude is
// relative to basePath at this point.
// We know for sure that both _localPath and basePath are absolute and that basePath is
// contained in _localPath. So we can simply remove it from the begining.
auto relPath = basePath.mid(_localPath.size());
// Make exclude relative to _localPath
exclude.prepend(relPath);
}
auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude), _wildcardsMatchSlash); auto regexExclude = convertToRegexpSyntax(QString::fromUtf8(exclude), _wildcardsMatchSlash);
if (!fullPath) { if (!fullPath) {
regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly); regexAppend(bnameFileDir, bnameDir, regexExclude, matchDirOnly);
@ -654,11 +775,11 @@ void ExcludedFiles::prepare()
// (exclude)|(excluderemove)|(bname triggers). // (exclude)|(excluderemove)|(bname triggers).
// If the third group matches, the fullActivatedRegex needs to be applied // If the third group matches, the fullActivatedRegex needs to be applied
// to the full path. // to the full path.
_bnameTraversalRegexFile.setPattern( _bnameTraversalRegexFile[basePath].setPattern(
"^(?P<exclude>" + bnameFileDirKeep + ")$|" "^(?P<exclude>" + bnameFileDirKeep + ")$|"
+ "^(?P<excluderemove>" + bnameFileDirRemove + ")$|" + "^(?P<excluderemove>" + bnameFileDirRemove + ")$|"
+ "^(?P<trigger>" + bnameTriggerFileDir + ")$"); + "^(?P<trigger>" + bnameTriggerFileDir + ")$");
_bnameTraversalRegexDir.setPattern( _bnameTraversalRegexDir[basePath].setPattern(
"^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|" "^(?P<exclude>" + bnameFileDirKeep + "|" + bnameDirKeep + ")$|"
+ "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|" + "^(?P<excluderemove>" + bnameFileDirRemove + "|" + bnameDirRemove + ")$|"
+ "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$"); + "^(?P<trigger>" + bnameTriggerFileDir + "|" + bnameTriggerDir + ")$");
@ -667,13 +788,13 @@ void ExcludedFiles::prepare()
// the bname regex matches. Its basic form is (exclude)|(excluderemove)". // the bname regex matches. Its basic form is (exclude)|(excluderemove)".
// This pattern can be much simpler than fullRegex since we can assume a traversal // This pattern can be much simpler than fullRegex since we can assume a traversal
// situation and doesn't need to look for bname patterns in parent paths. // situation and doesn't need to look for bname patterns in parent paths.
_fullTraversalRegexFile.setPattern( _fullTraversalRegexFile[basePath].setPattern(
QLatin1String("") QLatin1String("")
// Full patterns are anchored to the beginning // Full patterns are anchored to the beginning
+ "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)" + "^(?P<exclude>" + fullFileDirKeep + ")(?:$|/)"
+ "|" + "|"
+ "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)"); + "^(?P<excluderemove>" + fullFileDirRemove + ")(?:$|/)");
_fullTraversalRegexDir.setPattern( _fullTraversalRegexDir[basePath].setPattern(
QLatin1String("") QLatin1String("")
+ "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "^(?P<exclude>" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)"
+ "|" + "|"
@ -681,7 +802,7 @@ void ExcludedFiles::prepare()
// The full regex is applied to the full path and incorporates both bname and // The full regex is applied to the full path and incorporates both bname and
// full-path patterns. It has the form "(exclude)|(excluderemove)". // full-path patterns. It has the form "(exclude)|(excluderemove)".
_fullRegexFile.setPattern( _fullRegexFile[basePath].setPattern(
QLatin1String("(?P<exclude>") QLatin1String("(?P<exclude>")
// Full patterns are anchored to the beginning // Full patterns are anchored to the beginning
+ "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|" + "^(?:" + fullFileDirKeep + ")(?:$|/)" + "|"
@ -697,7 +818,7 @@ void ExcludedFiles::prepare()
+ "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|" + "(?:^|/)(?:" + bnameFileDirRemove + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameDirRemove + ")/" + "(?:^|/)(?:" + bnameDirRemove + ")/"
+ ")"); + ")");
_fullRegexDir.setPattern( _fullRegexDir[basePath].setPattern(
QLatin1String("(?P<exclude>") QLatin1String("(?P<exclude>")
+ "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|" + "^(?:" + fullFileDirKeep + "|" + fullDirKeep + ")(?:$|/)" + "|"
+ "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)" + "(?:^|/)(?:" + bnameFileDirKeep + "|" + bnameDirKeep + ")(?:$|/)"
@ -711,16 +832,16 @@ void ExcludedFiles::prepare()
QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption; QRegularExpression::PatternOptions patternOptions = QRegularExpression::NoPatternOption;
if (OCC::Utility::fsCasePreserving()) if (OCC::Utility::fsCasePreserving())
patternOptions |= QRegularExpression::CaseInsensitiveOption; patternOptions |= QRegularExpression::CaseInsensitiveOption;
_bnameTraversalRegexFile.setPatternOptions(patternOptions); _bnameTraversalRegexFile[basePath].setPatternOptions(patternOptions);
_bnameTraversalRegexFile.optimize(); _bnameTraversalRegexFile[basePath].optimize();
_bnameTraversalRegexDir.setPatternOptions(patternOptions); _bnameTraversalRegexDir[basePath].setPatternOptions(patternOptions);
_bnameTraversalRegexDir.optimize(); _bnameTraversalRegexDir[basePath].optimize();
_fullTraversalRegexFile.setPatternOptions(patternOptions); _fullTraversalRegexFile[basePath].setPatternOptions(patternOptions);
_fullTraversalRegexFile.optimize(); _fullTraversalRegexFile[basePath].optimize();
_fullTraversalRegexDir.setPatternOptions(patternOptions); _fullTraversalRegexDir[basePath].setPatternOptions(patternOptions);
_fullTraversalRegexDir.optimize(); _fullTraversalRegexDir[basePath].optimize();
_fullRegexFile.setPatternOptions(patternOptions); _fullRegexFile[basePath].setPatternOptions(patternOptions);
_fullRegexFile.optimize(); _fullRegexFile[basePath].optimize();
_fullRegexDir.setPatternOptions(patternOptions); _fullRegexDir[basePath].setPatternOptions(patternOptions);
_fullRegexDir.optimize(); _fullRegexDir[basePath].optimize();
} }

View File

@ -66,7 +66,7 @@ class OCSYNC_EXPORT ExcludedFiles : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ExcludedFiles(); ExcludedFiles(QString localPath = "/");
~ExcludedFiles(); ~ExcludedFiles();
/** /**
@ -75,6 +75,7 @@ public:
* Does not load the file. Use reloadExcludeFiles() afterwards. * Does not load the file. Use reloadExcludeFiles() afterwards.
*/ */
void addExcludeFilePath(const QString &path); void addExcludeFilePath(const QString &path);
void addInTreeExcludeFilePath(const QString &path);
/** /**
* Whether conflict files shall be excluded. * Whether conflict files shall be excluded.
@ -95,12 +96,13 @@ public:
bool excludeHidden) const; bool excludeHidden) const;
/** /**
* Adds an exclude pattern. * Adds an exclude pattern anchored to base path
* *
* Primarily used in tests. Patterns added this way are preserved when * Primarily used in tests. Patterns added this way are preserved when
* reloadExcludeFiles() is called. * reloadExcludeFiles() is called.
*/ */
void addManualExclude(const QByteArray &expr); void addManualExclude(const QByteArray &expr);
void addManualExclude(const QByteArray &expr, const QByteArray &basePath);
/** /**
* Removes all manually added exclude patterns. * Removes all manually added exclude patterns.
@ -121,7 +123,7 @@ public:
* Careful: The function will only be valid for as long as this * Careful: The function will only be valid for as long as this
* ExcludedFiles instance stays alive. * ExcludedFiles instance stays alive.
*/ */
auto csyncTraversalMatchFun() const auto csyncTraversalMatchFun()
-> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>; -> std::function<CSYNC_EXCLUDE_TYPE(const char *path, ItemType filetype)>;
public slots: public slots:
@ -129,6 +131,10 @@ public slots:
* Reloads the exclude patterns from the registered paths. * Reloads the exclude patterns from the registered paths.
*/ */
bool reloadExcludeFiles(); bool reloadExcludeFiles();
/**
* Loads the exclude patterns from file the registered base paths.
*/
bool loadExcludeFile(const QByteArray & basePath, const QString & file);
private: private:
/** /**
@ -156,10 +162,32 @@ private:
* Note that this only matches patterns. It does not check whether the file * Note that this only matches patterns. It does not check whether the file
* or directory pointed to is hidden (or whether it even exists). * or directory pointed to is hidden (or whether it even exists).
*/ */
CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype) const; CSYNC_EXCLUDE_TYPE traversalPatternMatch(const char *path, ItemType filetype);
// Our BasePath need to end with '/'
class BasePathByteArray : public QByteArray
{
public:
BasePathByteArray(QByteArray && other)
: QByteArray(std::move(other))
{
Q_ASSERT(this->endsWith('/'));
}
BasePathByteArray(const QByteArray & other)
: QByteArray(other)
{
Q_ASSERT(this->endsWith('/'));
}
BasePathByteArray(const char * data, int size = -1)
: BasePathByteArray(QByteArray(data, size))
{
}
};
/** /**
* Generate optimized regular expressions for the exclude patterns. * Generate optimized regular expressions for the exclude patterns anchored to basePath.
* *
* The optimization works in two steps: First, all supported patterns are put * The optimization works in two steps: First, all supported patterns are put
* into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full * into _fullRegexFile/_fullRegexDir. These regexes can be applied to the full
@ -187,24 +215,28 @@ private:
* full matcher would exclude. Example: "b" is excluded. traversal("b/c") * full matcher would exclude. Example: "b" is excluded. traversal("b/c")
* returns not-excluded because "c" isn't a bname activation pattern. * returns not-excluded because "c" isn't a bname activation pattern.
*/ */
void prepare(const BasePathByteArray & basePath);
void prepare(); void prepare();
QString _localPath;
/// Files to load excludes from /// Files to load excludes from
QSet<QString> _excludeFiles; QMap<BasePathByteArray, QList<QString>> _excludeFiles;
/// Exclude patterns added with addManualExclude() /// Exclude patterns added with addManualExclude()
QList<QByteArray> _manualExcludes; QMap<BasePathByteArray, QList<QByteArray>> _manualExcludes;
/// List of all active exclude patterns /// List of all active exclude patterns
QList<QByteArray> _allExcludes; QMap<BasePathByteArray, QList<QByteArray>> _allExcludes;
/// see prepare() /// see prepare()
QRegularExpression _bnameTraversalRegexFile; QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexFile;
QRegularExpression _bnameTraversalRegexDir; QMap<BasePathByteArray, QRegularExpression> _bnameTraversalRegexDir;
QRegularExpression _fullTraversalRegexFile; QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexFile;
QRegularExpression _fullTraversalRegexDir; QMap<BasePathByteArray, QRegularExpression> _fullTraversalRegexDir;
QRegularExpression _fullRegexFile; QMap<BasePathByteArray, QRegularExpression> _fullRegexFile;
QRegularExpression _fullRegexDir; QMap<BasePathByteArray, QRegularExpression> _fullRegexDir;
bool _excludeConflictFiles = true; bool _excludeConflictFiles = true;

View File

@ -124,7 +124,9 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
* because it's a hidden file that should not be synced. * because it's a hidden file that should not be synced.
* This code should probably be in csync_exclude, but it does not have the fs parameter. * This code should probably be in csync_exclude, but it does not have the fs parameter.
* Keep it here for now */ * Keep it here for now */
if (ctx->ignore_hidden_files && (fs->is_hidden)) { if (ctx->ignore_hidden_files
&& fs->is_hidden
&& !fs->path.endsWith(".sync-exclude.lst")) {
qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData()); qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
excluded = CSYNC_FILE_EXCLUDE_HIDDEN; excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
} }

View File

@ -22,6 +22,7 @@ set(client_UI_SRCS
generalsettings.ui generalsettings.ui
legalnotice.ui legalnotice.ui
ignorelisteditor.ui ignorelisteditor.ui
ignorelisttablewidget.ui
networksettings.ui networksettings.ui
activitywidget.ui activitywidget.ui
synclogdialog.ui synclogdialog.ui
@ -38,6 +39,8 @@ set(client_UI_SRCS
wizard/owncloudconnectionmethoddialog.ui wizard/owncloudconnectionmethoddialog.ui
wizard/owncloudhttpcredspage.ui wizard/owncloudhttpcredspage.ui
wizard/owncloudoauthcredspage.ui wizard/owncloudoauthcredspage.ui
wizard/flow2authcredspage.ui
wizard/flow2authwidget.ui
wizard/owncloudsetupnocredspage.ui wizard/owncloudsetupnocredspage.ui
wizard/owncloudwizardresultpage.ui wizard/owncloudwizardresultpage.ui
wizard/webview.ui wizard/webview.ui
@ -59,6 +62,7 @@ set(client_SRCS
generalsettings.cpp generalsettings.cpp
legalnotice.cpp legalnotice.cpp
ignorelisteditor.cpp ignorelisteditor.cpp
ignorelisttablewidget.cpp
lockwatcher.cpp lockwatcher.cpp
logbrowser.cpp logbrowser.cpp
navigationpanehelper.cpp navigationpanehelper.cpp
@ -103,6 +107,7 @@ set(client_SRCS
creds/credentialsfactory.cpp creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp creds/httpcredentialsgui.cpp
creds/oauth.cpp creds/oauth.cpp
creds/flow2auth.cpp
creds/webflowcredentials.cpp creds/webflowcredentials.cpp
creds/webflowcredentialsdialog.cpp creds/webflowcredentialsdialog.cpp
wizard/postfixlineedit.cpp wizard/postfixlineedit.cpp
@ -111,6 +116,8 @@ set(client_SRCS
wizard/owncloudconnectionmethoddialog.cpp wizard/owncloudconnectionmethoddialog.cpp
wizard/owncloudhttpcredspage.cpp wizard/owncloudhttpcredspage.cpp
wizard/owncloudoauthcredspage.cpp wizard/owncloudoauthcredspage.cpp
wizard/flow2authcredspage.cpp
wizard/flow2authwidget.cpp
wizard/owncloudsetuppage.cpp wizard/owncloudsetuppage.cpp
wizard/owncloudwizardcommon.cpp wizard/owncloudwizardcommon.cpp
wizard/owncloudwizard.cpp wizard/owncloudwizard.cpp
@ -165,6 +172,7 @@ set(3rdparty_SRC
../3rdparty/qtsingleapplication/qtlocalpeer.cpp ../3rdparty/qtsingleapplication/qtlocalpeer.cpp
../3rdparty/qtsingleapplication/qtsingleapplication.cpp ../3rdparty/qtsingleapplication/qtsingleapplication.cpp
../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp ../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp
../3rdparty/kmessagewidget/kmessagewidget.cpp
) )
if (APPLE) if (APPLE)
@ -243,7 +251,7 @@ if(APPLE)
file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*") file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*")
MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}") MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}")
endif() endif()
ecm_add_app_icon(final_src ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASE "${APPLICATION_ICON_NAME}") ecm_add_app_icon(final_src ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}")
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
@ -265,6 +273,9 @@ if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
endforeach(_file) endforeach(_file)
install(FILES ${client_I18N} DESTINATION ${SHAREDIR}/${APPLICATION_EXECUTABLE}/i18n) install(FILES ${client_I18N} DESTINATION ${SHAREDIR}/${APPLICATION_EXECUTABLE}/i18n)
else() else()
file(GLOB_RECURSE VISUAL_ELEMENTS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-w10startmenu*")
install(FILES ${VISUAL_ELEMENTS} DESTINATION bin/visualelements)
install(FILES "${theme_dir}/${APPLICATION_EXECUTABLE}.VisualElementsManifest.xml" DESTINATION bin)
install(FILES ${client_I18N} DESTINATION i18n) install(FILES ${client_I18N} DESTINATION i18n)
endif() endif()
@ -329,6 +340,7 @@ target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile ${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src ${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication ${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
) )

View File

@ -342,9 +342,6 @@ AccountPtr AccountManager::createAccount()
connect(acc.data(), &Account::proxyAuthenticationRequired, connect(acc.data(), &Account::proxyAuthenticationRequired,
ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired); ProxyAuthHandler::instance(), &ProxyAuthHandler::handleProxyAuthenticationRequired);
connect(acc.data()->e2e(), &ClientSideEncryption::mnemonicGenerated,
&AccountManager::displayMnemonic);
return acc; return acc;
} }
@ -364,6 +361,7 @@ void AccountManager::displayMnemonic(const QString& mnemonic)
widget->exec(); widget->exec();
widget->resize(widget->sizeHint()); widget->resize(widget->sizeHint());
} }
void AccountManager::shutdown() void AccountManager::shutdown()
{ {
auto accountsCopy = _accounts; auto accountsCopy = _accounts;

View File

@ -35,10 +35,12 @@
#include "filesystem.h" #include "filesystem.h"
#include "clientsideencryptionjobs.h" #include "clientsideencryptionjobs.h"
#include "syncresult.h" #include "syncresult.h"
#include "ignorelisttablewidget.h"
#include <math.h> #include <math.h>
#include <QDesktopServices> #include <QDesktopServices>
#include <QDialogButtonBox>
#include <QDir> #include <QDir>
#include <QListWidgetItem> #include <QListWidgetItem>
#include <QMessageBox> #include <QMessageBox>
@ -194,6 +196,14 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
// Connect E2E stuff // Connect E2E stuff
connect(this, &AccountSettings::requesetMnemonic, _accountState->account()->e2e(), &ClientSideEncryption::slotRequestMnemonic); connect(this, &AccountSettings::requesetMnemonic, _accountState->account()->e2e(), &ClientSideEncryption::slotRequestMnemonic);
connect(_accountState->account()->e2e(), &ClientSideEncryption::showMnemonic, this, &AccountSettings::slotShowMnemonic); connect(_accountState->account()->e2e(), &ClientSideEncryption::showMnemonic, this, &AccountSettings::slotShowMnemonic);
connect(_accountState->account()->e2e(), &ClientSideEncryption::mnemonicGenerated, this, &AccountSettings::slotNewMnemonicGenerated);
if (_accountState->account()->e2e()->newMnemonicGenerated())
{
slotNewMnemonicGenerated();
} else {
ui->encryptionMessage->hide();
}
} }
@ -222,6 +232,19 @@ void AccountSettings::createAccountToolbox()
slotAccountAdded(_accountState); slotAccountAdded(_accountState);
} }
void AccountSettings::slotNewMnemonicGenerated()
{
ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
QAction *mnemonic = new QAction(tr("Enable encryption"), this);
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
connect(mnemonic, &QAction::triggered, ui->encryptionMessage, &KMessageWidget::hide);
ui->encryptionMessage->addAction(mnemonic);
ui->encryptionMessage->show();
}
void AccountSettings::slotMenuBeforeShow() { void AccountSettings::slotMenuBeforeShow() {
if (_menuShown) { if (_menuShown) {
return; return;
@ -401,7 +424,7 @@ bool AccountSettings::canEncryptOrDecrypt (const FolderStatusModel::SubFolderInf
return true; return true;
} }
void AccountSettings::slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo) void AccountSettings::slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo)
{ {
if (!canEncryptOrDecrypt(folderInfo)) { if (!canEncryptOrDecrypt(folderInfo)) {
return; return;
@ -518,6 +541,51 @@ void AccountSettings::slotLockForDecryptionError(const QByteArray& fileId, int h
qDebug() << "Error Locking for decryption"; qDebug() << "Error Locking for decryption";
} }
void AccountSettings::slotEditCurrentIgnoredFiles()
{
Folder *f = FolderMan::instance()->folder(selectedFolderAlias());
if (f == nullptr)
return;
openIgnoredFilesDialog(f->path());
}
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
return;
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
openIgnoredFilesDialog(fileName);
}
void AccountSettings::openIgnoredFilesDialog(const QString & absFolderPath)
{
Q_ASSERT(absFolderPath.startsWith('/'));
Q_ASSERT(absFolderPath.endsWith('/'));
const QString ignoreFile = absFolderPath + ".sync-exclude.lst";
auto layout = new QVBoxLayout();
auto ignoreListWidget = new IgnoreListTableWidget(this);
ignoreListWidget->readIgnoreFile(ignoreFile);
layout->addWidget(ignoreListWidget);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttonBox);
auto dialog = new QDialog();
dialog->setLayout(layout);
connect(buttonBox, &QDialogButtonBox::clicked, [=](QAbstractButton * button) {
if (buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
ignoreListWidget->slotWriteIgnoreFile(ignoreFile);
dialog->close();
});
connect(buttonBox, &QDialogButtonBox::rejected,
dialog, &QDialog::close);
dialog->open();
}
void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos) void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index, const QPoint& pos)
{ {
Q_UNUSED(pos); Q_UNUSED(pos);
@ -540,12 +608,16 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
if (!isEncrypted) { if (!isEncrypted) {
ac = menu.addAction(tr("Encrypt")); ac = menu.addAction(tr("Encrypt"));
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info); }); connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrypted(info); });
} else { } else {
// Ingore decrypting for now since it only works with an empty folder // Ingore decrypting for now since it only works with an empty folder
// connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); }); // connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); });
} }
} }
ac = menu.addAction(tr("Edit Ignored Files"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles);
menu.exec(QCursor::pos()); menu.exec(QCursor::pos());
} }
@ -579,6 +651,9 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
QAction *ac = menu->addAction(tr("Open folder")); QAction *ac = menu->addAction(tr("Open folder"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder); connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenCurrentFolder);
ac = menu->addAction(tr("Edit Ignored Files"));
connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
if (!ui->_folderList->isExpanded(index)) { if (!ui->_folderList->isExpanded(index)) {
ac = menu->addAction(tr("Choose what to sync")); ac = menu->addAction(tr("Choose what to sync"));
ac->setEnabled(folderConnected); ac->setEnabled(folderConnected);

View File

@ -80,6 +80,8 @@ protected slots:
void slotRemoveCurrentFolder(); void slotRemoveCurrentFolder();
void slotOpenCurrentFolder(); // sync folder void slotOpenCurrentFolder(); // sync folder
void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder
void slotEditCurrentIgnoredFiles();
void slotEditCurrentLocalIgnoredFiles();
void slotFolderWizardAccepted(); void slotFolderWizardAccepted();
void slotFolderWizardRejected(); void slotFolderWizardRejected();
void slotDeleteAccount(); void slotDeleteAccount();
@ -87,7 +89,7 @@ protected slots:
void slotOpenAccountWizard(); void slotOpenAccountWizard();
void slotAccountAdded(AccountState *); void slotAccountAdded(AccountState *);
void refreshSelectiveSyncStatus(); void refreshSelectiveSyncStatus();
void slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo); void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo); void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point); void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point);
void slotCustomContextMenuRequested(const QPoint &); void slotCustomContextMenuRequested(const QPoint &);
@ -99,6 +101,7 @@ protected slots:
// Encryption Related Stuff. // Encryption Related Stuff.
void slotShowMnemonic(const QString &mnemonic); void slotShowMnemonic(const QString &mnemonic);
void slotNewMnemonicGenerated();
void slotEncryptionFlagSuccess(const QByteArray &folderId); void slotEncryptionFlagSuccess(const QByteArray &folderId);
void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode); void slotEncryptionFlagError(const QByteArray &folderId, int httpReturnCode);
@ -109,7 +112,7 @@ protected slots:
void slotUploadMetadataSuccess(const QByteArray& folderId); void slotUploadMetadataSuccess(const QByteArray& folderId);
void slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode); void slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode);
// Remove Encryotion Bit. // Remove Encryption Bit.
void slotLockForDecryptionSuccess(const QByteArray& folderId, const QByteArray& token); void slotLockForDecryptionSuccess(const QByteArray& folderId, const QByteArray& token);
void slotLockForDecryptionError(const QByteArray& folderId, int httpReturnCode); void slotLockForDecryptionError(const QByteArray& folderId, int httpReturnCode);
void slotDeleteMetadataSuccess(const QByteArray& folderId); void slotDeleteMetadataSuccess(const QByteArray& folderId);
@ -124,6 +127,7 @@ private:
QStringList errors = QStringList()); QStringList errors = QStringList());
bool event(QEvent *) override; bool event(QEvent *) override;
void createAccountToolbox(); void createAccountToolbox();
void openIgnoredFilesDialog(const QString & absFolderPath);
/// Returns the alias of the selected folder, empty string if none /// Returns the alias of the selected folder, empty string if none
QString selectedFolderAlias() const; QString selectedFolderAlias() const;

View File

@ -6,136 +6,15 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>575</width> <width>582</width>
<height>557</height> <height>557</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="4" column="0">
<widget class="QWidget" name="accountStatus" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="SslButton" name="sslButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="connectLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connected with &lt;server&gt; as &lt;user&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="_accountToolbox">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="storageGroupBox">
<item>
<widget class="QLabel" name="quotaInfoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Storage space: ...</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="quotaProgressBar">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>7</height>
</size>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>-1</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="OCC::FolderStatusView" name="_folderList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="animated">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QWidget" name="selectiveSyncStatus" native="true"> <widget class="QWidget" name="selectiveSyncStatus" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
@ -267,6 +146,130 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QWidget" name="accountStatus" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="SslButton" name="sslButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="connectLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Connected with &lt;server&gt; as &lt;user&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="_accountToolbox">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="storageGroupBox">
<item>
<widget class="QLabel" name="quotaInfoLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Storage space: ...</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="quotaProgressBar">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>7</height>
</size>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>-1</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="OCC::FolderStatusView" name="_folderList">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="animated">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="KMessageWidget" name="encryptionMessage" native="true"/>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
@ -280,6 +283,12 @@
<extends>QTreeView</extends> <extends>QTreeView</extends>
<header>folderstatusview.h</header> <header>folderstatusview.h</header>
</customwidget> </customwidget>
<customwidget>
<class>KMessageWidget</class>
<extends>QWidget</extends>
<header>kmessagewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -103,10 +103,12 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|| a._status == SyncFileItem::BlacklistedError) { || a._status == SyncFileItem::BlacklistedError) {
return QIcon(QLatin1String(":/client/resources/state-error.svg")); return QIcon(QLatin1String(":/client/resources/state-error.svg"));
} else if(a._status == SyncFileItem::SoftError } else if(a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::FileIgnored
|| a._status == SyncFileItem::Conflict || a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration){ || a._status == SyncFileItem::Restoration
|| a._status == SyncFileItem::FileLocked){
return QIcon(QLatin1String(":/client/resources/state-warning.svg")); return QIcon(QLatin1String(":/client/resources/state-warning.svg"));
} else if(a._status == SyncFileItem::FileIgnored){
return QIcon(QLatin1String(":/client/resources/state-info.svg"));
} }
return QIcon(QLatin1String(":/client/resources/state-sync.svg")); return QIcon(QLatin1String(":/client/resources/state-sync.svg"));
} }

View File

@ -121,6 +121,11 @@ void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo
continue; continue;
} }
if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity);
continue;
}
if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()){ if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity); _model->removeActivityFromActivityList(activity);

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -81,7 +81,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>

176
src/gui/creds/flow2auth.cpp Normal file
View File

@ -0,0 +1,176 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
* Copyright (C) by Michael Schuster <michael@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.
*/
#include <QDesktopServices>
#include <QTimer>
#include <QBuffer>
#include "account.h"
#include "creds/flow2auth.h"
#include <QJsonObject>
#include <QJsonDocument>
#include "theme.h"
#include "networkjobs.h"
#include "configfile.h"
namespace OCC {
Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
Flow2Auth::~Flow2Auth()
{
}
void Flow2Auth::start()
{
// Note: All startup code is in openBrowser() to allow reinitiate a new request with
// fresh tokens. Opening the same pollEndpoint link twice triggers an expiration
// message by the server (security, intended design).
openBrowser();
}
QUrl Flow2Auth::authorisationLink() const
{
return _loginUrl;
}
void Flow2Auth::openBrowser()
{
_pollTimer.stop();
// Step 1: Initiate a login, do an anonymous POST request
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
auto job = _account->sendRequest("POST", url);
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
auto jsonData = reply->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
QString pollToken = json.value("poll").toObject().value("token").toString();
QString pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
QUrl loginUrl = json["login"].toString();
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|| json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
QString errorReason;
QString errorFromJson = json["error"].toString();
if (!errorFromJson.isEmpty()) {
errorReason = tr("Error returned from the server: <em>%1</em>")
.arg(errorFromJson.toHtmlEscaped());
} else if (reply->error() != QNetworkReply::NoError) {
errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
.arg(reply->errorString().toHtmlEscaped());
} else if (jsonParseError.error != QJsonParseError::NoError) {
errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
.arg(jsonParseError.errorString());
} else {
errorReason = tr("The reply from the server did not contain all expected fields");
}
qCWarning(lcFlow2auth) << "Error when getting the loginUrl" << json << errorReason;
emit result(Error);
return;
}
_loginUrl = loginUrl;
_pollToken = pollToken;
_pollEndpoint = pollEndpoint;
// Start polling
ConfigFile cfg;
std::chrono::milliseconds polltime = cfg.remotePollInterval();
qCInfo(lcFlow2auth) << "setting remote poll timer interval to" << polltime.count() << "msec";
_pollTimer.setInterval(polltime.count());
QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
_pollTimer.start();
// Try to open Browser
if (!QDesktopServices::openUrl(authorisationLink())) {
// We cannot open the browser, then we claim we don't support Flow2Auth.
// Our UI callee should ask the user to copy and open the link.
emit result(NotSupported, QString());
}
});
}
void Flow2Auth::slotPollTimerTimeout()
{
_pollTimer.stop();
// Step 2: Poll
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
auto requestBody = new QBuffer;
QUrlQuery arguments(QString("token=%1").arg(_pollToken));
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
auto job = _account->sendRequest("POST", _pollEndpoint, req, requestBody);
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
auto jsonData = reply->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
QUrl serverUrl = json["server"].toString();
QString loginName = json["loginName"].toString();
QString appPassword = json["appPassword"].toString();
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|| json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
QString errorReason;
QString errorFromJson = json["error"].toString();
if (!errorFromJson.isEmpty()) {
errorReason = tr("Error returned from the server: <em>%1</em>")
.arg(errorFromJson.toHtmlEscaped());
} else if (reply->error() != QNetworkReply::NoError) {
errorReason = tr("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
.arg(reply->errorString().toHtmlEscaped());
} else if (jsonParseError.error != QJsonParseError::NoError) {
errorReason = tr("Could not parse the JSON returned from the server: <br><em>%1</em>")
.arg(jsonParseError.errorString());
} else {
errorReason = tr("The reply from the server did not contain all expected fields");
}
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
// Forget sensitive data
appPassword.clear();
loginName.clear();
// Failed: poll again
_pollTimer.start();
return;
}
// Success
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
_account->setUrl(serverUrl);
emit result(LoggedIn, loginName, appPassword);
// Forget sensitive data
appPassword.clear();
loginName.clear();
});
}
} // namespace OCC

68
src/gui/creds/flow2auth.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
* Copyright (C) by Michael Schuster <michael@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.
*/
#pragma once
#include <QPointer>
#include <QUrl>
#include <QTimer>
#include "accountfwd.h"
namespace OCC {
/**
* Job that does the authorization, grants and fetches the access token via Login Flow v2
*
* See: https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
*
*/
class Flow2Auth : public QObject
{
Q_OBJECT
public:
Flow2Auth(Account *account, QObject *parent)
: QObject(parent)
, _account(account)
{
}
~Flow2Auth();
enum Result { NotSupported,
LoggedIn,
Error };
Q_ENUM(Result);
void start();
void openBrowser();
QUrl authorisationLink() const;
signals:
/**
* The state has changed.
* when logged in, appPassword has the value of the app password.
*/
void result(Flow2Auth::Result result, const QString &user = QString(), const QString &appPassword = QString());
private slots:
void slotPollTimerTimeout();
private:
Account *_account;
QUrl _loginUrl;
QString _pollToken;
QString _pollEndpoint;
QTimer _pollTimer;
};
} // namespace OCC

View File

@ -90,7 +90,7 @@ ShibbolethWebView::ShibbolethWebView(AccountPtr account, QWidget *parent)
QWebView *debugView = new QWebView(this); QWebView *debugView = new QWebView(this);
debugView->setPage(debugPage); debugView->setPage(debugPage);
QMainWindow *window = new QMainWindow(this); QMainWindow *window = new QMainWindow(this);
window->setWindowTitle(tr("SSL Chipher Debug View")); window->setWindowTitle(tr("SSL Cipher Debug View"));
window->setCentralWidget(debugView); window->setCentralWidget(debugView);
window->show(); window->show();
} }

View File

@ -14,6 +14,7 @@
#include "accessmanager.h" #include "accessmanager.h"
#include "account.h" #include "account.h"
#include "configfile.h"
#include "theme.h" #include "theme.h"
#include "wizard/webview.h" #include "wizard/webview.h"
#include "webflowcredentialsdialog.h" #include "webflowcredentialsdialog.h"
@ -24,6 +25,13 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcWebFlowCredentials, "sync.credentials.webflow", QtInfoMsg) Q_LOGGING_CATEGORY(lcWebFlowCredentials, "sync.credentials.webflow", QtInfoMsg)
namespace {
const char userC[] = "user";
const char clientCertificatePEMC[] = "_clientCertificatePEM";
const char clientKeyPEMC[] = "_clientKeyPEM";
const char clientCaCertificatePEMC[] = "_clientCaCertificatePEM";
} // ns
class WebFlowCredentialsAccessManager : public AccessManager class WebFlowCredentialsAccessManager : public AccessManager
{ {
public: public:
@ -37,13 +45,27 @@ protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override
{ {
QNetworkRequest req(request); QNetworkRequest req(request);
if (!req.attribute(HttpCredentials::DontAddCredentialsAttribute).toBool()) { if (!req.attribute(WebFlowCredentials::DontAddCredentialsAttribute).toBool()) {
if (_cred && !_cred->password().isEmpty()) { if (_cred && !_cred->password().isEmpty()) {
QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64(); QByteArray credHash = QByteArray(_cred->user().toUtf8() + ":" + _cred->password().toUtf8()).toBase64();
req.setRawHeader("Authorization", "Basic " + credHash); req.setRawHeader("Authorization", "Basic " + credHash);
} }
} }
if (_cred && !_cred->_clientSslKey.isNull() && !_cred->_clientSslCertificate.isNull()) {
// SSL configuration
QSslConfiguration sslConfiguration = req.sslConfiguration();
sslConfiguration.setLocalCertificate(_cred->_clientSslCertificate);
sslConfiguration.setPrivateKey(_cred->_clientSslKey);
// Merge client side CA with system CA
auto ca = sslConfiguration.systemCaCertificates();
ca.append(_cred->_clientSslCaCertificates);
sslConfiguration.setCaCertificates(ca);
req.setSslConfiguration(sslConfiguration);
}
return AccessManager::createRequest(op, req, outgoingData); return AccessManager::createRequest(op, req, outgoingData);
} }
@ -53,22 +75,33 @@ private:
QPointer<const WebFlowCredentials> _cred; QPointer<const WebFlowCredentials> _cred;
}; };
static void addSettingsToJob(Account *account, QKeychain::Job *job)
{
Q_UNUSED(account)
auto settings = ConfigFile::settingsWithGroup(Theme::instance()->appName());
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
}
WebFlowCredentials::WebFlowCredentials() WebFlowCredentials::WebFlowCredentials()
: _ready(false) : _ready(false)
, _credentialsValid(false) , _credentialsValid(false)
, _keychainMigration(false) , _keychainMigration(false)
, _retryOnKeyChainError(false)
{ {
} }
WebFlowCredentials::WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate, const QSslKey &key) WebFlowCredentials::WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate, const QSslKey &key, const QList<QSslCertificate> &caCertificates)
: _user(user) : _user(user)
, _password(password) , _password(password)
, _clientSslKey(key) , _clientSslKey(key)
, _clientSslCertificate(certificate) , _clientSslCertificate(certificate)
, _clientSslCaCertificates(caCertificates)
, _ready(true) , _ready(true)
, _credentialsValid(true) , _credentialsValid(true)
, _keychainMigration(false) , _keychainMigration(false)
, _retryOnKeyChainError(false)
{ {
} }
@ -102,24 +135,29 @@ bool WebFlowCredentials::ready() const {
void WebFlowCredentials::fetchFromKeychain() { void WebFlowCredentials::fetchFromKeychain() {
_wasFetched = true; _wasFetched = true;
// Make sure we get the user fromt he config file // Make sure we get the user from the config file
fetchUser(); fetchUser();
if (ready()) { if (ready()) {
emit fetched(); emit fetched();
} else { } else {
qCInfo(lcWebFlowCredentials()) << "Fetch from keyhchain!"; qCInfo(lcWebFlowCredentials()) << "Fetch from keychain!";
fetchFromKeychainHelper(); fetchFromKeychainHelper();
} }
} }
void WebFlowCredentials::askFromUser() { void WebFlowCredentials::askFromUser() {
_askDialog = new WebFlowCredentialsDialog(); // LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0));
QUrl url = _account->url(); _askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
QString path = url.path() + "/index.php/login/flow";
url.setPath(path); if (!useFlow2) {
_askDialog->setUrl(url); QUrl url = _account->url();
QString path = url.path() + "/index.php/login/flow";
url.setPath(path);
_askDialog->setUrl(url);
}
QString msg = tr("You have been logged out of %1 as user %2. Please login again") QString msg = tr("You have been logged out of %1 as user %2. Please login again")
.arg(_account->displayName(), _user); .arg(_account->displayName(), _user);
@ -133,7 +171,7 @@ void WebFlowCredentials::askFromUser() {
} }
void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host) { void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host) {
Q_UNUSED(host); Q_UNUSED(host)
if (_user != user) { if (_user != user) {
qCInfo(lcWebFlowCredentials()) << "Authed with the wrong user!"; qCInfo(lcWebFlowCredentials()) << "Authed with the wrong user!";
@ -142,10 +180,12 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user,
.arg(_user); .arg(_user);
_askDialog->setError(msg); _askDialog->setError(msg);
QUrl url = _account->url(); if (!_askDialog->isUsingFlow2()) {
QString path = url.path() + "/index.php/login/flow"; QUrl url = _account->url();
url.setPath(path); QString path = url.path() + "/index.php/login/flow";
_askDialog->setUrl(url); url.setPath(path);
_askDialog->setUrl(url);
}
return; return;
} }
@ -178,17 +218,128 @@ void WebFlowCredentials::persist() {
return; return;
} }
_account->setCredentialSetting("user", _user); _account->setCredentialSetting(userC, _user);
_account->wantsAccountSaved(_account); _account->wantsAccountSaved(_account);
//TODO: Add ssl cert and key storing // write cert if there is one
if (!_clientSslCertificate.isNull()) {
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
job->setBinaryData(_clientSslCertificate.toPem());
job->start();
} else {
// no cert, just write credentials
slotWriteClientCertPEMJobDone();
}
}
void WebFlowCredentials::slotWriteClientCertPEMJobDone()
{
// write ssl key if there is one
if (!_clientSslKey.isNull()) {
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
job->setBinaryData(_clientSslKey.toPem());
job->start();
} else {
// no key, just write credentials
slotWriteClientKeyPEMJobDone();
}
}
void WebFlowCredentials::writeSingleClientCaCertPEM()
{
// write a ca cert if there is any in the queue
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
// grab and remove the first cert from the queue
auto cert = _clientSslCaCertificatesWriteQueue.dequeue();
auto index = (_clientSslCaCertificates.count() - _clientSslCaCertificatesWriteQueue.count()) - 1;
// keep the limit
if (index > (_clientSslCaCertificatesMaxCount - 1)) {
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while writing slot" << QString::number(index) << "), cutting off after " << QString::number(_clientSslCaCertificatesMaxCount) << "certs";
_clientSslCaCertificatesWriteQueue.clear();
slotWriteClientCaCertsPEMJobDone(nullptr);
return;
}
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
job->setKey(keychainKey(_account->url().toString(), _user + clientCaCertificatePEMC + QString::number(index), _account->id()));
job->setBinaryData(cert.toPem());
job->start();
} else {
slotWriteClientCaCertsPEMJobDone(nullptr);
}
}
void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
{
_clientSslCaCertificatesWriteQueue.clear();
// write ca certs if there are any
if (!_clientSslCaCertificates.isEmpty()) {
// queue the certs to avoid trouble on Windows (Workaround for CredWriteW used by QtKeychain)
_clientSslCaCertificatesWriteQueue.append(_clientSslCaCertificates);
// first ca cert
writeSingleClientCaCertPEM();
} else {
slotWriteClientCaCertsPEMJobDone(nullptr);
}
}
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob)
{
// errors / next ca cert?
if (incomingJob && !_clientSslCaCertificates.isEmpty()) {
WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
if (writeJob->error() != NoError) {
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
}
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
// next ca cert
writeSingleClientCaCertPEM();
return;
}
}
// done storing ca certs, time for the password
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName()); WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
job->setKey(keychainKey(_account->url().toString(), _user, _account->id())); job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
job->setTextData(_password); job->setTextData(_password);
job->start(); job->start();
} }
void WebFlowCredentials::slotWriteJobDone(QKeychain::Job *job)
{
delete job->settings();
switch (job->error()) {
case NoError:
break;
default:
qCWarning(lcWebFlowCredentials) << "Error while writing password" << job->errorString();
}
WritePasswordJob *wjob = qobject_cast<WritePasswordJob *>(job);
wjob->deleteLater();
}
void WebFlowCredentials::invalidateToken() { void WebFlowCredentials::invalidateToken() {
// clear the session cookie. // clear the session cookie.
_account->clearCookieJar(); _account->clearCookieJar();
@ -201,7 +352,7 @@ void WebFlowCredentials::invalidateToken() {
QTimer::singleShot(0, _account, &Account::clearQNAMCache); QTimer::singleShot(0, _account, &Account::clearQNAMCache);
} }
void WebFlowCredentials::forgetSensitiveData(){ void WebFlowCredentials::forgetSensitiveData() {
_password = QString(); _password = QString();
_ready = false; _ready = false;
@ -219,6 +370,15 @@ void WebFlowCredentials::forgetSensitiveData(){
job->start(); job->start();
invalidateToken(); invalidateToken();
/* IMPORTANT
/* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
*
* Disabled as long as selecting another cert is not supported by the UI.
*
* Being able to specify a new certificate is important anyway: expiry etc.
*/
//deleteKeychainEntries();
} }
void WebFlowCredentials::setAccount(Account *account) { void WebFlowCredentials::setAccount(Account *account) {
@ -229,12 +389,12 @@ void WebFlowCredentials::setAccount(Account *account) {
} }
QString WebFlowCredentials::fetchUser() { QString WebFlowCredentials::fetchUser() {
_user = _account->credentialSetting("user").toString(); _user = _account->credentialSetting(userC).toString();
return _user; return _user;
} }
void WebFlowCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) { void WebFlowCredentials::slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) {
Q_UNUSED(reply); Q_UNUSED(reply)
if (!_ready) { if (!_ready) {
return; return;
@ -260,12 +420,139 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
} }
void WebFlowCredentials::fetchFromKeychainHelper() { void WebFlowCredentials::fetchFromKeychainHelper() {
// Read client cert from keychain
const QString kck = keychainKey(
_account->url().toString(),
_user + clientCertificatePEMC,
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
job->start();
}
void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
Q_ASSERT(!incomingJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
if (_retryOnKeyChainError && (incomingJob->error() == QKeychain::NoBackendAvailable
|| incomingJob->error() == QKeychain::OtherError)) {
// Could be that the backend was not yet available. Wait some extra seconds.
// (Issues #4274 and #6522)
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incomingJob->errorString();
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
_retryOnKeyChainError = false;
return;
}
_retryOnKeyChainError = false;
#endif
// Store PEM in memory
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
if (sslCertificateList.length() >= 1) {
_clientSslCertificate = sslCertificateList.at(0);
}
}
// Load key too
const QString kck = keychainKey(
_account->url().toString(),
_user + clientKeyPEMC,
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
job->start();
}
void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob)
{
// Store key in memory
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QByteArray clientKeyPEM = readJob->binaryData();
// FIXME Unfortunately Qt has a bug and we can't just use QSsl::Opaque to let it
// load whatever we have. So we try until it works.
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Rsa);
if (_clientSslKey.isNull()) {
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Dsa);
}
if (_clientSslKey.isNull()) {
_clientSslKey = QSslKey(clientKeyPEM, QSsl::Ec);
}
if (_clientSslKey.isNull()) {
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
}
}
// Start fetching client CA certs
_clientSslCaCertificates.clear();
readSingleClientCaCertPEM();
}
void WebFlowCredentials::readSingleClientCaCertPEM()
{
// try to fetch a client ca cert
if (_clientSslCaCertificates.count() < _clientSslCaCertificatesMaxCount) {
const QString kck = keychainKey(
_account->url().toString(),
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
job->start();
} else {
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after " << _clientSslCaCertificatesMaxCount;
slotReadClientCaCertsPEMJobDone(nullptr);
}
}
void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomingJob) {
// Store key in memory
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incomingJob);
if (readJob) {
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
if (sslCertificateList.length() >= 1) {
_clientSslCaCertificates.append(sslCertificateList.at(0));
}
// try next cert
readSingleClientCaCertPEM();
return;
} else {
if (readJob->error() != QKeychain::Error::EntryNotFound ||
(readJob->error() == QKeychain::Error::EntryNotFound) && _clientSslCaCertificates.count() == 0) {
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot " << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
}
}
}
// Now fetch the actual server password
const QString kck = keychainKey( const QString kck = keychainKey(
_account->url().toString(), _account->url().toString(),
_user, _user,
_keychainMigration ? QString() : _account->id()); _keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
job->setKey(kck); job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone); connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
@ -283,6 +570,10 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
return; return;
} }
if (_user.isEmpty()) {
qCWarning(lcWebFlowCredentials) << "Strange: User is empty!";
}
if (error == QKeychain::NoError) { if (error == QKeychain::NoError) {
_password = job->textData(); _password = job->textData();
_ready = true; _ready = true;
@ -296,16 +587,29 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
if (_keychainMigration && _ready) { if (_keychainMigration && _ready) {
_keychainMigration = false; _keychainMigration = false;
persist(); persist();
deleteOldKeychainEntries(); deleteKeychainEntries(true); // true: delete old entries
qCInfo(lcWebFlowCredentials) << "Migrated old keychain entries"; qCInfo(lcWebFlowCredentials) << "Migrated old keychain entries";
} }
} }
void WebFlowCredentials::deleteOldKeychainEntries() { void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); auto startDeleteJob = [this, oldKeychainEntries](QString user) {
job->setInsecureFallback(false); DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setKey(keychainKey(_account->url().toString(), _user, QString())); addSettingsToJob(_account, job);
job->start(); job->setInsecureFallback(true);
job->setKey(keychainKey(_account->url().toString(),
user,
oldKeychainEntries ? QString() : _account->id()));
job->start();
};
startDeleteJob(_user);
startDeleteJob(_user + clientKeyPEMC);
startDeleteJob(_user + clientCertificatePEMC);
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
}
} }
} }

View File

@ -3,6 +3,8 @@
#include <QSslCertificate> #include <QSslCertificate>
#include <QSslKey> #include <QSslKey>
#include <QNetworkRequest>
#include <QQueue>
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
@ -22,9 +24,19 @@ class WebFlowCredentialsDialog;
class WebFlowCredentials : public AbstractCredentials class WebFlowCredentials : public AbstractCredentials
{ {
Q_OBJECT Q_OBJECT
friend class WebFlowCredentialsAccessManager;
public: public:
/// Don't add credentials if this is set on a QNetworkRequest
static constexpr QNetworkRequest::Attribute DontAddCredentialsAttribute = QNetworkRequest::User;
explicit WebFlowCredentials(); explicit WebFlowCredentials();
WebFlowCredentials(const QString &user, const QString &password, const QSslCertificate &certificate = QSslCertificate(), const QSslKey &key = QSslKey()); WebFlowCredentials(
const QString &user,
const QString &password,
const QSslCertificate &certificate = QSslCertificate(),
const QSslKey &key = QSslKey(),
const QList<QSslCertificate> &caCertificates = QList<QSslCertificate>());
QString authType() const override; QString authType() const override;
QString user() const override; QString user() const override;
@ -48,12 +60,50 @@ private slots:
void slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator); void slotAuthentication(QNetworkReply *reply, QAuthenticator *authenticator);
void slotFinished(QNetworkReply *reply); void slotFinished(QNetworkReply *reply);
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host); void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob);
void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob);
void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob);
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
void slotWriteClientCertPEMJobDone();
void slotWriteClientKeyPEMJobDone();
void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob);
void slotWriteJobDone(QKeychain::Job *);
private: private:
/*
* Windows: Workaround for CredWriteW used by QtKeychain
*
* Saving all client CA's within one credential may result in:
* Error: "Credential size exceeds maximum size of 2560"
*/
void readSingleClientCaCertPEM();
void writeSingleClientCaCertPEM();
/*
* Since we're limited by Windows limits we just create our own
* limit to avoid evil things happening by endless recursion
*
* Better than storing the count and relying on maybe-hacked values
*/
static constexpr int _clientSslCaCertificatesMaxCount = 10;
QQueue<QSslCertificate> _clientSslCaCertificatesWriteQueue;
protected:
/** Reads data from keychain locations
*
* Goes through
* slotReadClientCertPEMJobDone to
* slotReadClientKeyPEMJobDone to
* slotReadClientCaCertsPEMJobDone to
* slotReadJobDone
*/
void fetchFromKeychainHelper(); void fetchFromKeychainHelper();
void deleteOldKeychainEntries();
/// Wipes legacy keychain locations
void deleteKeychainEntries(bool oldKeychainEntries = false);
QString fetchUser(); QString fetchUser();
@ -61,10 +111,12 @@ private:
QString _password; QString _password;
QSslKey _clientSslKey; QSslKey _clientSslKey;
QSslCertificate _clientSslCertificate; QSslCertificate _clientSslCertificate;
QList<QSslCertificate> _clientSslCaCertificates;
bool _ready; bool _ready;
bool _credentialsValid; bool _credentialsValid;
bool _keychainMigration; bool _keychainMigration;
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
WebFlowCredentialsDialog *_askDialog; WebFlowCredentialsDialog *_askDialog;
}; };

View File

@ -3,13 +3,21 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QLabel> #include <QLabel>
#include "theme.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/webview.h" #include "wizard/webview.h"
#include "wizard/flow2authwidget.h"
namespace OCC { namespace OCC {
WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent) WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent)
: QDialog(parent) : QDialog(parent),
_useFlow2(useFlow2),
_flow2AuthWidget(nullptr),
_webView(nullptr)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
_layout = new QVBoxLayout(this); _layout = new QVBoxLayout(this);
//QString msg = tr("You have been logged out of %1 as user %2, please login again") //QString msg = tr("You have been logged out of %1 as user %2, please login again")
@ -17,28 +25,43 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(QWidget *parent)
_infoLabel = new QLabel(); _infoLabel = new QLabel();
_layout->addWidget(_infoLabel); _layout->addWidget(_infoLabel);
_webView = new WebView(); if (_useFlow2) {
_layout->addWidget(_webView); _flow2AuthWidget = new Flow2AuthWidget(account);
_layout->addWidget(_flow2AuthWidget);
connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
} else {
_webView = new WebView();
_layout->addWidget(_webView);
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
}
_errorLabel = new QLabel(); _errorLabel = new QLabel();
_errorLabel->hide(); _errorLabel->hide();
_layout->addWidget(_errorLabel); _layout->addWidget(_errorLabel);
setLayout(_layout); WizardCommon::initErrorLabel(_errorLabel);
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched); setLayout(_layout);
} }
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) { void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
Q_UNUSED(e); Q_UNUSED(e)
// Force calling WebView::~WebView() earlier so that _profile and _page are if (_webView) {
// deleted in the correct order. // Force calling WebView::~WebView() earlier so that _profile and _page are
delete _webView; // deleted in the correct order.
delete _webView;
}
if (_flow2AuthWidget)
delete _flow2AuthWidget;
} }
void WebFlowCredentialsDialog::setUrl(const QUrl &url) { void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
_webView->setUrl(url); if (_webView)
_webView->setUrl(url);
} }
void WebFlowCredentialsDialog::setInfo(const QString &msg) { void WebFlowCredentialsDialog::setInfo(const QString &msg) {
@ -46,6 +69,11 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) {
} }
void WebFlowCredentialsDialog::setError(const QString &error) { void WebFlowCredentialsDialog::setError(const QString &error) {
if (_useFlow2 && _flow2AuthWidget) {
_flow2AuthWidget->setError(error);
return;
}
if (error.isEmpty()) { if (error.isEmpty()) {
_errorLabel->hide(); _errorLabel->hide();
} else { } else {

View File

@ -4,23 +4,30 @@
#include <QDialog> #include <QDialog>
#include <QUrl> #include <QUrl>
#include "accountfwd.h"
class QLabel; class QLabel;
class QVBoxLayout; class QVBoxLayout;
namespace OCC { namespace OCC {
class WebView; class WebView;
class Flow2AuthWidget;
class WebFlowCredentialsDialog : public QDialog class WebFlowCredentialsDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
WebFlowCredentialsDialog(QWidget *parent = nullptr); WebFlowCredentialsDialog(Account *account, bool useFlow2, QWidget *parent = nullptr);
void setUrl(const QUrl &url); void setUrl(const QUrl &url);
void setInfo(const QString &msg); void setInfo(const QString &msg);
void setError(const QString &error); void setError(const QString &error);
bool isUsingFlow2() const {
return _useFlow2;
}
protected: protected:
void closeEvent(QCloseEvent * e) override; void closeEvent(QCloseEvent * e) override;
@ -28,7 +35,11 @@ signals:
void urlCatched(const QString user, const QString pass, const QString host); void urlCatched(const QString user, const QString pass, const QString host);
private: private:
bool _useFlow2;
Flow2AuthWidget *_flow2AuthWidget;
WebView *_webView; WebView *_webView;
QLabel *_errorLabel; QLabel *_errorLabel;
QLabel *_infoLabel; QLabel *_infoLabel;
QVBoxLayout *_layout; QVBoxLayout *_layout;

View File

@ -351,6 +351,10 @@ void Folder::showSyncResultPopup()
createGuiLog(_syncResult.firstItemError()->_file, LogStatusError, errorCount); createGuiLog(_syncResult.firstItemError()->_file, LogStatusError, errorCount);
} }
if (int lockedCount = _syncResult.numLockedItems()) {
createGuiLog(_syncResult.firstItemLocked()->_file, LogStatusFileLocked, lockedCount);
}
qCInfo(lcFolder) << "Folder sync result: " << int(_syncResult.status()); qCInfo(lcFolder) << "Folder sync result: " << int(_syncResult.status());
} }

View File

@ -344,7 +344,8 @@ private:
LogStatusNew, LogStatusNew,
LogStatusError, LogStatusError,
LogStatusConflict, LogStatusConflict,
LogStatusUpdated LogStatusUpdated,
LogStatusFileLocked
}; };
void createGuiLog(const QString &filename, LogStatus status, int count, void createGuiLog(const QString &filename, LogStatus status, int count,

View File

@ -168,6 +168,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
QString itemString = qvariant_cast<QString>(index.data(SyncProgressItemString)); QString itemString = qvariant_cast<QString>(index.data(SyncProgressItemString));
int warningCount = qvariant_cast<int>(index.data(WarningCount)); int warningCount = qvariant_cast<int>(index.data(WarningCount));
bool syncOngoing = qvariant_cast<bool>(index.data(SyncRunning)); bool syncOngoing = qvariant_cast<bool>(index.data(SyncRunning));
QDateTime syncDate = qvariant_cast<QDateTime>(index.data(SyncDate));
bool syncEnabled = qvariant_cast<bool>(index.data(FolderAccountConnected)); bool syncEnabled = qvariant_cast<bool>(index.data(FolderAccountConnected));
QRect iconRect = option.rect; QRect iconRect = option.rect;
@ -252,7 +253,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
if (!showProgess) { if (!showProgess) {
painter->setFont(subFont); painter->setFont(subFont);
QString elidedRemotePathText = subFm.elidedText( QString elidedRemotePathText = subFm.elidedText(
tr("Synchronized with local folder"), tr("Synchronized with local folder (%1)").arg(syncDate.toTimeSpec(Qt::LocalTime).toString(Qt::SystemLocaleShortDate)),
Qt::ElideRight, remotePathRect.width()); Qt::ElideRight, remotePathRect.width());
painter->drawText(QStyle::visualRect(option.direction, option.rect, remotePathRect), painter->drawText(QStyle::visualRect(option.direction, option.rect, remotePathRect),
textAlign, elidedRemotePathText); textAlign, elidedRemotePathText);

View File

@ -44,6 +44,7 @@ public:
SyncProgressItemString, SyncProgressItemString,
WarningCount, WarningCount,
SyncRunning, SyncRunning,
SyncDate,
AddButton // 1 = enabled; 2 = disabled AddButton // 1 = enabled; 2 = disabled
}; };

View File

@ -218,6 +218,8 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
return f->syncResult().errorStrings(); return f->syncResult().errorStrings();
case FolderStatusDelegate::SyncRunning: case FolderStatusDelegate::SyncRunning:
return f->syncResult().status() == SyncResult::SyncRunning; return f->syncResult().status() == SyncResult::SyncRunning;
case FolderStatusDelegate::SyncDate:
return f->syncResult().syncTime();
case FolderStatusDelegate::HeaderRole: case FolderStatusDelegate::HeaderRole:
return f->shortGuiRemotePathOrAppName(); return f->shortGuiRemotePathOrAppName();
case FolderStatusDelegate::FolderAliasRole: case FolderStatusDelegate::FolderAliasRole:

View File

@ -75,9 +75,27 @@ bool FolderWatcher::isReliable() const
return _isReliable; return _isReliable;
} }
void FolderWatcher::appendSubPaths(QDir dir, QStringList& subPaths) {
QStringList newSubPaths = dir.entryList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files);
for (int i = 0; i < newSubPaths.size(); i++) {
QString path = dir.path() + "/" + newSubPaths[i];
QFileInfo fileInfo(path);
subPaths.append(path);
if (fileInfo.isDir()) {
QDir dir(path);
appendSubPaths(dir, subPaths);
}
}
}
void FolderWatcher::changeDetected(const QString &path) void FolderWatcher::changeDetected(const QString &path)
{ {
QFileInfo fileInfo(path);
QStringList paths(path); QStringList paths(path);
if (fileInfo.isDir()) {
QDir dir(path);
appendSubPaths(dir, paths);
}
changeDetected(paths); changeDetected(paths);
} }

View File

@ -26,6 +26,7 @@
#include <QHash> #include <QHash>
#include <QScopedPointer> #include <QScopedPointer>
#include <QSet> #include <QSet>
#include <QDir>
class QTimer; class QTimer;
@ -120,6 +121,8 @@ private:
Folder *_folder; Folder *_folder;
bool _isReliable = true; bool _isReliable = true;
void appendSubPaths(QDir dir, QStringList& subPaths);
friend class FolderWatcherPrivate; friend class FolderWatcherPrivate;
}; };
} }

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0"> <item row="2" column="0">

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_6"> <layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="0"> <item row="1" column="0">

View File

@ -184,6 +184,7 @@ void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
void GeneralSettings::slotIgnoreFilesEditor() void GeneralSettings::slotIgnoreFilesEditor()
{ {
if (_ignoreEditor.isNull()) { if (_ignoreEditor.isNull()) {
ConfigFile cfgFile;
_ignoreEditor = new IgnoreListEditor(this); _ignoreEditor = new IgnoreListEditor(this);
_ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true); _ignoreEditor->setAttribute(Qt::WA_DeleteOnClose, true);
_ignoreEditor->open(); _ignoreEditor->open();

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="0"> <item row="2" column="0">

View File

@ -14,8 +14,9 @@
#include "configfile.h" #include "configfile.h"
#include "ignorelisteditor.h"
#include "folderman.h" #include "folderman.h"
#include "generalsettings.h"
#include "ignorelisteditor.h"
#include "ui_ignorelisteditor.h" #include "ui_ignorelisteditor.h"
#include <QFile> #include <QFile>
@ -27,10 +28,6 @@
namespace OCC { namespace OCC {
static int patternCol = 0;
static int deletableCol = 1;
static int readOnlyRows = 3;
IgnoreListEditor::IgnoreListEditor(QWidget *parent) IgnoreListEditor::IgnoreListEditor(QWidget *parent)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::IgnoreListEditor) , ui(new Ui::IgnoreListEditor)
@ -39,28 +36,28 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent)
ui->setupUi(this); ui->setupUi(this);
ConfigFile cfgFile; ConfigFile cfgFile;
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n" //FIXME This is not true. The entries are hardcoded below in setupTableReadOnlyItems
"Items where deletion is allowed will be deleted if they prevent a "
"directory from being removed. "
"This is useful for meta data."));
readOnlyTooltip = tr("This entry is provided by the system at '%1' " readOnlyTooltip = tr("This entry is provided by the system at '%1' "
"and cannot be modified in this view.") "and cannot be modified in this view.")
.arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope))); .arg(QDir::toNativeSeparators(cfgFile.excludeFile(ConfigFile::SystemScope)));
setupTableReadOnlyItems(); setupTableReadOnlyItems();
readIgnoreFile(cfgFile.excludeFile(ConfigFile::UserScope), false); const auto userConfig = cfgFile.excludeFile(ConfigFile::Scope::UserScope);
ui->ignoreTableWidget->readIgnoreFile(userConfig);
connect(this, &QDialog::accepted, this, &IgnoreListEditor::slotUpdateLocalIgnoreList); connect(this, &QDialog::accepted, [=]() {
ui->removePushButton->setEnabled(false); ui->ignoreTableWidget->slotWriteIgnoreFile(userConfig);
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, &IgnoreListEditor::slotItemSelectionChanged); /* handle the hidden file checkbox */
connect(ui->removePushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveCurrentItem);
connect(ui->addPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotAddPattern);
connect(ui->removeAllPushButton, &QAbstractButton::clicked, this, &IgnoreListEditor::slotRemoveAllItems);
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &IgnoreListEditor::slotRestoreDefaults);
ui->tableWidget->resizeColumnsToContents(); /* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch); * handled globally. Save it to every folder that is defined.
ui->tableWidget->verticalHeader()->setVisible(false); * TODO this can now be fixed, simply attach this IgnoreListEditor to top-level account
* settings
*/
FolderMan::instance()->setIgnoreHiddenFiles(ignoreHiddenFiles());
});
connect(ui->buttonBox, &QDialogButtonBox::clicked,
this, &IgnoreListEditor::slotRestoreDefaults);
ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles()); ui->syncHiddenFilesCheckBox->setChecked(!FolderMan::instance()->ignoreHiddenFiles());
} }
@ -70,12 +67,11 @@ IgnoreListEditor::~IgnoreListEditor()
delete ui; delete ui;
} }
void IgnoreListEditor::setupTableReadOnlyItems(){ void IgnoreListEditor::setupTableReadOnlyItems()
ui->tableWidget->setRowCount(0); {
addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true); ui->ignoreTableWidget->addPattern(".csync_journal.db*", /*deletable=*/false, /*readonly=*/true);
addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true); ui->ignoreTableWidget->addPattern("._sync_*.db*", /*deletable=*/false, /*readonly=*/true);
addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true); ui->ignoreTableWidget->addPattern(".sync_*.db*", /*deletable=*/false, /*readonly=*/true);
ui->removeAllPushButton->setEnabled(false);
} }
bool IgnoreListEditor::ignoreHiddenFiles() bool IgnoreListEditor::ignoreHiddenFiles()
@ -83,140 +79,16 @@ bool IgnoreListEditor::ignoreHiddenFiles()
return !ui->syncHiddenFilesCheckBox->isChecked(); return !ui->syncHiddenFilesCheckBox->isChecked();
} }
void IgnoreListEditor::slotItemSelectionChanged() void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button)
{ {
QTableWidgetItem *item = ui->tableWidget->currentItem(); if(ui->buttonBox->buttonRole(button) != QDialogButtonBox::ResetRole)
if (!item) {
ui->removePushButton->setEnabled(false);
return; return;
}
bool enable = item->flags() & Qt::ItemIsEnabled; ui->ignoreTableWidget->slotRemoveAllItems();
ui->removePushButton->setEnabled(enable);
}
void IgnoreListEditor::slotRemoveCurrentItem()
{
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
if(ui->tableWidget->rowCount() == readOnlyRows)
ui->removeAllPushButton->setEnabled(false);
}
void IgnoreListEditor::slotRemoveAllItems()
{
ui->tableWidget->clearContents();
setupTableReadOnlyItems();
}
void IgnoreListEditor::slotUpdateLocalIgnoreList()
{
ConfigFile cfgFile; ConfigFile cfgFile;
QString ignoreFile = cfgFile.excludeFile(ConfigFile::UserScope); setupTableReadOnlyItems();
QFile ignores(ignoreFile); ui->ignoreTableWidget->readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
if (ignores.open(QIODevice::WriteOnly)) {
// rewrites the whole file since now the user can also remove system patterns
QFile::resize(ignoreFile, 0);
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
if (patternItem->flags() & Qt::ItemIsEnabled) {
QByteArray prepend;
if (deletableItem->checkState() == Qt::Checked) {
prepend = "]";
} else if (patternItem->text().startsWith('#')) {
prepend = "\\";
}
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
}
}
} else {
QMessageBox::warning(this, tr("Could not open file"),
tr("Cannot write changes to '%1'.").arg(ignoreFile));
}
ignores.close(); //close the file before reloading stuff.
FolderMan *folderMan = FolderMan::instance();
/* handle the hidden file checkbox */
/* the ignoreHiddenFiles flag is a folder specific setting, but for now, it is
* handled globally. Save it to every folder that is defined.
*/
folderMan->setIgnoreHiddenFiles(ignoreHiddenFiles());
// We need to force a remote discovery after a change of the ignore list.
// Otherwise we would not download the files/directories that are no longer
// ignored (because the remote etag did not change) (issue #3172)
foreach (Folder *folder, folderMan->map()) {
folder->journalDb()->forceRemoteDiscoveryNextSync();
folderMan->scheduleFolder(folder);
}
}
void IgnoreListEditor::slotAddPattern()
{
bool okClicked;
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
tr("Add a new ignore pattern:"),
QLineEdit::Normal, QString(), &okClicked);
if (!okClicked || pattern.isEmpty())
return;
addPattern(pattern, false, false);
ui->tableWidget->scrollToBottom();
}
void IgnoreListEditor::slotRestoreDefaults(QAbstractButton *button){
if(ui->buttonBox->buttonRole(button) == QDialogButtonBox::ResetRole){
ConfigFile cfgFile;
setupTableReadOnlyItems();
readIgnoreFile(cfgFile.excludeFile(ConfigFile::SystemScope), false);
}
}
void IgnoreListEditor::readIgnoreFile(const QString &file, bool readOnly)
{
QFile ignores(file);
if (ignores.open(QIODevice::ReadOnly)) {
while (!ignores.atEnd()) {
QString line = QString::fromUtf8(ignores.readLine());
line.chop(1);
if (!line.isEmpty() && !line.startsWith("#")) {
bool deletable = false;
if (line.startsWith(']')) {
deletable = true;
line = line.mid(1);
}
addPattern(line, deletable, readOnly);
}
}
}
}
int IgnoreListEditor::addPattern(const QString &pattern, bool deletable, bool readOnly)
{
int newRow = ui->tableWidget->rowCount();
ui->tableWidget->setRowCount(newRow + 1);
QTableWidgetItem *patternItem = new QTableWidgetItem;
patternItem->setText(pattern);
ui->tableWidget->setItem(newRow, patternCol, patternItem);
QTableWidgetItem *deletableItem = new QTableWidgetItem;
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
if (readOnly) {
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
patternItem->setToolTip(readOnlyTooltip);
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
}
ui->removeAllPushButton->setEnabled(true);
return newRow;
} }
} // namespace OCC } // namespace OCC

View File

@ -35,23 +35,16 @@ class IgnoreListEditor : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit IgnoreListEditor(QWidget *parent = nullptr); IgnoreListEditor(QWidget *parent = nullptr);
~IgnoreListEditor(); ~IgnoreListEditor();
bool ignoreHiddenFiles(); bool ignoreHiddenFiles();
private slots: private slots:
void slotItemSelectionChanged();
void slotRemoveCurrentItem();
void slotUpdateLocalIgnoreList();
void slotAddPattern();
void slotRestoreDefaults(QAbstractButton *button); void slotRestoreDefaults(QAbstractButton *button);
void slotRemoveAllItems();
private: private:
void readIgnoreFile(const QString &file, bool readOnly);
void setupTableReadOnlyItems(); void setupTableReadOnlyItems();
int addPattern(const QString &pattern, bool deletable, bool readOnly);
QString readOnlyTooltip; QString readOnlyTooltip;
Ui::IgnoreListEditor *ui; Ui::IgnoreListEditor *ui;
}; };

View File

@ -36,96 +36,8 @@
<string>Files Ignored by Patterns</string> <string>Files Ignored by Patterns</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="4" column="0" colspan="2"> <item row="0" column="0">
<widget class="QLabel" name="descriptionLabel"> <widget class="IgnoreListTableWidget" name="ignoreTableWidget" native="true"/>
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>213</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" rowspan="4">
<widget class="QTableWidget" name="tableWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<column>
<property name="text">
<string>Pattern</string>
</property>
</column>
<column>
<property name="text">
<string>Allow Deletion</string>
</property>
</column>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="removePushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="addPushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="removeAllPushButton">
<property name="text">
<string>Remove all</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -139,6 +51,14 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>IgnoreListTableWidget</class>
<extends>QWidget</extends>
<header>ignorelisttablewidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>

View File

@ -0,0 +1,167 @@
#include "ignorelisttablewidget.h"
#include "ui_ignorelisttablewidget.h"
#include "folderman.h"
#include <QFile>
#include <QInputDialog>
#include <QLineEdit>
#include <QMessageBox>
namespace OCC {
static constexpr int patternCol = 0;
static constexpr int deletableCol = 1;
static constexpr int readOnlyRows = 3;
IgnoreListTableWidget::IgnoreListTableWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::IgnoreListTableWidget)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
ui->setupUi(this);
ui->descriptionLabel->setText(tr("Files or folders matching a pattern will not be synchronized.\n\n"
"Items where deletion is allowed will be deleted if they prevent a "
"directory from being removed. "
"This is useful for meta data."));
ui->removePushButton->setEnabled(false);
connect(ui->tableWidget, &QTableWidget::itemSelectionChanged,
this, &IgnoreListTableWidget::slotItemSelectionChanged);
connect(ui->removePushButton, &QAbstractButton::clicked,
this, &IgnoreListTableWidget::slotRemoveCurrentItem);
connect(ui->addPushButton, &QAbstractButton::clicked,
this, &IgnoreListTableWidget::slotAddPattern);
connect(ui->removeAllPushButton, &QAbstractButton::clicked,
this, &IgnoreListTableWidget::slotRemoveAllItems);
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setSectionResizeMode(patternCol, QHeaderView::Stretch);
ui->tableWidget->verticalHeader()->setVisible(false);
}
IgnoreListTableWidget::~IgnoreListTableWidget()
{
delete ui;
}
void IgnoreListTableWidget::slotItemSelectionChanged()
{
QTableWidgetItem *item = ui->tableWidget->currentItem();
if (!item) {
ui->removePushButton->setEnabled(false);
return;
}
bool enable = item->flags() & Qt::ItemIsEnabled;
ui->removePushButton->setEnabled(enable);
}
void IgnoreListTableWidget::slotRemoveCurrentItem()
{
ui->tableWidget->removeRow(ui->tableWidget->currentRow());
if(ui->tableWidget->rowCount() == readOnlyRows)
ui->removeAllPushButton->setEnabled(false);
}
void IgnoreListTableWidget::slotRemoveAllItems()
{
ui->tableWidget->setRowCount(0);
}
void IgnoreListTableWidget::slotWriteIgnoreFile(const QString & file)
{
QFile ignores(file);
if (ignores.open(QIODevice::WriteOnly)) {
// rewrites the whole file since now the user can also remove system patterns
QFile::resize(file, 0);
for (int row = 0; row < ui->tableWidget->rowCount(); ++row) {
QTableWidgetItem *patternItem = ui->tableWidget->item(row, patternCol);
QTableWidgetItem *deletableItem = ui->tableWidget->item(row, deletableCol);
if (patternItem->flags() & Qt::ItemIsEnabled) {
QByteArray prepend;
if (deletableItem->checkState() == Qt::Checked) {
prepend = "]";
} else if (patternItem->text().startsWith('#')) {
prepend = "\\";
}
ignores.write(prepend + patternItem->text().toUtf8() + '\n');
}
}
} else {
QMessageBox::warning(this, tr("Could not open file"),
tr("Cannot write changes to '%1'.").arg(file));
}
ignores.close(); //close the file before reloading stuff.
FolderMan *folderMan = FolderMan::instance();
// We need to force a remote discovery after a change of the ignore list.
// Otherwise we would not download the files/directories that are no longer
// ignored (because the remote etag did not change) (issue #3172)
foreach (Folder *folder, folderMan->map()) {
folder->journalDb()->forceRemoteDiscoveryNextSync();
folderMan->scheduleFolder(folder);
}
}
void IgnoreListTableWidget::slotAddPattern()
{
bool okClicked;
QString pattern = QInputDialog::getText(this, tr("Add Ignore Pattern"),
tr("Add a new ignore pattern:"),
QLineEdit::Normal, QString(), &okClicked);
if (!okClicked || pattern.isEmpty())
return;
addPattern(pattern, false, false);
ui->tableWidget->scrollToBottom();
}
void IgnoreListTableWidget::readIgnoreFile(const QString &file, bool readOnly)
{
QFile ignores(file);
if (ignores.open(QIODevice::ReadOnly)) {
while (!ignores.atEnd()) {
QString line = QString::fromUtf8(ignores.readLine());
line.chop(1);
if (!line.isEmpty() && !line.startsWith("#")) {
bool deletable = false;
if (line.startsWith(']')) {
deletable = true;
line = line.mid(1);
}
addPattern(line, deletable, readOnly);
}
}
}
}
int IgnoreListTableWidget::addPattern(const QString &pattern, bool deletable, bool readOnly)
{
int newRow = ui->tableWidget->rowCount();
ui->tableWidget->setRowCount(newRow + 1);
QTableWidgetItem *patternItem = new QTableWidgetItem;
patternItem->setText(pattern);
ui->tableWidget->setItem(newRow, patternCol, patternItem);
QTableWidgetItem *deletableItem = new QTableWidgetItem;
deletableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
deletableItem->setCheckState(deletable ? Qt::Checked : Qt::Unchecked);
ui->tableWidget->setItem(newRow, deletableCol, deletableItem);
if (readOnly) {
patternItem->setFlags(patternItem->flags() ^ Qt::ItemIsEnabled);
patternItem->setToolTip(readOnlyTooltip);
deletableItem->setFlags(deletableItem->flags() ^ Qt::ItemIsEnabled);
}
ui->removeAllPushButton->setEnabled(true);
return newRow;
}
} // namespace OCC

View File

@ -0,0 +1,38 @@
#pragma once
#include <QWidget>
class QAbstractButton;
namespace OCC {
namespace Ui {
class IgnoreListTableWidget;
}
class IgnoreListTableWidget : public QWidget
{
Q_OBJECT
public:
IgnoreListTableWidget(QWidget *parent = nullptr);
~IgnoreListTableWidget();
void readIgnoreFile(const QString &file, bool readOnly = false);
int addPattern(const QString &pattern, bool deletable, bool readOnly);
public slots:
void slotRemoveAllItems();
void slotWriteIgnoreFile(const QString & file);
private slots:
void slotItemSelectionChanged();
void slotRemoveCurrentItem();
void slotAddPattern();
private:
void setupTableReadOnlyItems();
QString readOnlyTooltip;
Ui::IgnoreListTableWidget *ui;
};
} // namespace OCC

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OCC::IgnoreListTableWidget</class>
<widget class="QWidget" name="OCC::IgnoreListTableWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>342</width>
<height>378</height>
</rect>
</property>
<property name="windowTitle">
<string>IgnoreListTableWidget</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" rowspan="4">
<widget class="QTableWidget" name="tableWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<column>
<property name="text">
<string>Pattern</string>
</property>
</column>
<column>
<property name="text">
<string>Allow Deletion</string>
</property>
</column>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="addPushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="removePushButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="removeAllPushButton">
<property name="text">
<string>Remove all</string>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>322</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="descriptionLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Dialog</string> <string notr="true">Dialog</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
@ -33,7 +33,7 @@
<item> <item>
<widget class="QLabel" name="notice"> <widget class="QLabel" name="notice">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -20,7 +20,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Dialog</string> <string notr="true">Dialog</string>
</property> </property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
<bool>false</bool> <bool>false</bool>

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0"> <item row="0" column="0">

View File

@ -21,7 +21,7 @@ namespace OCC {
OcsShareeJob::OcsShareeJob(AccountPtr account) OcsShareeJob::OcsShareeJob(AccountPtr account)
: OcsJob(account) : OcsJob(account)
{ {
setPath("ocs/v1.php/apps/files_sharing/api/v1/sharees"); setPath("ocs/v2.php/apps/files_sharing/api/v1/sharees");
connect(this, &OcsJob::jobFinished, this, &OcsShareeJob::jobDone); connect(this, &OcsJob::jobFinished, this, &OcsShareeJob::jobDone);
} }

View File

@ -24,7 +24,7 @@ namespace OCC {
OcsShareJob::OcsShareJob(AccountPtr account) OcsShareJob::OcsShareJob(AccountPtr account)
: OcsJob(account) : OcsJob(account)
{ {
setPath("ocs/v1.php/apps/files_sharing/api/v1/shares"); setPath("ocs/v2.php/apps/files_sharing/api/v1/shares");
connect(this, &OcsJob::jobFinished, this, &OcsShareJob::jobDone); connect(this, &OcsJob::jobFinished, this, &OcsShareJob::jobDone);
} }
@ -33,6 +33,7 @@ void OcsShareJob::getShares(const QString &path)
setVerb("GET"); setVerb("GET");
addParam(QString::fromLatin1("path"), path); addParam(QString::fromLatin1("path"), path);
addParam(QString::fromLatin1("reshares"), QString("true"));
addPassStatusCode(404); addPassStatusCode(404);
start(); start();

View File

@ -779,7 +779,7 @@ void ownCloudGui::setupActions()
_actionStatus->setEnabled(false); _actionStatus->setEnabled(false);
_actionSettings = new QAction(tr("Settings..."), this); _actionSettings = new QAction(tr("Settings..."), this);
_actionNewAccountWizard = new QAction(tr("New account..."), this); _actionNewAccountWizard = new QAction(tr("New account..."), this);
_actionRecent = new QAction(tr("Details..."), this); _actionRecent = new QAction(tr("View more activity..."), this);
_actionRecent->setEnabled(true); _actionRecent->setEnabled(true);
QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol); QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol);

View File

@ -408,7 +408,7 @@ void OwncloudSetupWizard::slotAuthError()
} }
_ocWizard->show(); _ocWizard->show();
if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds) { if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
_ocWizard->back(); _ocWizard->back();
} }
_ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply)); _ocWizard->displayError(errorMsg, _ocWizard->currentId() == WizardCommon::Page_ServerSetup && checkDowngradeAdvised(reply));

View File

@ -194,7 +194,7 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
const QString versionString = _accountState->account()->serverVersion(); const QString versionString = _accountState->account()->serverVersion();
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares"; qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
foreach (auto share, shares) { foreach (auto share, shares) {
if (share->getShareType() != Share::TypeLink) { if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
continue; continue;
} }
@ -271,6 +271,7 @@ void ShareDialog::showSharingUi()
auto label = new QLabel(this); auto label = new QLabel(this);
label->setText(tr("The file can not be shared because it was shared without sharing permission.")); label->setText(tr("The file can not be shared because it was shared without sharing permission."));
label->setWordWrap(true); label->setWordWrap(true);
_ui->verticalLayout->insertWidget(1, label);
return; return;
} }

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>350</width> <width>350</width>
<height>136</height> <height>160</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -29,6 +29,131 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="2" column="1">
<widget class="QDateEdit" name="calendar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Password:</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="shareLinkToolButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QLabel" name="errorLabel">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>123</red>
<green>121</green>
<blue>134</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="confirmPassword">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="confirmExpirationDate">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3"> <item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
@ -102,60 +227,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="0" column="3">
<widget class="QToolButton" name="shareLinkToolButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Password:</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="confirmPassword">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="expirationLabel"> <widget class="QLabel" name="expirationLabel">
<property name="sizePolicy"> <property name="sizePolicy">
@ -172,77 +243,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QDateEdit" name="calendar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="confirmExpirationDate">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QLabel" name="errorLabel">
<property name="palette">
<palette>
<active>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</active>
<inactive>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
</inactive>
<disabled>
<colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>123</red>
<green>121</green>
<blue>134</blue>
</color>
</brush>
</colorrole>
</disabled>
</palette>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>

View File

@ -52,12 +52,16 @@ static void updateFolder(const AccountPtr &account, const QString &path)
Share::Share(AccountPtr account, Share::Share(AccountPtr account,
const QString &id, const QString &id,
const QString &uidowner,
const QString &ownerDisplayName,
const QString &path, const QString &path,
const ShareType shareType, const ShareType shareType,
const Permissions permissions, const Permissions permissions,
const QSharedPointer<Sharee> shareWith) const QSharedPointer<Sharee> shareWith)
: _account(account) : _account(account)
, _id(id) , _id(id)
, _uidowner(uidowner)
, _ownerDisplayName(ownerDisplayName)
, _path(path) , _path(path)
, _shareType(shareType) , _shareType(shareType)
, _permissions(permissions) , _permissions(permissions)
@ -80,6 +84,16 @@ QString Share::getId() const
return _id; return _id;
} }
QString Share::getUidOwner() const
{
return _uidowner;
}
QString Share::getOwnerDisplayName() const
{
return _ownerDisplayName;
}
Share::ShareType Share::getShareType() const Share::ShareType Share::getShareType() const
{ {
return _shareType; return _shareType;
@ -153,6 +167,8 @@ bool LinkShare::isPasswordSet() const
LinkShare::LinkShare(AccountPtr account, LinkShare::LinkShare(AccountPtr account,
const QString &id, const QString &id,
const QString &uidowner,
const QString &ownerDisplayName,
const QString &path, const QString &path,
const QString &name, const QString &name,
const QString &token, const QString &token,
@ -160,7 +176,7 @@ LinkShare::LinkShare(AccountPtr account,
bool passwordSet, bool passwordSet,
const QUrl &url, const QUrl &url,
const QDate &expireDate) const QDate &expireDate)
: Share(account, id, path, Share::TypeLink, permissions) : Share(account, id, uidowner, ownerDisplayName, path, Share::TypeLink, permissions)
, _name(name) , _name(name)
, _token(token) , _token(token)
, _passwordSet(passwordSet) , _passwordSet(passwordSet)
@ -393,6 +409,8 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
return QSharedPointer<LinkShare>(new LinkShare(_account, return QSharedPointer<LinkShare>(new LinkShare(_account,
data.value("id").toVariant().toString(), // "id" used to be an integer, support both data.value("id").toVariant().toString(), // "id" used to be an integer, support both
data.value("uid_owner").toString(),
data.value("displayname_owner").toString(),
data.value("path").toString(), data.value("path").toString(),
data.value("name").toString(), data.value("name").toString(),
data.value("token").toString(), data.value("token").toString(),
@ -410,6 +428,8 @@ QSharedPointer<Share> ShareManager::parseShare(const QJsonObject &data)
return QSharedPointer<Share>(new Share(_account, return QSharedPointer<Share>(new Share(_account,
data.value("id").toVariant().toString(), // "id" used to be an integer, support both data.value("id").toVariant().toString(), // "id" used to be an integer, support both
data.value("uid_owner").toVariant().toString(),
data.value("displayname_owner").toVariant().toString(),
data.value("path").toString(), data.value("path").toString(),
(Share::ShareType)data.value("share_type").toInt(), (Share::ShareType)data.value("share_type").toInt(),
(Share::Permissions)data.value("permissions").toInt(), (Share::Permissions)data.value("permissions").toInt(),

View File

@ -54,6 +54,8 @@ public:
*/ */
explicit Share(AccountPtr account, explicit Share(AccountPtr account,
const QString &id, const QString &id,
const QString &owner,
const QString &ownerDisplayName,
const QString &path, const QString &path,
const ShareType shareType, const ShareType shareType,
const Permissions permissions = SharePermissionDefault, const Permissions permissions = SharePermissionDefault,
@ -71,6 +73,16 @@ public:
*/ */
QString getId() const; QString getId() const;
/*
* Get the uid_owner
*/
QString getUidOwner() const;
/*
* Get the owner display name
*/
QString getOwnerDisplayName() const;
/* /*
* Get the shareType * Get the shareType
*/ */
@ -110,6 +122,8 @@ signals:
protected: protected:
AccountPtr _account; AccountPtr _account;
QString _id; QString _id;
QString _uidowner;
QString _ownerDisplayName;
QString _path; QString _path;
ShareType _shareType; ShareType _shareType;
Permissions _permissions; Permissions _permissions;
@ -134,6 +148,8 @@ class LinkShare : public Share
public: public:
explicit LinkShare(AccountPtr account, explicit LinkShare(AccountPtr account,
const QString &id, const QString &id,
const QString &uidowner,
const QString &ownerDisplayName,
const QString &path, const QString &path,
const QString &name, const QString &name,
const QString &token, const QString &token,

View File

@ -190,13 +190,23 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
int x = 0; int x = 0;
int height = 0; int height = 0;
QList<QString> linkOwners({});
foreach (const auto &share, shares) { foreach (const auto &share, shares) {
// We don't handle link shares, only TypeUser or TypeGroup // We don't handle link shares, only TypeUser or TypeGroup
if (share->getShareType() == Share::TypeLink) { if (share->getShareType() == Share::TypeLink) {
if(!share->getUidOwner().isEmpty() &&
share->getUidOwner() != share->account()->davUser()){
linkOwners.append(share->getOwnerDisplayName());
}
continue; continue;
} }
// the owner of the file that shared it first
if(x == 0 && !share->getUidOwner().isEmpty()){
_ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
}
ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _parentScrollArea); ShareUserLine *s = new ShareUserLine(share, _maxSharingPermissions, _isFile, _parentScrollArea);
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize); connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares); connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
@ -209,7 +219,18 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
} }
} }
scrollArea->setFrameShape(x > 3 ? QFrame::StyledPanel : QFrame::NoFrame); foreach (const QString &owner, linkOwners) {
auto ownerLabel = new QLabel(QString(owner + " shared via link"));
layout->addWidget(ownerLabel);
ownerLabel->setVisible(true);
x++;
if (x <= 6) {
height = newViewPort->sizeHint().height();
}
}
scrollArea->setFrameShape(x > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
scrollArea->setVisible(!shares.isEmpty()); scrollArea->setVisible(!shares.isEmpty());
scrollArea->setFixedHeight(height); scrollArea->setFixedHeight(height);
scrollArea->setWidget(newViewPort); scrollArea->setWidget(newViewPort);
@ -367,6 +388,15 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
menu->addAction(_permissionReshare); menu->addAction(_permissionReshare);
connect(_permissionReshare, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged); connect(_permissionReshare, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
menu->addSeparator();
// Adds action to delete share widget
QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),QIcon(QLatin1String(":/client/resources/delete.png")));
_deleteShareButton= new QAction(deleteicon,tr("Unshare"), this);
menu->addAction(_deleteShareButton);
connect(_deleteShareButton, &QAction::triggered, this, &ShareUserLine::on_deleteShareButton_clicked);
/* /*
* Files can't have create or delete permissions * Files can't have create or delete permissions
*/ */
@ -413,8 +443,8 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet); connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet);
connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted); connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted);
_ui->deleteShareButton->setIcon(QIcon::fromTheme(QLatin1String("user-trash"), // _ui->deleteShareButton->setIcon(QIcon::fromTheme(QLatin1String("user-trash"),
QIcon(QLatin1String(":/client/resources/delete.png")))); // QIcon(QLatin1String(":/client/resources/delete.png"))));
if (!share->account()->capabilities().shareResharing()) { if (!share->account()->capabilities().shareResharing()) {
_permissionReshare->setVisible(false); _permissionReshare->setVisible(false);

View File

@ -148,6 +148,7 @@ private:
// _permissionEdit is a checkbox // _permissionEdit is a checkbox
QAction *_permissionReshare; QAction *_permissionReshare;
QAction *_deleteShareButton;
QAction *_permissionCreate; QAction *_permissionCreate;
QAction *_permissionChange; QAction *_permissionChange;
QAction *_permissionDelete; QAction *_permissionDelete;

View File

@ -32,6 +32,13 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QLabel" name="mainOwnerLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="shareeHorizontalLayout"> <layout class="QHBoxLayout" name="shareeHorizontalLayout">
<property name="leftMargin"> <property name="leftMargin">

View File

@ -91,15 +91,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> </layout>
<widget class="QToolButton" name="deleteShareButton">
<property name="icon">
<iconset theme="user-trash">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
</layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -184,10 +184,15 @@ QString SslErrorDialog::certDiv(QSslCertificate cert) const
msg += QL("<p>"); msg += QL("<p>");
QString md5sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Md5).toHex()); if (cert.effectiveDate() < QDateTime(QDate(2016, 1, 1), QTime(), Qt::UTC)) {
QString sha1sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex()); QString sha1sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha1).toHex());
msg += tr("Fingerprint (MD5): <tt>%1</tt>").arg(md5sum) + QL("<br/>"); msg += tr("Fingerprint (SHA1): <tt>%1</tt>").arg(sha1sum) + QL("<br/>");
msg += tr("Fingerprint (SHA1): <tt>%1</tt>").arg(sha1sum) + QL("<br/>"); }
QString sha256sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha256).toHex());
QString sha512sum = Utility::formatFingerprint(cert.digest(QCryptographicHash::Sha512).toHex());
msg += tr("Fingerprint (SHA-256): <tt>%1</tt>").arg(sha256sum) + QL("<br/>");
msg += tr("Fingerprint (SHA-512): <tt>%1</tt>").arg(sha512sum) + QL("<br/>");
msg += QL("<br/>"); msg += QL("<br/>");
msg += tr("Effective Date: %1").arg(cert.effectiveDate().toString()) + QL("<br/>"); msg += tr("Effective Date: %1").arg(cert.effectiveDate().toString()) + QL("<br/>");
msg += tr("Expiration Date: %1").arg(cert.expiryDate().toString()) + QL("</p>"); msg += tr("Expiration Date: %1").arg(cert.expiryDate().toString()) + QL("</p>");

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="0"> <item row="1" column="0">

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
* Copyright (C) by Michael Schuster <michael@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.
*/
#include <QVariant>
#include <QMenu>
#include <QClipboard>
#include "wizard/flow2authcredspage.h"
#include "theme.h"
#include "account.h"
#include "cookiejar.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/owncloudwizard.h"
#include "creds/credentialsfactory.h"
#include "creds/webflowcredentials.h"
namespace OCC {
Flow2AuthCredsPage::Flow2AuthCredsPage()
: AbstractCredentialsWizardPage()
{
_ui.setupUi(this);
Theme *theme = Theme::instance();
_ui.topLabel->hide();
_ui.bottomLabel->hide();
QVariant variant = theme->customMedia(Theme::oCSetupTop);
WizardCommon::setupCustomMedia(variant, _ui.topLabel);
variant = theme->customMedia(Theme::oCSetupBottom);
WizardCommon::setupCustomMedia(variant, _ui.bottomLabel);
WizardCommon::initErrorLabel(_ui.errorLabel);
setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI())));
setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser (Login Flow v2)")));
connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthCredsPage::slotOpenBrowser);
connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthCredsPage::slotCopyLinkToClipboard);
}
void Flow2AuthCredsPage::initializePage()
{
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
ocWizard->account()->setCredentials(CredentialsFactory::create("http"));
_asyncAuth.reset(new Flow2Auth(ocWizard->account().data(), this));
connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthCredsPage::asyncAuthResult, Qt::QueuedConnection);
_asyncAuth->start();
// Don't hide the wizard (avoid user confusion)!
//wizard()->hide();
}
void OCC::Flow2AuthCredsPage::cleanupPage()
{
// The next or back button was activated, show the wizard again
wizard()->show();
_asyncAuth.reset();
// Forget sensitive data
_appPassword.clear();
_user.clear();
}
void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user,
const QString &appPassword)
{
switch (r) {
case Flow2Auth::NotSupported: {
/* Flow2Auth not supported (can't open browser) */
_ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser."));
_ui.errorLabel->show();
/* Don't fallback to HTTP credentials */
/*OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
ocWizard->back();
ocWizard->setAuthType(DetermineAuthTypeJob::Basic);*/
break;
}
case Flow2Auth::Error:
/* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
_ui.errorLabel->show();
wizard()->show();
break;
case Flow2Auth::LoggedIn: {
_user = user;
_appPassword = appPassword;
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
emit connectToOCUrl(ocWizard->account()->url().toString());
break;
}
}
}
int Flow2AuthCredsPage::nextId() const
{
return WizardCommon::Page_AdvancedSetup;
}
void Flow2AuthCredsPage::setConnected()
{
wizard()->show();
}
AbstractCredentials *Flow2AuthCredsPage::getCredentials() const
{
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
return new WebFlowCredentials(
_user,
_appPassword,
ocWizard->_clientSslCertificate,
ocWizard->_clientSslKey,
ocWizard->_clientSslCaCertificates
);
}
bool Flow2AuthCredsPage::isComplete() const
{
return false; /* We can never go forward manually */
}
void Flow2AuthCredsPage::slotOpenBrowser()
{
if (_ui.errorLabel)
_ui.errorLabel->hide();
if (_asyncAuth)
_asyncAuth->openBrowser();
}
void Flow2AuthCredsPage::slotCopyLinkToClipboard()
{
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
}
} // namespace OCC

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
* Copyright (C) by Michael Schuster <michael@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.
*/
#pragma once
#include <QList>
#include <QMap>
#include <QNetworkCookie>
#include <QUrl>
#include <QPointer>
#include "wizard/abstractcredswizardpage.h"
#include "accountfwd.h"
#include "creds/flow2auth.h"
#include "ui_flow2authcredspage.h"
namespace OCC {
class Flow2AuthCredsPage : public AbstractCredentialsWizardPage
{
Q_OBJECT
public:
Flow2AuthCredsPage();
AbstractCredentials *getCredentials() const override;
void initializePage() override;
void cleanupPage() override;
int nextId() const override;
void setConnected();
bool isComplete() const override;
public Q_SLOTS:
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
signals:
void connectToOCUrl(const QString &);
public:
QString _user;
QString _appPassword;
QScopedPointer<Flow2Auth> _asyncAuth;
Ui_Flow2AuthCredsPage _ui;
protected slots:
void slotOpenBrowser();
void slotCopyLinkToClipboard();
};
} // namespace OCC

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Flow2AuthCredsPage</class>
<widget class="QWidget" name="Flow2AuthCredsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>424</width>
<height>373</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="topLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Please switch to your browser to proceed.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="errorLabel">
<property name="text">
<string>An error occurred while connecting. Please try again.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="openLinkButton">
<property name="text">
<string>Re-open Browser</string>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="copyLinkButton">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Copy link</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>127</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="bottomLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) by Michael Schuster <michael@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.
*/
#include "flow2authwidget.h"
#include <QDesktopServices>
#include <QProgressBar>
#include <QLoggingCategory>
#include <QLocale>
#include <QMessageBox>
#include <QMenu>
#include <QClipboard>
#include "common/utility.h"
#include "account.h"
#include "theme.h"
#include "wizard/owncloudwizardcommon.h"
namespace OCC {
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "gui.wizard.flow2authwidget", QtInfoMsg)
Flow2AuthWidget::Flow2AuthWidget(Account *account, QWidget *parent)
: QWidget(parent),
_account(account),
_ui()
{
_ui.setupUi(this);
Theme *theme = Theme::instance();
_ui.topLabel->hide();
_ui.bottomLabel->hide();
QVariant variant = theme->customMedia(Theme::oCSetupTop);
WizardCommon::setupCustomMedia(variant, _ui.topLabel);
variant = theme->customMedia(Theme::oCSetupBottom);
WizardCommon::setupCustomMedia(variant, _ui.bottomLabel);
WizardCommon::initErrorLabel(_ui.errorLabel);
connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthWidget::slotOpenBrowser);
connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &Flow2AuthWidget::slotCopyLinkToClipboard);
_asyncAuth.reset(new Flow2Auth(_account, this));
connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::asyncAuthResult, Qt::QueuedConnection);
_asyncAuth->start();
}
void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user,
const QString &appPassword)
{
switch (r) {
case Flow2Auth::NotSupported:
/* Flow2Auth can't open browser */
_ui.errorLabel->setText(tr("Unable to open the Browser, please copy the link to your Browser."));
_ui.errorLabel->show();
break;
case Flow2Auth::Error:
/* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
_ui.errorLabel->show();
break;
case Flow2Auth::LoggedIn: {
_user = user;
_appPassword = appPassword;
emit urlCatched(_user, _appPassword, QString());
break;
}
}
}
void Flow2AuthWidget::setError(const QString &error) {
if (error.isEmpty()) {
_ui.errorLabel->hide();
} else {
_ui.errorLabel->setText(error);
_ui.errorLabel->show();
}
}
Flow2AuthWidget::~Flow2AuthWidget() {
_asyncAuth.reset();
// Forget sensitive data
_appPassword.clear();
_user.clear();
}
void Flow2AuthWidget::slotOpenBrowser()
{
if (_ui.errorLabel)
_ui.errorLabel->hide();
if (_asyncAuth)
_asyncAuth->openBrowser();
}
void Flow2AuthWidget::slotCopyLinkToClipboard()
{
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
}
} // namespace OCC

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) by Michael Schuster <michael@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 FLOW2AUTHWIDGET_H
#define FLOW2AUTHWIDGET_H
#include <QUrl>
#include <QWidget>
#include "creds/flow2auth.h"
#include "ui_flow2authwidget.h"
namespace OCC {
class Flow2AuthWidget : public QWidget
{
Q_OBJECT
public:
Flow2AuthWidget(Account *account, QWidget *parent = nullptr);
virtual ~Flow2AuthWidget();
void setError(const QString &error);
public Q_SLOTS:
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
signals:
void urlCatched(const QString user, const QString pass, const QString host);
private:
Account *_account;
QString _user;
QString _appPassword;
QScopedPointer<Flow2Auth> _asyncAuth;
Ui_Flow2AuthWidget _ui;
protected slots:
void slotOpenBrowser();
void slotCopyLinkToClipboard();
};
}
#endif // FLOW2AUTHWIDGET_H

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Flow2AuthWidget</class>
<widget class="QWidget" name="Flow2AuthWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>280</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>280</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="topLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Please switch to your browser to proceed.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="errorLabel">
<property name="text">
<string>An error occurred while connecting. Please try again.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="openLinkButton">
<property name="text">
<string>Re-open Browser</string>
</property>
</widget>
</item>
<item>
<widget class="QCommandLinkButton" name="copyLinkButton">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Copy link</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>127</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="bottomLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -17,7 +17,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
@ -29,7 +29,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -97,7 +97,7 @@
<item> <item>
<widget class="QLabel" name="lSyncEverythingSizeLabel"> <widget class="QLabel" name="lSyncEverythingSizeLabel">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::PlainText</enum> <enum>Qt::PlainText</enum>
@ -202,7 +202,7 @@
<item> <item>
<widget class="QLabel" name="lSelectiveSyncSizeLabel"> <widget class="QLabel" name="lSelectiveSyncSizeLabel">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::PlainText</enum> <enum>Qt::PlainText</enum>
@ -232,7 +232,7 @@
<item> <item>
<widget class="QLabel" name="lLocalIcon"> <widget class="QLabel" name="lLocalIcon">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
@ -282,7 +282,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>pbSelectLocalFolder</string> <string notr="true">pbSelectLocalFolder</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -291,7 +291,7 @@
<item> <item>
<widget class="QLabel" name="lServerIcon"> <widget class="QLabel" name="lServerIcon">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
@ -383,7 +383,7 @@
<item row="4" column="1"> <item row="4" column="1">
<widget class="QLabel" name="lFreeSpace"> <widget class="QLabel" name="lFreeSpace">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -392,7 +392,7 @@
<item> <item>
<widget class="QLabel" name="errorLabel"> <widget class="QLabel" name="errorLabel">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -418,7 +418,7 @@
<item> <item>
<widget class="QLabel" name="bottomLabel"> <widget class="QLabel" name="bottomLabel">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="5" column="1"> <item row="5" column="1">

View File

@ -45,21 +45,8 @@ OwncloudOAuthCredsPage::OwncloudOAuthCredsPage()
setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI()))); setTitle(WizardCommon::titleTemplate().arg(tr("Connect to %1").arg(Theme::instance()->appNameGUI())));
setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser"))); setSubTitle(WizardCommon::subTitleTemplate().arg(tr("Login in your browser")));
connect(_ui.openLinkButton, &QCommandLinkButton::clicked, [this] { connect(_ui.openLinkButton, &QCommandLinkButton::clicked, this, &OwncloudOAuthCredsPage::slotOpenBrowser);
_ui.errorLabel->hide(); connect(_ui.copyLinkButton, &QCommandLinkButton::clicked, this, &OwncloudOAuthCredsPage::slotCopyLinkToClipboard);
if (_asyncAuth)
_asyncAuth->openBrowser();
});
_ui.openLinkButton->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(_ui.openLinkButton, &QWidget::customContextMenuRequested, [this](const QPoint &pos) {
auto menu = new QMenu(_ui.openLinkButton);
menu->addAction(tr("Copy link to clipboard"), this, [this] {
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(_ui.openLinkButton->mapToGlobal(pos));
});
} }
void OwncloudOAuthCredsPage::initializePage() void OwncloudOAuthCredsPage::initializePage()
@ -70,7 +57,9 @@ void OwncloudOAuthCredsPage::initializePage()
_asyncAuth.reset(new OAuth(ocWizard->account().data(), this)); _asyncAuth.reset(new OAuth(ocWizard->account().data(), this));
connect(_asyncAuth.data(), &OAuth::result, this, &OwncloudOAuthCredsPage::asyncAuthResult, Qt::QueuedConnection); connect(_asyncAuth.data(), &OAuth::result, this, &OwncloudOAuthCredsPage::asyncAuthResult, Qt::QueuedConnection);
_asyncAuth->start(); _asyncAuth->start();
wizard()->hide();
// Don't hide the wizard (avoid user confusion)!
//wizard()->hide();
} }
void OCC::OwncloudOAuthCredsPage::cleanupPage() void OCC::OwncloudOAuthCredsPage::cleanupPage()
@ -131,4 +120,19 @@ bool OwncloudOAuthCredsPage::isComplete() const
return false; /* We can never go forward manually */ return false; /* We can never go forward manually */
} }
void OwncloudOAuthCredsPage::slotOpenBrowser()
{
if (_ui.errorLabel)
_ui.errorLabel->hide();
if (_asyncAuth)
_asyncAuth->openBrowser();
}
void OwncloudOAuthCredsPage::slotCopyLinkToClipboard()
{
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
}
} // namespace OCC } // namespace OCC

View File

@ -57,6 +57,10 @@ public:
QString _refreshToken; QString _refreshToken;
QScopedPointer<OAuth> _asyncAuth; QScopedPointer<OAuth> _asyncAuth;
Ui_OwncloudOAuthCredsPage _ui; Ui_OwncloudOAuthCredsPage _ui;
protected slots:
void slotOpenBrowser();
void slotCopyLinkToClipboard();
}; };
} // namespace OCC } // namespace OCC

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
@ -57,6 +57,19 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCommandLinkButton" name="copyLinkButton">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Copy link</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">

View File

@ -17,7 +17,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<property name="autoFillBackground"> <property name="autoFillBackground">
<bool>false</bool> <bool>false</bool>
@ -35,7 +35,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -94,7 +94,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>

View File

@ -258,6 +258,8 @@ int OwncloudSetupPage::nextId() const
return WizardCommon::Page_HttpCreds; return WizardCommon::Page_HttpCreds;
case DetermineAuthTypeJob::OAuth: case DetermineAuthTypeJob::OAuth:
return WizardCommon::Page_OAuthCreds; return WizardCommon::Page_OAuthCreds;
case DetermineAuthTypeJob::LoginFlowV2:
return WizardCommon::Page_Flow2AuthCreds;
case DetermineAuthTypeJob::Shibboleth: case DetermineAuthTypeJob::Shibboleth:
return WizardCommon::Page_ShibbolethCreds; return WizardCommon::Page_ShibbolethCreds;
case DetermineAuthTypeJob::WebViewFlow: case DetermineAuthTypeJob::WebViewFlow:
@ -375,12 +377,13 @@ QString subjectInfoHelper(const QSslCertificate &cert, const QByteArray &qa)
//called during the validation of the client certificate. //called during the validation of the client certificate.
void OwncloudSetupPage::slotCertificateAccepted() void OwncloudSetupPage::slotCertificateAccepted()
{ {
QList<QSslCertificate> clientCaCertificates;
QFile certFile(addCertDial->getCertificatePath()); QFile certFile(addCertDial->getCertificatePath());
certFile.open(QFile::ReadOnly); certFile.open(QFile::ReadOnly);
if (QSslCertificate::importPkcs12(&certFile, if (QSslCertificate::importPkcs12(
&_ocWizard->_clientSslKey, &_ocWizard->_clientSslCertificate, &certFile,
&clientCaCertificates, &_ocWizard->_clientSslKey,
&_ocWizard->_clientSslCertificate,
&_ocWizard->_clientSslCaCertificates,
addCertDial->getCertificatePasswd().toLocal8Bit())) { addCertDial->getCertificatePasswd().toLocal8Bit())) {
AccountPtr acc = _ocWizard->account(); AccountPtr acc = _ocWizard->account();
@ -392,6 +395,12 @@ void OwncloudSetupPage::slotCertificateAccepted()
// cert will come via the HttpCredentials // cert will come via the HttpCredentials
sslConfiguration.setLocalCertificate(_ocWizard->_clientSslCertificate); sslConfiguration.setLocalCertificate(_ocWizard->_clientSslCertificate);
sslConfiguration.setPrivateKey(_ocWizard->_clientSslKey); sslConfiguration.setPrivateKey(_ocWizard->_clientSslKey);
// Be sure to merge the CAs
auto ca = sslConfiguration.systemCaCertificates();
ca.append(_ocWizard->_clientSslCaCertificates);
sslConfiguration.setCaCertificates(ca);
acc->setSslConfiguration(sslConfiguration); acc->setSslConfiguration(sslConfiguration);
// Make sure TCP connections get re-established // Make sure TCP connections get re-established

View File

@ -27,6 +27,7 @@
#include "wizard/owncloudadvancedsetuppage.h" #include "wizard/owncloudadvancedsetuppage.h"
#include "wizard/owncloudwizardresultpage.h" #include "wizard/owncloudwizardresultpage.h"
#include "wizard/webviewpage.h" #include "wizard/webviewpage.h"
#include "wizard/flow2authcredspage.h"
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
@ -45,6 +46,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
, _setupPage(new OwncloudSetupPage(this)) , _setupPage(new OwncloudSetupPage(this))
, _httpCredsPage(new OwncloudHttpCredsPage(this)) , _httpCredsPage(new OwncloudHttpCredsPage(this))
, _browserCredsPage(new OwncloudOAuthCredsPage) , _browserCredsPage(new OwncloudOAuthCredsPage)
, _flow2CredsPage(new Flow2AuthCredsPage)
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
, _shibbolethCredsPage(new OwncloudShibbolethCredsPage) , _shibbolethCredsPage(new OwncloudShibbolethCredsPage)
#endif #endif
@ -59,6 +61,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
setPage(WizardCommon::Page_ServerSetup, _setupPage); setPage(WizardCommon::Page_ServerSetup, _setupPage);
setPage(WizardCommon::Page_HttpCreds, _httpCredsPage); setPage(WizardCommon::Page_HttpCreds, _httpCredsPage);
setPage(WizardCommon::Page_OAuthCreds, _browserCredsPage); setPage(WizardCommon::Page_OAuthCreds, _browserCredsPage);
setPage(WizardCommon::Page_Flow2AuthCreds, _flow2CredsPage);
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage); setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage);
#endif #endif
@ -76,6 +79,7 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
connect(_setupPage, &OwncloudSetupPage::determineAuthType, this, &OwncloudWizard::determineAuthType); connect(_setupPage, &OwncloudSetupPage::determineAuthType, this, &OwncloudWizard::determineAuthType);
connect(_httpCredsPage, &OwncloudHttpCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl); connect(_httpCredsPage, &OwncloudHttpCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
connect(_browserCredsPage, &OwncloudOAuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl); connect(_browserCredsPage, &OwncloudOAuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
connect(_flow2CredsPage, &Flow2AuthCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
connect(_shibbolethCredsPage, &OwncloudShibbolethCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl); connect(_shibbolethCredsPage, &OwncloudShibbolethCredsPage::connectToOCUrl, this, &OwncloudWizard::connectToOCUrl);
#endif #endif
@ -129,11 +133,13 @@ QString OwncloudWizard::ocUrl() const
return url; return url;
} }
bool OwncloudWizard::registration() { bool OwncloudWizard::registration()
{
return _registration; return _registration;
} }
void OwncloudWizard::setRegistration(bool registration) { void OwncloudWizard::setRegistration(bool registration)
{
_registration = registration; _registration = registration;
} }
@ -162,6 +168,10 @@ void OwncloudWizard::successfulStep()
_browserCredsPage->setConnected(); _browserCredsPage->setConnected();
break; break;
case WizardCommon::Page_Flow2AuthCreds:
_flow2CredsPage->setConnected();
break;
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
case WizardCommon::Page_ShibbolethCreds: case WizardCommon::Page_ShibbolethCreds:
_shibbolethCredsPage->setConnected(); _shibbolethCredsPage->setConnected();
@ -195,6 +205,8 @@ void OwncloudWizard::setAuthType(DetermineAuthTypeJob::AuthType type)
#endif #endif
if (type == DetermineAuthTypeJob::OAuth) { if (type == DetermineAuthTypeJob::OAuth) {
_credentialsPage = _browserCredsPage; _credentialsPage = _browserCredsPage;
} else if (type == DetermineAuthTypeJob::LoginFlowV2) {
_credentialsPage = _flow2CredsPage;
} else if (type == DetermineAuthTypeJob::WebViewFlow) { } else if (type == DetermineAuthTypeJob::WebViewFlow) {
_credentialsPage = _webViewPage; _credentialsPage = _webViewPage;
} else { // try Basic auth even for "Unknown" } else { // try Basic auth even for "Unknown"
@ -221,7 +233,7 @@ void OwncloudWizard::slotCurrentPageChanged(int id)
} }
setOption(QWizard::HaveCustomButton1, id == WizardCommon::Page_AdvancedSetup); setOption(QWizard::HaveCustomButton1, id == WizardCommon::Page_AdvancedSetup);
if (id == WizardCommon::Page_AdvancedSetup && _credentialsPage == _browserCredsPage) { if (id == WizardCommon::Page_AdvancedSetup && (_credentialsPage == _browserCredsPage || _credentialsPage == _flow2CredsPage)) {
// For OAuth, disable the back button in the Page_AdvancedSetup because we don't want // For OAuth, disable the back button in the Page_AdvancedSetup because we don't want
// to re-open the browser. // to re-open the browser.
button(QWizard::BackButton)->setEnabled(false); button(QWizard::BackButton)->setEnabled(false);

View File

@ -40,6 +40,7 @@ class OwncloudWizardResultPage;
class AbstractCredentials; class AbstractCredentials;
class AbstractCredentialsWizardPage; class AbstractCredentialsWizardPage;
class WebViewPage; class WebViewPage;
class Flow2AuthCredsPage;
/** /**
* @brief The OwncloudWizard class * @brief The OwncloudWizard class
@ -77,6 +78,7 @@ public:
// Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage // Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage
QSslKey _clientSslKey; QSslKey _clientSslKey;
QSslCertificate _clientSslCertificate; QSslCertificate _clientSslCertificate;
QList<QSslCertificate> _clientSslCaCertificates;
public slots: public slots:
void setAuthType(DetermineAuthTypeJob::AuthType type); void setAuthType(DetermineAuthTypeJob::AuthType type);
@ -103,6 +105,7 @@ private:
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH
OwncloudShibbolethCredsPage *_shibbolethCredsPage; OwncloudShibbolethCredsPage *_shibbolethCredsPage;
#endif #endif
Flow2AuthCredsPage *_flow2CredsPage;
OwncloudAdvancedSetupPage *_advancedSetupPage; OwncloudAdvancedSetupPage *_advancedSetupPage;
OwncloudWizardResultPage *_resultPage; OwncloudWizardResultPage *_resultPage;
AbstractCredentialsWizardPage *_credentialsPage; AbstractCredentialsWizardPage *_credentialsPage;

View File

@ -38,6 +38,7 @@ namespace WizardCommon {
Page_HttpCreds, Page_HttpCreds,
Page_ShibbolethCreds, Page_ShibbolethCreds,
Page_OAuthCreds, Page_OAuthCreds,
Page_Flow2AuthCreds,
Page_WebView, Page_WebView,
Page_AdvancedSetup, Page_AdvancedSetup,
Page_Result Page_Result

View File

@ -11,13 +11,13 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QLabel" name="topLabel"> <widget class="QLabel" name="topLabel">
<property name="text"> <property name="text">
<string>TextLabel</string> <string notr="true">TextLabel</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@ -95,7 +95,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>PushButton</string> <string notr="true">PushButton</string>
</property> </property>
<property name="toolButtonStyle"> <property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum> <enum>Qt::ToolButtonTextUnderIcon</enum>
@ -117,7 +117,7 @@
</size> </size>
</property> </property>
<property name="text"> <property name="text">
<string>PushButton</string> <string notr="true">PushButton</string>
</property> </property>
<property name="toolButtonStyle"> <property name="toolButtonStyle">
<enum>Qt::ToolButtonTextUnderIcon</enum> <enum>Qt::ToolButtonTextUnderIcon</enum>

View File

@ -136,7 +136,7 @@ WebViewPageUrlSchemeHandler::WebViewPageUrlSchemeHandler(QObject *parent)
void WebViewPageUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *request) { void WebViewPageUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *request) {
QUrl url = request->requestUrl(); QUrl url = request->requestUrl();
QString path = url.path().mid(1); QString path = url.path(0).mid(1); // get undecoded path
QStringList parts = path.split("&"); QStringList parts = path.split("&");
QString server; QString server;
@ -153,12 +153,14 @@ void WebViewPageUrlSchemeHandler::requestStarted(QWebEngineUrlRequestJob *reques
} }
} }
user = QUrl::fromPercentEncoding(user.toUtf8()); qCDebug(lcWizardWebiew()) << "Got raw user from request path: " << user;
password = QUrl::fromPercentEncoding(password.toUtf8());
user = user.replace(QChar('+'), QChar(' ')); user = user.replace(QChar('+'), QChar(' '));
password = password.replace(QChar('+'), QChar(' ')); password = password.replace(QChar('+'), QChar(' '));
user = QUrl::fromPercentEncoding(user.toUtf8());
password = QUrl::fromPercentEncoding(password.toUtf8());
if (!server.startsWith("http://") && !server.startsWith("https://")) { if (!server.startsWith("http://") && !server.startsWith("https://")) {
server = "https://" + server; server = "https://" + server;
} }
@ -197,7 +199,7 @@ bool WebEnginePage::certificateError(const QWebEngineCertificateError &certifica
*/ */
QMessageBox messageBox; QMessageBox messageBox;
messageBox.setText(tr("Invalid certificate detected")); messageBox.setText(tr("Invalid certificate detected"));
messageBox.setInformativeText(tr("The host \"%1\" provided an invalid certitiface. Continue?").arg(certificateError.url().host())); messageBox.setInformativeText(tr("The host \"%1\" provided an invalid certificate. Continue?").arg(certificateError.url().host()));
messageBox.setIcon(QMessageBox::Warning); messageBox.setIcon(QMessageBox::Warning);
messageBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No); messageBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No);
messageBox.setDefaultButton(QMessageBox::No); messageBox.setDefaultButton(QMessageBox::No);

View File

@ -23,7 +23,7 @@
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string notr="true">Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="leftMargin"> <property name="leftMargin">

View File

@ -958,6 +958,7 @@ void ClientSideEncryption::encryptPrivateKey()
{ {
QStringList list = WordList::getRandomWords(12); QStringList list = WordList::getRandomWords(12);
_mnemonic = list.join(' '); _mnemonic = list.join(' ');
_newMnemonicGenerated = true;
qCInfo(lcCse()) << "mnemonic Generated:" << _mnemonic; qCInfo(lcCse()) << "mnemonic Generated:" << _mnemonic;
emit mnemonicGenerated(_mnemonic); emit mnemonicGenerated(_mnemonic);
@ -989,6 +990,11 @@ void ClientSideEncryption::encryptPrivateKey()
job->start(); job->start();
} }
bool ClientSideEncryption::newMnemonicGenerated() const
{
return _newMnemonicGenerated;
}
void ClientSideEncryption::decryptPrivateKey(const QByteArray &key) { void ClientSideEncryption::decryptPrivateKey(const QByteArray &key) {
QString msg = tr("Please enter your end to end encryption passphrase:<br>" QString msg = tr("Please enter your end to end encryption passphrase:<br>"
"<br>" "<br>"

View File

@ -87,6 +87,8 @@ public:
void forgetSensitiveData(); void forgetSensitiveData();
bool newMnemonicGenerated() const;
public slots: public slots:
void slotRequestMnemonic(); void slotRequestMnemonic();
@ -127,6 +129,7 @@ public:
QSslKey _publicKey; QSslKey _publicKey;
QSslCertificate _certificate; QSslCertificate _certificate;
QString _mnemonic; QString _mnemonic;
bool _newMnemonicGenerated = false;
}; };
/* Generates the Metadata for the folder */ /* Generates the Metadata for the folder */

View File

@ -37,7 +37,7 @@
#include <QNetworkProxy> #include <QNetworkProxy>
#include <QStandardPaths> #include <QStandardPaths>
#define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds #define DEFAULT_REMOTE_POLL_INTERVAL 5000 // default remote poll time in milliseconds
#define DEFAULT_MAX_LOG_LINES 20000 #define DEFAULT_MAX_LOG_LINES 20000
namespace OCC { namespace OCC {

View File

@ -173,7 +173,7 @@ void HttpCredentials::fetchFromKeychain()
fetchUser(); fetchUser();
if (!_ready && !_refreshToken.isEmpty()) { if (!_ready && !_refreshToken.isEmpty()) {
// This happens if the credentials are still loaded from the keychain, bur we are called // This happens if the credentials are still loaded from the keychain, but we are called
// here because the auth is invalid, so this means we simply need to refresh the credentials // here because the auth is invalid, so this means we simply need to refresh the credentials
refreshAccessToken(); refreshAccessToken();
return; return;

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