1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2026-04-04 20:34:17 +02:00

Compare commits

...

98 Commits

Author SHA1 Message Date
Michael Schuster
1d745535f7 KeychainChunk: Fix error handling in ReadJob::slotReadJobDone
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 12:13:26 +01:00
Michael Schuster
3184aeed43 Add temporary Flow2 translation (german)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 11:06:17 +01:00
Michael Schuster
3faf010b55 Update translations
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:18:57 +01:00
Michael Schuster
163c80e203 Fix date in ActivityWidget and remove unnecessary string conversion
The local date and time value was converted into a string, just to be converted
into another string, to be converted to a value once again, returning zero as
the result. This caused the widget to always display "now".

Looks like this was a simply copy and paste mistake from this line in
ActivityListModel::slotActivitiesReceived:

a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate);

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit e07859fb3c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:52 +01:00
Michael Schuster
73462e97aa Heavy refactoring: Windows workaround for >= 4k (4096 bit) client-cert SSL keys and large certs
With QtKeychain on Windows, storing larger keys or certs in one keychain entry causes the
following error due to limits in the Windows APIs:
    Error: "Credential size exceeds maximum size of 2560"

This fix implements the new wrapper class KeychainChunk with wrapper jobs ReadJob and WriteJob
to encapsulate the QKeychain handling of ReadPasswordJob and WritePasswordJob with binaryData
but split every supplied keychain entry's data into 2048 byte chunks, on Windows only.

The wrapper is used for all keychain operations in WebFlowCredentials, except for the server password.

All finished keychain jobs now get deleted properly, to avoid memory leaks.

For reference also see previous fixes:
- https://github.com/nextcloud/desktop/pull/1389
- https://github.com/nextcloud/desktop/pull/1394

This should finally fix the re-opened issue:
- https://github.com/nextcloud/desktop/issues/863

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 9b034a2eb0)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:52 +01:00
Michael Schuster
6f4144a464 Flow2AuthWidget: Minor fixes and improvements
- Improve status messages

- Add a counter to make sure that "Link copied to clipboard." is visible for
  three seconds and to not enable the buttons too early

- Add more space between buttons and status

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit bd9652b24c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:51 +01:00
Michael Schuster
9820464545 Flow2: Refactor UI into Flow2AuthWidget only and improve Flow2Auth
- Flow2AuthCredsPage:
  - Remove .ui file and embed Flow2AuthWidget into layout

- Flow2AuthWidget:
  - Make use generic for Flow2AuthCredsPage and WebFlowCredentialsDialog
  - Fix _errorLabel to render HTML tags instead of dumping them as plain text

- Flow2Auth:
  - Explicitly start auth with startAuth(account) instead of using constructor
  - Take control of copying the auth link to clipboard
  - Request a new auth link on copying, to avoid expiry invalidation
  - Use signals statusChanged() and result() to be more verbose (status, errors)
  - Change timer invocation and add safety bool's to avoid weird behaviour when
    the user triggers multiple link-copy calls (fetchNewToken)

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 8b5f09305c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:51 +01:00
Michael Schuster
3ecd7823f9 Add new HeaderBanner class for WebFlowCredentialsDialog
New widget on top of the layout, based on Qt's own modern wizard header banner.

This should improve the user's perception of the dialog.

Encapsulate the existing layout into a container layout to allow the banner taking
the full width of the dialog.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 6d033f2964)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:51 +01:00
Michael Schuster
9b504eaddd Make WebFlowCredentialsDialog cancellation- and deletion-safe
- Add new signal to let WebFlowCredentials know and emit asked() to also
  tell AccountState that the user won't authenticate, and triggering
  log-out state in the settings window.

- Use deleteLater() to safely delete WebFlowCredentialsDialog, so
  that Qt can free it at the right time and without crashes.
  Do the same with it's _webView and _flow2AuthWidget on closeEvent().

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 0bcac1882a)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:51 +01:00
Michael Schuster
838ca6cba0 WebFlowCredentialsDialog: Bring re-auth dialog to top (raise) on error
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b6b04aeff8)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:51 +01:00
Michael Schuster
7e5e40d5c4 Flow2: Make ProgressIndicator's background-aware (Dark-/Light-Mode switching)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit a69aed80e6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
abd8d1fda1 Flow2: Add poll status text, ProgressIndicator and countdown timer
Also enable / disable buttons during polling.

This aims to make the authentication status more transparent and should avoid the
impression that the client is perhaps doing nothing.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit e81f972270)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
0a3491f332 Small fixes and code cleanup
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 542590db7c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
15d9ca2b00 Flow2: Poll for re-auth result upon WebFlowCredentialsDialog window activation
Since the default remote poll interval has been re-raised recently to 30 seconds,
the delay between clicking "Grant access" in the browser and fetch and showing success
in the dialog may seem erroneous to the users and tempt them to click "Re-open browser"
again, causing the whole login process to restart.

This commit implements an event handler to pass the dialog's window activation
event down to the Login Flow v2 widget, in order to allow it to poll earlier.

See previous commits for dependent implementation details.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit e04aae94bc)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
7682749415 WebFlowCredentialsDialog: Bring re-auth dialog to top (raise) upon showing SettingsDialog
Purpose: The floating re-auth windows of the WebFlowCredentialsDialog often get hidden behind
the SettingsDialog, and the users have to minimize a lot of other windows to find them again.

See previous commit for dependent implementation details.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit aa18667905)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
a18fc5b5c9 Add helper slots and signals to catch SettingsDialog's window activation events
Signal the SettingsDialog's window activation event down to ownCloudGui and Application,
so that other classes can hook in to get notified when the SettingsDialog is being shown
again.

This approach has been chosen because we otherwise would have to deal with new instance
pointers of the current SettingsWindow - but Application is already there ;-)

Purpose: The floating re-auth windows of the WebFlowCredentialsDialog often get hidden
behind the SettingsDialog, and the users have to minimize a lot of other windows to find
them again. This commit implements the preparation for the upcoming fix commit.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit addb27a085)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:50 +01:00
Michael Schuster
96783a9b80 Flow2: Use ownCloudGui::raiseDialog to bring account setup wizard to top (raise) on auth result
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit be10d5200f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
258c2cee2e Flow2: Poll for auth result upon account setup wizard window activation
Since the default remote poll interval has been re-raised recently to 30 seconds,
the delay between clicking "Grant access" in the browser and fetch and showing success
in the wizard may seem erroneous to the users and tempt them to click "Re-open browser"
again, causing the whole login process to restart.

This commit implements an event handler to pass the wizard's window activation
event down to the Login Flow v2 page, in order to allow it to poll earlier.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit e8348612b4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
65ff3c0de1 Flow2: Bring account setup wizard to top (raise) on auth result
Show and raise the wizard on success / error in the Login Flow v2 auth.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 3a160a4dce)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
6879c9f1c9 Fix issue #1237: White text on almost-white background
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit ccd20f0172)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
fade8465e4 Fix folder opening in ActivityListModel
After fixing the crash in the previous commit, double-clicking on Activity list rows still didn't work.

This fix partly reverts commit 8546d53b05 in ActivityItemDelegate::PathRole
of ActivityListModel::data, but adds a new check for relPath's existence in line 74.

I'm assuming the previous change there has been done to shorten the code and avoid opening the user's home
folder upon clicking entries which file value is "App Password". The new path-check takes care of that too now.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit c03bc8540c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
76b5c6b6d4 Fix crash in ActivityListModel (fixes #1693)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 003acb7254)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:49 +01:00
Michael Schuster
bdd0cc4dc3 Show date and time in activity log (fixes issue #1683)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b961b683d6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:16:48 +01:00
Michael Schuster
876b1e239e Fix build (missing refactoring)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit a7dade979c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:53 +01:00
Michael Schuster
da2007c7f6 Mac and high-dpi displays: Add workaround in ActivityItemDelegate to show full uncropped activity's actionText and timeText
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 3a2caf61e5)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:53 +01:00
Michael Schuster
7a33cb97cd Make all ProgressIndicator's background-aware (Dark-/Light-Mode switching)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b5ed16088a)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:53 +01:00
Michael Schuster
7a18a58fae Make OwncloudWizard and its pages background-aware (Dark-/Light-Mode switching)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit e4a20b9e72)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:53 +01:00
Michael Schuster
fcc9d02bcc Remove unnecessary string translation and copy in ActivityItemDelegate
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 37e5fe786f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:52 +01:00
Michael Schuster
98238669fe Remove unnecessary breaks in ActivityListModel
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 643995528b)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:52 +01:00
Michael Schuster
a80de38517 Make ActivityItemDelegate background- and selection-aware (Dark-/Light-Mode switching)
Also implement cached member icons in ActivityListModel and return their enums to
ActivityItemDelegate instead of always recreating them for each call to paint().

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit ecd17f2ea2)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:52 +01:00
Michael Schuster
4cb75d91f9 Refactor ActivitySettings: Rename member variable ui to _ui
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit bf0bf2c1b6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:52 +01:00
Michael Schuster
55e4dceeb7 Make AccountSettings and ActivitySettings background-aware (Dark-/Light-Mode switching)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit acedf362b6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:52 +01:00
Michael Schuster
0006b35abf Change error link colour in AccountSettings::showConnectionLabel
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 3b580eeca7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:51 +01:00
Michael Schuster
b153391cbf Add new Theme helper method to custom-colourize links
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 6adfff1f13)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:51 +01:00
Michael Schuster
c0659a3124 Change Dark Mode link colour
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 7d542d7989)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-24 09:15:49 +01:00
Michael Schuster
de8a7aa680 Fix Activity List: Add check to avoid first empty entry
Add checks to ActivityListModel::combineActivityLists in order to avoid adding
empty Activity entries to the _finalList.

The previous implementation always added an empty entry to the top of the list because
_notificationIgnoredFiles was appended without checking (_listOfIgnoredFiles.size() > 0).

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit cc21d175f1)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-18 03:31:32 +01:00
Michael Schuster
03b11693a3 Fix build for stable-2.6 (changes from PR #1651)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 22:45:47 +01:00
Camila San
23f2d79f70 Bump version to 2.6.2 2019-12-09 21:51:13 +01:00
Michael Schuster
f6db365391 Update translations
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:48:20 +01:00
Michael Schuster
95f74ceb1f macOS: Rename 'Explorer' appropriately on non-Windows
- Rename it to 'Finder' on macOS.
- This way we also avoid having the whole string re-translated on Transifex.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b754eacd74)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:48 +01:00
Michael Schuster
91be4961d7 Make the ShareDialog background-aware (Dark-/Light-Mode switching)
Use customizeStyle() to change link colours, icons and pixmaps in the ShareDialog and notify
it's widgets via slots.

TODO - known issue (macOS):
- The background and font colours in the ShareUserLine widget still stay the same.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 7ce8a6a201)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:48 +01:00
Michael Schuster
b3eb16bfd3 Make SettingsDialog background-aware (Dark-/Light-Mode switching)
Use customizeStyle() to change link colours in the SettingsDialog and notify it's widgets via slots.

Also modify the background colour of the errors messages in AccountSettings::showConnectionLabel
to always use an appropiate colour for it's custom-defined background.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 789a2a7ae3)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:47 +01:00
Michael Schuster
52d91ce198 Implement and move some colour-aware helper methods into the Theme class
This introduces a new method to change the colours in the links in QLabel's.
Utilizes a custom crafted RegEx function to replace already-coloured links.

Moved code is based on stuff from the SettingsDialog class.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit be4fc6b887)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:47 +01:00
Michael Schuster
c49efcc137 Pick from upstream: Fix too low contrast when tab is selected
The low contrast is also a problem with the Dark Mode on macOS.

For reference please see:
- Commit: 413ef5e96d
- Issue: https://github.com/owncloud/client/issues/7512

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 2dae31486a)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:47 +01:00
Michael Schuster
c14556153f Fix SSL-button's info background color for Dark Mode
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 71f66c6229)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:47 +01:00
Michael Schuster
5e1a2a423f Remove submodule qtmacgoodies and the MacSettingsDialog class
Reverts back to the SettingsDialog class because of bugs and glitches with
more recent Qt versions (Qt 5.12) and with the macOS Dark Mode.

See upstream: https://github.com/owncloud/client/pull/7492

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b4f926ded7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-09 21:46:44 +01:00
Michael Schuster
3ac1ba079a Fix deleteKeychainEntries: Stay consistent with job->setInsecureFallback(false)
We never fall back to the insecure variant in the whole codebase, so don't do it here.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 5869b93acb)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:30 +01:00
Michael Schuster
b7a9cd1d45 Fix Windows key-chunk deletion (too paranoid)
Clear the key chunk buffer, but don't set _clientSslKeyChunkCount to zero because we need it later for deleteKeychainEntries

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit b15eb27aa9)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:29 +01:00
Michael Schuster
c7b0ce036d Fix member variable name from last refactoring (Windows code)
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 336b64a569)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:29 +01:00
Michael Schuster
f5afa07b0a Fix Remote Wipe keychain storage
In certain cases don't write the app password in Account::writeAppPasswordOnce:
- id() is empty: This always happend once the Account Wizard showed the folder selection
- appPassword is empty: Caused by Logout -> Relaunch, preventing remote wipe on relaunch

Implement some logging to ease debugging in the future.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 6a49e787bb)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:29 +01:00
Michael Schuster
1b550b976f Remote Wipe: Implement hack to allow deleting all keychain entries (SSL certs & keys)
Client SSL certificates and keys cannot be deleted at this time because there is
no UI for selecting them on re-login.

We introduce this dirty hack here, to allow deleting them upon Remote Wipe.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 0c5f4a1525)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:29 +01:00
Michael Schuster
68f0b9e0aa Refactoring: Windows workaround for >= 4k (4096 bit) client-cert SSL keys
WebFlowCredentials:
- Remove _clientSslCaKeyWriteQueue and simply use _clientSslKeyChunkBufferPEM
- Store key's sub-chunks in slots with "." (dot) suffix
- Implement deletion of the key chunks in WebFlowCredentials::deleteKeychainEntries
- Remove spaces in log messages
- Improve code readability

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 6ef9f3cc26)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:29 +01:00
Michael Schuster
8945ef2652 Windows: Workaround for storing >= 4k (4096 bit) client-cert SSL keys
With QtKeychain on Windows, storing larger keys in one keychain entry causes the
following error due to limits in the Windows APIs:
  Error: "Credential size exceeds maximum size of 2560"

To avoid overhead on the other platforms and balance code duplication, this
approach puts some read- and write-parts into Windows-only defines.

For reference also see previous fixes:
- https://github.com/nextcloud/desktop/pull/1389
- https://github.com/nextcloud/desktop/pull/1394

This (again) fixes the re-opened issue:
- https://github.com/nextcloud/desktop/issues/863

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 72be80cbd9)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-08 03:10:26 +01:00
Felix Eckhofer
0cc74d21a2 Use user-provided username in displayName()
This replaces `davUser()`, which is replaced by a numeric ID when using
LDAP, by the username that was actually used for logging in.

Fixes #836

Signed-off-by: Felix Eckhofer <felix@eckhofer.com>
(cherry picked from commit ace142bb23)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 22:28:53 +01:00
Felix Eckhofer
f4c79c8f68 Fix typo
Signed-off-by: Felix Eckhofer <felix@eckhofer.com>
(cherry picked from commit 28da954aad)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 22:24:20 +01:00
Joas Schilling
b6ef8edb12 Use … instead of 3 dots
Signed-off-by: Joas Schilling <coding@schilljs.com>
(cherry picked from commit a4fa8b05e5065da62bb7f6d3cc8c371a2a68ed06)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 21:26:23 +01:00
Joas Schilling
06ffd3e841 Fix some translations
Signed-off-by: Joas Schilling <coding@schilljs.com>
(cherry picked from commit 862342872d9685c8be89406182d471d246c2f9a7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 21:26:15 +01:00
Joas Schilling
a3596b80e9 Improve the translation of "Share via …"
Signed-off-by: Joas Schilling <coding@schilljs.com>
(cherry picked from commit 98b7d2ebc00ceda1ff74e2adad3b340193e7eb17)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 21:25:43 +01:00
Michael Schuster
8ac1d92161 Fall back to old login flow on GS as this is not yet ready (#2: re-auth)
This commit ensures that the check also occurs on re-authorization in case
the user gets logged out.

See: https://github.com/nextcloud/desktop/pull/1644

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 877fd7abb9)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 21:10:13 +01:00
Roeland Jago Douma
d55f1d5c06 Fall back to old login flow on GS as this is not yet ready
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
(cherry picked from commit 363e62f8fa)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 21:10:02 +01:00
David Kahles
854c637c73 Compare QDateTime objects more efficient
There is no need to call toMSecsSinceEpoch() as QDateTime implements an
comparison operator itself. This is more efficient, because the
QDateTime comparison operator doesn't call localtime() in all cases. Thus, we
don't read /etc/localtime for every comparison. This improves
performance in some cases.

Signed-off-by: David Kahles <david.kahles96@gmail.com>
(cherry picked from commit 9a3aa55b29)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-06 16:20:27 +01:00
István Váradi
92ad0c4a43 Add libdbus-1-dev to the build dependencies
Signed-off-by: István Váradi <ivaradi@varadiistvan.hu>
(cherry picked from commit 805b85a4d7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:27:44 +01:00
Corentin Noël
fb8facb787 Build with libcloudproviders on debian and in the AppImage
Signed-off-by: Corentin Noël <corentin.noel@collabora.com>
(cherry picked from commit 04dd02c295)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:27:02 +01:00
Michael Schuster
30832cc427 MSVC Fix for PR #1526: Fix clang's variadic macro warnings
The recently merged #1526 caused the MSVC Builds on Windows to fail.

This patch sets the new CMake flag only if the compiler it not MSVC.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit a12164ad53)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:21:13 +01:00
Johannes Lorenz
ab7f29c0b2 Fix clang's variadic macro warnings
Using variadic macros like `qCWarning()` without any parameter for "..."
is a GNU extension, which causes a lot of `clang` warnings:

```
desktop/src/common/ownsql.cpp:74:24: warning: must specify at least one
 argument for '...' parameter of variadic macro
 [-Wgnu-zero-variadic-macro-arguments]
        qCWarning(lcSql) << "Error:" << _error << "for" << filename;
                       ^
/usr/include/qt/QtCore/qloggingcategory.h:140:11: note: macro 'qCWarning'
 defined here
          ^
```

This patch tells `clang` to be silent.

Signed-off-by: Johannes Lorenz <j.git@lorenz-ho.me>
(cherry picked from commit 8c334a1f43)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:20:56 +01:00
Camila San
6500442217 Merge the list of ignored files/symlinks into one Activity notification.
Signed-off-by: Camila San <hello@camila.codes>
(cherry picked from commit 8546d53b05)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:12:11 +01:00
Dominique Fuchs
0e6a9fd0c1 Don't need a second if for non-Windows, using Qt macro for platform check
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 7750d2198d)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:05:06 +01:00
Dominique Fuchs
ed7c489722 Fixed typo preventing successful path rename on Win and modified ASSERT for Window path styles
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit f993e7c555)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-12-05 20:04:25 +01:00
Michael Schuster
d4b9b5129a Fix copyright year in MacOSXBundleInfo.plist.in for 2019
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 407864c40e)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-29 17:27:15 +01:00
Michael Schuster
4a79e24db2 Use ReadPasswordJob::finished for ReadPasswordJob
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 76c7ab499f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-29 15:59:33 +01:00
Michael Schuster
215d185fbe Fix remote wipe keychain storage (issue #1592)
The app password for the remote wipe was constantly being written in
WebFlowCredentials::slotFinished to the keychain, leading to unnecessary
write and log overhead on the system.

This fix introduces a check to only store the app password once in
a lifetime of the Account class. Also the method used to store the
password will be renamed from setAppPassword to writeAppPasswordOnce
to be more expressive.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit dcc84d3508)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-29 15:59:30 +01:00
Andreas Wunderlich
6f273bf7dd Revert default remote poll interval back from 5 seconds to 30 seconds
Signed-off-by: Andreas Wunderlich <code@andwun.me>
(cherry picked from commit f293dbfeeb)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-28 18:08:22 +01:00
Michael Schuster
f2bc25c9ca macOS build: Avoid the Get-Task-Allow Entitlement (Notarization)
Setting CODE_SIGN_INJECT_BASE_ENTITLEMENTS to NO is required for macOS
Notarization.

See: https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/resolving_common_notarization_issues

And upstream: 97f4af32ae

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-27 15:19:41 +01:00
István Váradi
ca6de6128f Build for Debian stable and oldstable
Signed-off-by: István Váradi <ivaradi@varadiistvan.hu>
(cherry picked from commit 2fc76c2b24)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-27 15:09:50 +01:00
Christoph Wurst
a1e5e6ca40 Fix legal notice year
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
(cherry picked from commit 923abd8d0b)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-27 15:09:38 +01:00
Michael Schuster
cf8bb1c5bc Merge pull request #1622 from nextcloud/backport/1621/stable-2.6
[stable-2.6] Add timestamp to Mac installer code signing
2019-11-17 20:35:16 +01:00
Michael Schuster
ebe1cef357 Add timestamp to Mac installer code signing
Use the --timestamp option for 'productsign' to add a secure timestamp.

See: https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution/resolving_common_notarization_issues

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 19:33:56 +00:00
Michael Schuster
922d14f016 Fix: add /usr/local/lib to LD_LIBRARY_PATH for OpenSSL 1.1.1
Drone pipeline qt-5.12 failed because the unit tests didn't find OpenSSL.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit fce0a50e37)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 03:23:45 +01:00
Michael Schuster
7a0c6a2f8f Fix: escape env vars
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit c047232c3b)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 03:23:42 +01:00
Michael Schuster
f35a2c0b2c Bump Qt 5.12.5 image
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit a846f0276d)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 03:23:37 +01:00
Michael Schuster
c6b22089e3 Fix dir in upload script
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 801098f546)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 02:17:02 +01:00
Michael Schuster
cf0082f92f Make AppImage upload optional and add timeout
Moves the upload into a separate Drone command and adds a timeout for curl
to fail after 15 minutes.

Returns zero to keep Drone from failing.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 4fc8936553)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 02:17:00 +01:00
Michael Schuster
f4e129d4e2 Move .desktop file path to new env var to avoid duplicates
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit dd0135ce2e)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 02:16:55 +01:00
Michael Schuster
ec6eaf2121 Upgrade for Qt 5.12.5 in docker-ci
- Use the new image: nextcloudci/client-5.12:client-5.12-5
- Use it's new QT_BASE_DIR: /opt/qt5.12.5

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit d830a1c5f7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-17 02:13:03 +01:00
Michael Schuster
1dc1f91620 Fix: Apply http2 patch from owncloud #1573 - only with Qt >= 5.8
Drone builds failed with Qt 5.7 and we introduce a new ifdef here
to avoid patching specifically for Ubuntu Xenial only.

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 5131463644)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-14 03:33:49 +01:00
Adrian Brzezinski
5adbc01ef1 * fix for issue no. 1351 2019-11-04 22:48:42 +01:00
Sergey Zolotarev
c4a04bfd05 Don't run connection wizard when quitting the application
Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-11-04 22:40:18 +01:00
Sergey Zolotarev
03711429f4 Replace isQuitting flag with disconnect()
Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-11-04 22:40:07 +01:00
Michael Schuster
e3cb040c3c Update submodules for Qt 5.12.5 (qtmacgoodies)
Fetch the new submodule commits to get these fixes:
- Merge upstream: Retrieve the associated NSView more reliably: https://github.com/camilasan/qtmacgoodies/pull/1
- Bugfix for Qt 5.12.5 - Redraw the ToolBar: https://github.com/camilasan/qtmacgoodies/pull/2

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-04 22:38:21 +01:00
Camila San
83a21179fa Update translation files.
Signed-off-by: Camila San <hello@camila.codes>
2019-11-04 20:54:47 +01:00
XNG
829da85aa5 Apply http2 patch from owncloud
Signed-off-by: XNG <Milokita@users.noreply.github.com>
2019-11-04 20:22:31 +01:00
Camila San
3b1ac89312 Fix remote wipe when a proxy is configured.
Signed-off-by: Camila San <hello@camila.codes>
2019-11-04 20:20:00 +01:00
Michael Schuster
57df29e7c9 Merge pull request #1582 from nextcloud/backport/1580/stable-2.6
[stable-2.6] Fix updater message: Download link instead of "use the system's updat…
2019-11-04 19:58:08 +01:00
Michael Schuster
d774067004 Fix updater message: Download link instead of "use the system's update tool"
Provide a download link to the new version instead of the confusing message that
users should use their "system's update tool to install it".

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-11-04 18:56:20 +00:00
Camila San
728154386c Once client gets 401/403 from the server, check if remote wipe was requested.
- When the the users logs because of 401 or 403 errors, it checks if the
server requested the remote wipe. If yes, locally deletes account and folders
connected to the account and notify the server. If no, proceeds to ask the
user to login again.
- The app password is restored in the keychain.
- WIP: The change also includes a test class for RemoteWipe.

Signed-off-by: Camila San <hello@camila.codes>
2019-11-04 19:31:17 +01:00
tuxmaster5000
bc31182c63 Rename owncloud tests to nextcloud 2019-11-04 19:31:07 +01:00
Michael Schuster
a6bb84080a Merge pull request #1555 from nextcloud/backport/1554/stable-2.6
[stable-2.6] Fix duplicate items in Apps menu (a bug introduced in #1477)
2019-10-23 23:49:01 +02:00
Sergey Zolotarev
576ba7c011 Fix duplicate items in Apps menu (a bug introduced in #1477)
Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-10-23 21:46:35 +00:00
167 changed files with 37183 additions and 28412 deletions

View File

@@ -237,13 +237,17 @@ name: qt-5.12
steps:
- name: build and test
image: nextcloudci/client-5.12:client-5.12-2
image: nextcloudci/client-5.12:client-5.12-5
commands:
# Install QtKeyChain
- /bin/bash -c "
export CC=gcc-7 &&
export CXX=g++-7 &&
source /opt/qt512/bin/qt512-env.sh &&
export QT_BASE_DIR=/opt/qt5.12.5 &&
export QTDIR=\$QT_BASE_DIR &&
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain &&
@@ -257,7 +261,11 @@ steps:
- /bin/bash -c "
export CC=gcc-7 &&
export CXX=g++-7 &&
source /opt/qt512/bin/qt512-env.sh &&
export QT_BASE_DIR=/opt/qt5.12.5 &&
export QTDIR=\$QT_BASE_DIR &&
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
@@ -278,13 +286,17 @@ name: qt-5.12-clang
steps:
- name: build and test
image: nextcloudci/client-5.12:client-5.12-2
image: nextcloudci/client-5.12:client-5.12-5
commands:
# Install QtKeyChain
- /bin/bash -c "
export CC=clang-6.0 &&
export CXX=clang++-6.0 &&
source /opt/qt512/bin/qt512-env.sh &&
export QT_BASE_DIR=/opt/qt5.12.5 &&
export QTDIR=\$QT_BASE_DIR &&
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
cd /tmp &&
git clone https://github.com/frankosterfeld/qtkeychain.git &&
cd qtkeychain &&
@@ -298,7 +310,11 @@ steps:
- /bin/bash -c "
export CC=clang-6.0 &&
export CXX=clang++-6.0 &&
source /opt/qt512/bin/qt512-env.sh &&
export QT_BASE_DIR=/opt/qt5.12.5 &&
export QTDIR=\$QT_BASE_DIR &&
export PATH=\$QT_BASE_DIR/bin:\$PATH &&
export LD_LIBRARY_PATH=\$QT_BASE_DIR/lib/x86_64-linux-gnu:\$QT_BASE_DIR/lib:/usr/local/lib:\$LD_LIBRARY_PATH &&
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
@@ -319,9 +335,10 @@ name: AppImage
steps:
- name: build
image: nextcloudci/client-5.12:client-5.12-2
image: nextcloudci/client-5.12:client-5.12-5
commands:
- /bin/bash -c "./admin/linux/build-appimage.sh"
- /bin/bash -c "./admin/linux/upload-appimage.sh"
trigger:
branch:
- master

3
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "src/3rdparty/qtmacgoodies"]
path = src/3rdparty/qtmacgoodies
url = https://github.com/camilasan/qtmacgoodies.git
[submodule "binary"]
path = binary
url = git://github.com/owncloud/owncloud-client-binary.git

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[cy_GB]=@APPLICATION_ICON_NAME@
Name[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
Comment[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
GenericName[cy_GB]=Cydweddu Ffolder

View File

@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[de_DE]=@APPLICATION_ICON_NAME@
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
GenericName[de_DE]=Synchronisationsordner
Icon[de]=@APPLICATION_ICON_NAME@
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
GenericName[de]=Synchronisationsordner

View File

@@ -198,4 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[el]=@APPLICATION_ICON_NAME@
Name[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
Comment[el]=@APPLICATION_NAME@ πρόγραμμα συγχρονισμού
GenericName[el]=Συγχρονισμός φακέλου

View File

@@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
Icon[ro]=@APPLICATION_ICON_NAME@
Name[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
Comment[ro]=@APPLICATION_NAME@ client de sincronizare pentru desktop
GenericName[ro]=Sincronizare dosare
GenericName[ro]=Sincronizare director

View File

@@ -201,4 +201,4 @@ X-GNOME-Autostart-Delay=3
Icon[sk_SK]=@APPLICATION_ICON_NAME@
Name[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
Comment[sk_SK]=@APPLICATION_NAME@ synchronizačný klient pre PC
GenericName[sk_SK]=Synchnonizácia priečinkov
GenericName[sk_SK]=Synchronizácia priečinkov

View File

@@ -199,6 +199,6 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[sl]=@APPLICATION_ICON_NAME@
Name[sl]=@APPLICATION_NAME@ odjemalec za usklajevanje
Comment[sl]=@APPLICATION_NAME@ odjemalec za usklajevanje
Name[sl]=@APPLICATION_NAME@ program za usklajevanje
Comment[sl]=@APPLICATION_NAME@ program za usklajevanje
GenericName[sl]=Usklajevanje map

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[sw]=@APPLICATION_ICON_NAME@
Name[sw]=Teja ya @APPLICATION_NAME@ ya kufanana faili kwa seva na faili ziko hapa
Comment[sw]=Teja ya @APPLICATION_NAME@ ya kufanana faili kwa seva na faili ziko hapa
GenericName[sw]=Fanana Kabrasha

View File

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

View File

@@ -14,6 +14,7 @@ RUN apt-get update -q && DEBIAN_FRONTEND=noninteractive apt-get install -q -y --
libsqlite3-dev \
libssl-dev \
libcmocka-dev \
libcloudproviders-dev \
qt5-default \
qttools5-dev-tools \
libqt5webkit5-dev \

View File

@@ -6,7 +6,7 @@ mkdir /app
mkdir /build
#Set Qt-5.12
export QT_BASE_DIR=/opt/qt512
export QT_BASE_DIR=/opt/qt5.12.5
export QTDIR=$QT_BASE_DIR
export PATH=$QT_BASE_DIR/bin:$PATH
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
@@ -66,7 +66,8 @@ rm -rf ./usr/share/nemo-python/
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
rm -rf ./etc
sed -i -e 's|Icon=nextcloud|Icon=Nextcloud|g' usr/share/applications/nextcloud.desktop # Bug in desktop file?
DESKTOP_FILE=/app/usr/share/applications/${LINUX_APPLICATION_ID}.desktop
sed -i -e 's|Icon=nextcloud|Icon=Nextcloud|g' ${DESKTOP_FILE} # Bug in desktop file?
cp ./usr/share/icons/hicolor/512x512/apps/Nextcloud.png . # Workaround for linuxeployqt bug, FIXME
@@ -87,17 +88,12 @@ chmod a+x linuxdeployqt*.AppImage
rm ./linuxdeployqt-continuous-x86_64.AppImage
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/app/usr/lib/
./squashfs-root/AppRun /app/usr/share/applications/nextcloud.desktop -bundle-non-qt-libs
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs
# Set origin
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/libnextcloudsync.so.0
# Build AppImage
./squashfs-root/AppRun /app/usr/share/applications/nextcloud.desktop -appimage
./squashfs-root/AppRun ${DESKTOP_FILE} -appimage
mv Nextcloud*.AppImage Nextcloud-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
curl --upload-file $(readlink -f ./Nextcloud*.AppImage) https://transfer.sh/Nextcloud-${SUFFIX}-${DRONE_COMMIT}-x86_64.AppImage
echo
echo "Get the AppImage at the link above!"

View File

@@ -9,6 +9,8 @@ Build-Depends: cmake,
extra-cmake-modules (>= 5.16),
libkf5kio-dev,
libcmocka-dev,
libcloudproviders-dev,
libdbus-1-dev,
libhttp-dav-perl,
libinotify-dev [kfreebsd-any],
libqt5svg5-dev,

View File

@@ -0,0 +1,48 @@
nextcloud-client (2.3.3-1.0~oldstable1) oldstable; urgency=medium
* Debian build support for the forked client.
-- István Váradi <ivaradi@varadiistvan.hu> Mon, 6 Nov 2017 20:20:04 +0100
nextcloud-client (2.3.1-1.0) oldstable; urgency=medium
* New upstream version
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 23 Mar 2017 19:07:36 +0100
nextcloud-client (2.3.0-1.0) oldstable; urgency=medium
* New upstream version
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 21 Mar 2017 19:34:13 +0100
nextcloud-client (2.2.4-1.4) oldstable; urgency=medium
* The locale-specific icon names are correct too
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 7 Feb 2017 19:55:40 +0100
nextcloud-client (2.2.4-1.3) oldstable; urgency=medium
* Caja syncstate plugin is built.
* The syncstate plugin has application-specific name
-- István Váradi <ivaradi@varadiistvan.hu> Fri, 27 Jan 2017 19:34:18 +0100
nextcloud-client (2.2.4-1.2) oldstable; urgency=medium
* Fixed appname in the Nemo syncstate extension.
-- István Váradi <ivaradi@varadiistvan.hu> Thu, 19 Jan 2017 16:46:50 +0100
nextcloud-client (2.2.4-1.1) oldstable; urgency=medium
* Added Nautilus and Nemo syncstate extensions.
-- István Váradi <ivaradi@varadiistvan.hu> Tue, 17 Jan 2017 19:55:32 +0100
nextcloud-client (2.2.4-1.0) oldstable; urgency=medium
* Initial release.
-- István Váradi <ivaradi@varadiistvan.hu> Wed, 14 Dec 2016 20:07:46 +0100

View File

@@ -0,0 +1,84 @@
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,
kio-dev,
libcmocka-dev,
libdbus-1-dev,
libhttp-dav-perl,
libinotify-dev [kfreebsd-any],
libqt5webkit5-dev,
libqt5svg5-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
Recommends: libgnome-keyring0
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, ${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.

View File

@@ -9,7 +9,10 @@ Build-Depends: cmake,
extra-cmake-modules (>= 5.16),
kdelibs5-dev,
kio-dev,
libavcodec58,
libcmocka-dev,
libcloudproviders-dev,
libdbus-1-dev,
libhttp-dav-perl,
libinotify-dev [kfreebsd-any],
libqt5webkit5-dev,

View File

@@ -10,6 +10,8 @@ Build-Depends: cmake,
kdelibs5-dev,
libkf5kio-dev,
libcmocka-dev,
libcloudproviders-dev,
libdbus-1-dev,
libhttp-dav-perl,
libinotify-dev [kfreebsd-any],
libqt5svg5-dev,

View File

@@ -51,7 +51,7 @@ if ! wget http://ppa.launchpad.net/${repo}/ubuntu/pool/main/n/nextcloud-client/n
origsourceopt="-sa"
fi
for distribution in xenial bionic disco eoan stable; do
for distribution in xenial bionic disco eoan stable oldstable; do
rm -rf nextcloud-client_${basever}
cp -a ${DRONE_WORKSPACE} nextcloud-client_${basever}
@@ -98,26 +98,46 @@ if test "${pull_request}" = "master"; then
PPA=$PPA_BETA
OBS_PROJECT=$OBS_PROJECT_BETA
fi
OBS_SUBDIR="${OBS_PROJECT}/${OBS_PACKAGE}"
if test -f ~/.has_ppa_keys; then
for changes in nextcloud-client_*~+([a-z])1_source.changes; do
dput $PPA $changes > /dev/null
case "${changes}" in
*oldstable1*)
;;
*)
dput $PPA $changes > /dev/null
;;
esac
done
mkdir osc
cd osc
osc co ${OBS_PROJECT} ${OBS_PACKAGE}
if test "$(ls ${OBS_SUBDIR})"; then
osc delete ${OBS_SUBDIR}/*
fi
cp ../nextcloud-client*.orig.tar.* ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9].dsc ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9].debian.tar* ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9]_source.changes ${OBS_SUBDIR}/
osc add ${OBS_SUBDIR}/*
for distribution in stable oldstable; do
if test "${distribution}" = "oldstable"; then
pkgsuffix=".${distribution}"
pkgvertag="~${distribution}1"
else
pkgsuffix=""
pkgvertag=""
fi
cd ${OBS_SUBDIR}
osc commit -m "Travis update"
package="${OBS_PACKAGE}${pkgsuffix}"
OBS_SUBDIR="${OBS_PROJECT}/${package}"
mkdir -p osc
pushd osc
osc co ${OBS_PROJECT} ${package}
if test "$(ls ${OBS_SUBDIR})"; then
osc delete ${OBS_SUBDIR}/*
fi
cp ../nextcloud-client*.orig.tar.* ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}.dsc ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}.debian.tar* ${OBS_SUBDIR}/
cp ../nextcloud-client_*[0-9.][0-9]${pkgvertag}_source.changes ${OBS_SUBDIR}/
osc add ${OBS_SUBDIR}/*
cd ${OBS_SUBDIR}
osc commit -m "Travis update"
popd
done
fi
fi

21
admin/linux/upload-appimage.sh Executable file
View File

@@ -0,0 +1,21 @@
#! /bin/bash
set -xe
cd /build
# Upload AppImage
APPIMAGE=$(readlink -f ./Nextcloud*.AppImage)
BASENAME=$(basename ${APPIMAGE})
if curl --max-time 900 --upload-file ${APPIMAGE} https://transfer.sh/${BASENAME}
then
echo
echo "Get the AppImage at the link above!"
else
echo
echo "Upload failed, however this is an optional step."
fi
# Don't let the Drone build fail
exit 0

View File

@@ -49,7 +49,7 @@ fi
if [ ! -z "$identity" ]; then
echo "Will try to sign the installer"
pushd $install_path
productsign --sign "$identity" "$installer_file" "$installer_file.new"
productsign --timestamp --sign "$identity" "$installer_file" "$installer_file.new"
mv "$installer_file".new "$installer_file"
popd
else

2
binary

Submodule binary updated: 3425fab2c6...09f12de312

View File

@@ -27,7 +27,7 @@
<key>CFBundleShortVersionString</key>
<string>@MIRALL_VERSION_STRING@</string>
<key>NSHumanReadableCopyright</key>
<string>(C) 2014-2018 @APPLICATION_VENDOR@</string>
<string>(C) 2014-2019 @APPLICATION_VENDOR@</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
<key>SUShowReleaseNotes</key>

View File

@@ -467,6 +467,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = FinderSyncExt/FinderSyncExt.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";

View File

@@ -35,11 +35,11 @@ public:
QString contextMenuTitle() const
{
return _strings.value("CONTEXT_MENU_TITLE", "ownCloud");
return _strings.value("CONTEXT_MENU_TITLE", "Nextcloud");
}
QString shareActionTitle() const
{
return _strings.value("SHARE_MENU_TITLE", "Share...");
return _strings.value("SHARE_MENU_TITLE", "Share");
}
QString copyPrivateLinkTitle() const { return _strings["COPY_PRIVATE_LINK_MENU_TITLE"]; }

View File

@@ -29,6 +29,11 @@ if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
endif()
# Calling Qt's qCWarning(category, ...) with no params for "..." is a GNU
# extension (C++11 §16.3/4 forbids them). Silence clang's warnings.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-zero-variadic-macro-arguments")
endif()
if(WIN32)

View File

@@ -288,7 +288,11 @@ void ExcludedFiles::addManualExclude(const QByteArray &expr)
void ExcludedFiles::addManualExclude(const QByteArray &expr, const QByteArray &basePath)
{
#if defined(Q_OS_WIN)
Q_ASSERT(basePath.size() >= 2 && basePath.at(1) == ':');
#else
Q_ASSERT(basePath.startsWith('/'));
#endif
Q_ASSERT(basePath.endsWith('/'));
auto key = basePath;

View File

@@ -35,12 +35,11 @@ set(client_UI_SRCS
addcertificatedialog.ui
proxyauthdialog.ui
mnemonicdialog.ui
wizard/flow2authwidget.ui
wizard/owncloudadvancedsetuppage.ui
wizard/owncloudconnectionmethoddialog.ui
wizard/owncloudhttpcredspage.ui
wizard/owncloudoauthcredspage.ui
wizard/flow2authcredspage.ui
wizard/flow2authwidget.ui
wizard/owncloudsetupnocredspage.ui
wizard/owncloudwizardresultpage.ui
wizard/webview.ui
@@ -103,11 +102,14 @@ set(client_SRCS
servernotificationhandler.cpp
guiutility.cpp
elidedlabel.cpp
headerbanner.cpp
iconjob.cpp
remotewipe.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/oauth.cpp
creds/flow2auth.cpp
creds/keychainchunk.cpp
creds/webflowcredentials.cpp
creds/webflowcredentialsdialog.cpp
wizard/postfixlineedit.cpp
@@ -144,8 +146,6 @@ set(updater_SRCS
IF( APPLE )
list(APPEND client_SRCS cocoainitializer_mac.mm)
list(APPEND client_SRCS settingsdialogmac.cpp)
list(REMOVE_ITEM client_SRCS settingsdialog.cpp)
list(APPEND client_SRCS socketapisocket_mac.mm)
list(APPEND client_SRCS systray.mm)
@@ -175,17 +175,6 @@ set(3rdparty_SRC
../3rdparty/kmessagewidget/kmessagewidget.cpp
)
if (APPLE)
list(APPEND 3rdparty_SRC
../3rdparty/qtmacgoodies/src/macpreferenceswindow.mm
../3rdparty/qtmacgoodies/src/macstandardicon.mm
../3rdparty/qtmacgoodies/src/macwindow.mm
)
# We want to access Cocoa specific structures in the code above
# and need the platform plugin interface for that - which is private.
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
if(NOT WIN32)
list(APPEND 3rdparty_SRC ../3rdparty/qtlockedfile/qtlockedfile_unix.cpp)
else()
@@ -341,7 +330,6 @@ ENDIF()
target_include_directories(${APPLICATION_EXECUTABLE} PRIVATE
${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
${CMAKE_SOURCE_DIR}/src/3rdparty/qtmacgoodies/src
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
${CMAKE_CURRENT_BINARY_DIR}

View File

@@ -368,6 +368,7 @@ void AccountManager::shutdown()
_accounts.clear();
foreach (const auto &acc, accountsCopy) {
emit accountRemoved(acc.data());
emit removeAccountFolders(acc.data());
}
}

View File

@@ -70,7 +70,6 @@ public:
*/
void deleteAccount(AccountState *account);
/**
* Creates an account and sets up some basic handlers.
* Does *not* add the account to the account manager just yet.
@@ -104,6 +103,7 @@ public slots:
Q_SIGNALS:
void accountAdded(AccountState *account);
void accountRemoved(AccountState *account);
void removeAccountFolders(AccountState *account);
private:
AccountManager() {}

View File

@@ -57,10 +57,6 @@
#include "account.h"
#ifdef Q_OS_MAC
#include "settingsdialogmac.h"
#endif
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountSettings, "nextcloud.gui.account.settings", QtInfoMsg)
@@ -113,13 +109,13 @@ protected:
AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
: QWidget(parent)
, ui(new Ui::AccountSettings)
, _ui(new Ui::AccountSettings)
, _wasDisabledBefore(false)
, _accountState(accountState)
, _quotaInfo(accountState)
, _menuShown(false)
{
ui->setupUi(this);
_ui->setupUi(this);
_model = new FolderStatusModel;
_model->setAccountState(_accountState);
@@ -127,35 +123,40 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
FolderStatusDelegate *delegate = new FolderStatusDelegate;
delegate->setParent(this);
ui->_folderList->header()->hide();
ui->_folderList->setItemDelegate(delegate);
ui->_folderList->setModel(_model);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &AccountSettings::styleChanged, delegate, &FolderStatusDelegate::slotStyleChanged);
_ui->_folderList->header()->hide();
_ui->_folderList->setItemDelegate(delegate);
_ui->_folderList->setModel(_model);
#if defined(Q_OS_MAC)
ui->_folderList->setMinimumWidth(400);
_ui->_folderList->setMinimumWidth(400);
#else
ui->_folderList->setMinimumWidth(300);
_ui->_folderList->setMinimumWidth(300);
#endif
new ToolTipUpdater(ui->_folderList);
new ToolTipUpdater(_ui->_folderList);
auto mouseCursorChanger = new MouseCursorChanger(this);
mouseCursorChanger->folderList = ui->_folderList;
mouseCursorChanger->folderList = _ui->_folderList;
mouseCursorChanger->model = _model;
ui->_folderList->setMouseTracking(true);
ui->_folderList->setAttribute(Qt::WA_Hover, true);
ui->_folderList->installEventFilter(mouseCursorChanger);
_ui->_folderList->setMouseTracking(true);
_ui->_folderList->setAttribute(Qt::WA_Hover, true);
_ui->_folderList->installEventFilter(mouseCursorChanger);
createAccountToolbox();
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &AccountSettings::slotAccountAdded);
connect(ui->_folderList, &QWidget::customContextMenuRequested,
connect(this, &AccountSettings::removeAccountFolders,
AccountManager::instance(), &AccountManager::removeAccountFolders);
connect(_ui->_folderList, &QWidget::customContextMenuRequested,
this, &AccountSettings::slotCustomContextMenuRequested);
connect(ui->_folderList, &QAbstractItemView::clicked,
connect(_ui->_folderList, &QAbstractItemView::clicked,
this, &AccountSettings::slotFolderListClicked);
connect(ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus);
connect(ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus);
connect(ui->selectiveSyncNotification, &QLabel::linkActivated,
connect(_ui->_folderList, &QTreeView::expanded, this, &AccountSettings::refreshSelectiveSyncStatus);
connect(_ui->_folderList, &QTreeView::collapsed, this, &AccountSettings::refreshSelectiveSyncStatus);
connect(_ui->selectiveSyncNotification, &QLabel::linkActivated,
this, &AccountSettings::slotLinkActivated);
connect(_model, &FolderStatusModel::suggestExpand, ui->_folderList, &QTreeView::expand);
connect(_model, &FolderStatusModel::suggestExpand, _ui->_folderList, &QTreeView::expand);
connect(_model, &FolderStatusModel::dirtyChanged, this, &AccountSettings::refreshSelectiveSyncStatus);
refreshSelectiveSyncStatus();
connect(_model, &QAbstractItemModel::rowsInserted,
@@ -172,20 +173,21 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
addAction(syncNowWithRemoteDiscovery);
connect(ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
connect(ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders);
connect(ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
connect(ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders);
connect(ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders);
connect(_ui->selectiveSyncApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
connect(_ui->selectiveSyncCancel, &QAbstractButton::clicked, _model, &FolderStatusModel::resetFolders);
connect(_ui->bigFolderApply, &QAbstractButton::clicked, _model, &FolderStatusModel::slotApplySelectiveSync);
connect(_ui->bigFolderSyncAll, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncAllPendingBigFolders);
connect(_ui->bigFolderSyncNone, &QAbstractButton::clicked, _model, &FolderStatusModel::slotSyncNoPendingBigFolders);
connect(FolderMan::instance(), &FolderMan::folderListChanged, _model, &FolderStatusModel::resetFolders);
connect(this, &AccountSettings::folderChanged, _model, &FolderStatusModel::resetFolders);
QColor color = palette().highlight().color();
ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
// quotaProgressBar style now set in customizeStyle()
/*QColor color = palette().highlight().color();
_ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));*/
ui->connectLabel->setText(tr("No account configured."));
_ui->connectLabel->setText(tr("No account configured."));
connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
slotAccountStateChanged();
@@ -202,8 +204,10 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
{
slotNewMnemonicGenerated();
} else {
ui->encryptionMessage->hide();
_ui->encryptionMessage->hide();
}
customizeStyle();
}
@@ -225,9 +229,9 @@ void AccountSettings::createAccountToolbox()
menu->addAction(action);
connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount);
ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' '));
ui->_accountToolbox->setMenu(menu);
ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
_ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' '));
_ui->_accountToolbox->setMenu(menu);
_ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
slotAccountAdded(_accountState);
}
@@ -235,14 +239,14 @@ void AccountSettings::createAccountToolbox()
void AccountSettings::slotNewMnemonicGenerated()
{
ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
_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);
connect(mnemonic, &QAction::triggered, _ui->encryptionMessage, &KMessageWidget::hide);
ui->encryptionMessage->addAction(mnemonic);
ui->encryptionMessage->show();
_ui->encryptionMessage->addAction(mnemonic);
_ui->encryptionMessage->show();
}
void AccountSettings::slotMenuBeforeShow() {
@@ -250,7 +254,7 @@ void AccountSettings::slotMenuBeforeShow() {
return;
}
auto menu = ui->_accountToolbox->menu();
auto menu = _ui->_accountToolbox->menu();
// We can't check this during the initial creation as there is no account yet then
if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) {
@@ -265,7 +269,7 @@ void AccountSettings::slotMenuBeforeShow() {
QString AccountSettings::selectedFolderAlias() const
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
if (!selected.isValid())
return "";
return _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
@@ -278,16 +282,7 @@ void AccountSettings::slotOpenAccountWizard()
if (qgetenv("QT_QPA_PLATFORMTHEME") == "appmenu-qt5" || QSystemTrayIcon::isSystemTrayAvailable()) {
topLevelWidget()->close();
}
#ifdef Q_OS_MAC
qCDebug(lcAccountSettings) << parent() << topLevelWidget();
SettingsDialogMac *sd = qobject_cast<SettingsDialogMac *>(topLevelWidget());
if (sd) {
sd->showActivityPage();
} else {
qFatal("nope");
}
#endif
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), nullptr);
}
@@ -303,7 +298,7 @@ void AccountSettings::slotToggleSignInState()
void AccountSettings::doExpand()
{
ui->_folderList->expandToDepth(0);
_ui->_folderList->expandToDepth(0);
}
void AccountSettings::slotShowMnemonic(const QString &mnemonic) {
@@ -334,9 +329,9 @@ void AccountSettings::slotEncryptionFlagError(const QByteArray& fileId, int http
void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, const QByteArray &token)
{
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
FolderMetadata emptyMetadata(accountsState()->account());
FolderMetadata emptyMetadata(accountsState()->account());
auto encryptedMetadata = emptyMetadata.encryptedMetadata();
if (encryptedMetadata.isEmpty()) {
//TODO: Mark the folder as unencrypted as the metadata generation failed.
@@ -348,36 +343,36 @@ void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, con
return;
}
auto storeMetadataJob = new StoreMetaDataApiJob(accountsState()->account(), fileId, emptyMetadata.encryptedMetadata());
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
this, &AccountSettings::slotUploadMetadataSuccess);
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
this, &AccountSettings::slotUpdateMetadataError);
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
this, &AccountSettings::slotUploadMetadataSuccess);
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
this, &AccountSettings::slotUpdateMetadataError);
storeMetadataJob->start();
storeMetadataJob->start();
}
void AccountSettings::slotUploadMetadataSuccess(const QByteArray& folderId)
{
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
}
void AccountSettings::slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode)
{
Q_UNUSED(httpReturnCode);
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
this, &AccountSettings::slotUnlockFolderSuccess);
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
this, &AccountSettings::slotUnlockFolderError);
unlockJob->start();
}
void AccountSettings::slotLockForEncryptionError(const QByteArray& fileId, int httpErrorCode)
@@ -551,7 +546,7 @@ void AccountSettings::slotEditCurrentIgnoredFiles()
void AccountSettings::slotEditCurrentLocalIgnoredFiles()
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
return;
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
@@ -623,7 +618,7 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
{
QTreeView *tv = ui->_folderList;
QTreeView *tv = _ui->_folderList;
QModelIndex index = tv->indexAt(pos);
if (!index.isValid()) {
return;
@@ -654,7 +649,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
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->setEnabled(folderConnected);
connect(ac, &QAction::triggered, this, &AccountSettings::doExpand);
@@ -693,7 +688,7 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
}
if (_model->classify(indx) == FolderStatusModel::RootFolder) {
// tries to find if we clicked on the '...' button.
QTreeView *tv = ui->_folderList;
QTreeView *tv = _ui->_folderList;
auto pos = tv->mapFromGlobal(QCursor::pos());
if (FolderStatusDelegate::optionsButtonRect(tv->visualRect(indx), layoutDirection()).contains(pos)) {
slotCustomContextMenuRequested(pos);
@@ -706,8 +701,8 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
// Expand root items on single click
if (_accountState && _accountState->state() == AccountState::Connected) {
bool expanded = !(ui->_folderList->isExpanded(indx));
ui->_folderList->setExpanded(indx, expanded);
bool expanded = !(_ui->_folderList->isExpanded(indx));
_ui->_folderList->setExpanded(indx, expanded);
}
}
}
@@ -744,7 +739,7 @@ void AccountSettings::slotFolderWizardAccepted()
qCInfo(lcAccountSettings) << "Creating folder" << definition.localPath;
if (!dir.mkpath(".")) {
QMessageBox::warning(this, tr("Folder creation failed"),
tr("<p>Could not create local folder <i>%1</i>.")
tr("<p>Could not create local folder <i>%1</i>.</p>")
.arg(QDir::toNativeSeparators(definition.localPath)));
return;
}
@@ -789,7 +784,7 @@ void AccountSettings::slotRemoveCurrentFolder()
{
FolderMan *folderMan = FolderMan::instance();
auto folder = folderMan->folder(selectedFolderAlias());
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
if (selected.isValid() && folder) {
int row = selected.row();
@@ -831,7 +826,7 @@ void AccountSettings::slotOpenCurrentFolder()
void AccountSettings::slotOpenCurrentLocalSubFolder()
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
if (!selected.isValid() || _model->classify(selected) != FolderStatusModel::SubFolder)
return;
QString fileName = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
@@ -845,18 +840,21 @@ void AccountSettings::showConnectionLabel(const QString &message, QStringList er
"border-width: 1px; border-style: solid; border-color: #aaaaaa;"
"border-radius:5px;");
if (errors.isEmpty()) {
ui->connectLabel->setText(message);
ui->connectLabel->setToolTip(QString());
ui->connectLabel->setStyleSheet(QString());
QString msg = message;
Theme::replaceLinkColorStringBackgroundAware(msg);
_ui->connectLabel->setText(msg);
_ui->connectLabel->setToolTip(QString());
_ui->connectLabel->setStyleSheet(QString());
} else {
errors.prepend(message);
const QString msg = errors.join(QLatin1String("\n"));
QString msg = errors.join(QLatin1String("\n"));
qCDebug(lcAccountSettings) << msg;
ui->connectLabel->setText(msg);
ui->connectLabel->setToolTip(QString());
ui->connectLabel->setStyleSheet(errStyle);
Theme::replaceLinkColorString(msg, QColor("#c1c8e6"));
_ui->connectLabel->setText(msg);
_ui->connectLabel->setToolTip(QString());
_ui->connectLabel->setStyleSheet(errStyle);
}
ui->accountStatus->setVisible(!message.isEmpty());
_ui->accountStatus->setVisible(!message.isEmpty());
}
void AccountSettings::slotEnableCurrentFolder()
@@ -954,29 +952,29 @@ void AccountSettings::slotOpenOC()
void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
{
if (total > 0) {
ui->quotaProgressBar->setVisible(true);
ui->quotaProgressBar->setEnabled(true);
_ui->quotaProgressBar->setVisible(true);
_ui->quotaProgressBar->setEnabled(true);
// workaround the label only accepting ints (which may be only 32 bit wide)
const double percent = used / (double)total * 100;
const int percentInt = qMin(qRound(percent), 100);
ui->quotaProgressBar->setValue(percentInt);
_ui->quotaProgressBar->setValue(percentInt);
QString usedStr = Utility::octetsToString(used);
QString totalStr = Utility::octetsToString(total);
QString percentStr = Utility::compactFormatDouble(percent, 1);
QString toolTip = tr("%1 (%3%) of %2 in use. Some folders, including network mounted or shared folders, might have different limits.").arg(usedStr, totalStr, percentStr);
ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr));
ui->quotaInfoLabel->setToolTip(toolTip);
ui->quotaProgressBar->setToolTip(toolTip);
_ui->quotaInfoLabel->setText(tr("%1 of %2 in use").arg(usedStr, totalStr));
_ui->quotaInfoLabel->setToolTip(toolTip);
_ui->quotaProgressBar->setToolTip(toolTip);
} else {
ui->quotaProgressBar->setVisible(false);
ui->quotaInfoLabel->setToolTip(QString());
_ui->quotaProgressBar->setVisible(false);
_ui->quotaInfoLabel->setToolTip(QString());
/* -1 means not computed; -2 means unknown; -3 means unlimited (#3940)*/
if (total == 0 || total == -1) {
ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
_ui->quotaInfoLabel->setText(tr("Currently there is no storage usage information available."));
} else {
QString usedStr = Utility::octetsToString(used);
ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
_ui->quotaInfoLabel->setText(tr("%1 in use").arg(usedStr));
}
}
}
@@ -985,7 +983,7 @@ void AccountSettings::slotAccountStateChanged()
{
int state = _accountState ? _accountState->state() : AccountState::Disconnected;
if (_accountState) {
ui->sslButton->updateAccountState(_accountState);
_ui->sslButton->updateAccountState(_accountState);
AccountPtr account = _accountState->account();
QUrl safeUrl(account->url());
safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
@@ -1030,7 +1028,7 @@ void AccountSettings::slotAccountStateChanged()
"<a href='%1'>Click here</a> to re-open the browser.")
.arg(url.toString(QUrl::FullyEncoded)));
} else {
showConnectionLabel(tr("Connecting to %1...").arg(serverWithUser));
showConnectionLabel(tr("Connecting to %1").arg(serverWithUser));
}
} else {
showConnectionLabel(tr("No connection to %1 at %2.")
@@ -1044,14 +1042,14 @@ void AccountSettings::slotAccountStateChanged()
}
/* Allow to expand the item if the account is connected. */
ui->_folderList->setItemsExpandable(state == AccountState::Connected);
_ui->_folderList->setItemsExpandable(state == AccountState::Connected);
if (state != AccountState::Connected) {
/* check if there are expanded root items, if so, close them */
int i;
for (i = 0; i < _model->rowCount(); ++i) {
if (ui->_folderList->isExpanded(_model->index(i)))
ui->_folderList->setExpanded(_model->index(i), false);
if (_ui->_folderList->isExpanded(_model->index(i)))
_ui->_folderList->setExpanded(_model->index(i), false);
}
} else if (_model->isDirty()) {
// If we connect and have pending changes, show the list.
@@ -1095,21 +1093,21 @@ void AccountSettings::slotLinkActivated(const QString &link)
// Make sure the folder itself is expanded
Folder *f = FolderMan::instance()->folder(alias);
QModelIndex folderIndx = _model->indexForPath(f, QString());
if (!ui->_folderList->isExpanded(folderIndx)) {
ui->_folderList->setExpanded(folderIndx, true);
if (!_ui->_folderList->isExpanded(folderIndx)) {
_ui->_folderList->setExpanded(folderIndx, true);
}
QModelIndex indx = _model->indexForPath(f, myFolder);
if (indx.isValid()) {
// make sure all the parents are expanded
for (auto i = indx.parent(); i.isValid(); i = i.parent()) {
if (!ui->_folderList->isExpanded(i)) {
ui->_folderList->setExpanded(i, true);
if (!_ui->_folderList->isExpanded(i)) {
_ui->_folderList->setExpanded(i, true);
}
}
ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection);
ui->_folderList->setCurrentIndex(indx);
ui->_folderList->scrollTo(indx);
_ui->_folderList->setSelectionMode(QAbstractItemView::SingleSelection);
_ui->_folderList->setCurrentIndex(indx);
_ui->_folderList->scrollTo(indx);
} else {
qCWarning(lcAccountSettings) << "Unable to find a valid index for " << myFolder;
}
@@ -1118,7 +1116,7 @@ void AccountSettings::slotLinkActivated(const QString &link)
AccountSettings::~AccountSettings()
{
delete ui;
delete _ui;
}
void AccountSettings::refreshSelectiveSyncStatus()
@@ -1156,8 +1154,8 @@ void AccountSettings::refreshSelectiveSyncStatus()
}
if (msg.isEmpty()) {
ui->selectiveSyncButtons->setVisible(true);
ui->bigFolderUi->setVisible(false);
_ui->selectiveSyncButtons->setVisible(true);
_ui->bigFolderUi->setVisible(false);
} else {
ConfigFile cfg;
QString info = !cfg.confirmExternalStorage()
@@ -1166,27 +1164,27 @@ void AccountSettings::refreshSelectiveSyncStatus()
? tr("There are folders that were not synchronized because they are external storages: ")
: tr("There are folders that were not synchronized because they are too big or external storages: ");
ui->selectiveSyncNotification->setText(info + msg);
ui->selectiveSyncButtons->setVisible(false);
ui->bigFolderUi->setVisible(true);
_ui->selectiveSyncNotification->setText(info + msg);
_ui->selectiveSyncButtons->setVisible(false);
_ui->bigFolderUi->setVisible(true);
shouldBeVisible = true;
}
ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty());
bool wasVisible = !ui->selectiveSyncStatus->isHidden();
_ui->selectiveSyncApply->setEnabled(_model->isDirty() || !msg.isEmpty());
bool wasVisible = !_ui->selectiveSyncStatus->isHidden();
if (wasVisible != shouldBeVisible) {
QSize hint = ui->selectiveSyncStatus->sizeHint();
QSize hint = _ui->selectiveSyncStatus->sizeHint();
if (shouldBeVisible) {
ui->selectiveSyncStatus->setMaximumHeight(0);
ui->selectiveSyncStatus->setVisible(true);
_ui->selectiveSyncStatus->setMaximumHeight(0);
_ui->selectiveSyncStatus->setVisible(true);
}
auto anim = new QPropertyAnimation(ui->selectiveSyncStatus, "maximumHeight", ui->selectiveSyncStatus);
auto anim = new QPropertyAnimation(_ui->selectiveSyncStatus, "maximumHeight", _ui->selectiveSyncStatus);
anim->setEndValue(shouldBeVisible ? hint.height() : 0);
anim->start(QAbstractAnimation::DeleteWhenStopped);
connect(anim, &QPropertyAnimation::finished, [this, shouldBeVisible]() {
ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX);
_ui->selectiveSyncStatus->setMaximumHeight(QWIDGETSIZE_MAX);
if (!shouldBeVisible) {
ui->selectiveSyncStatus->hide();
_ui->selectiveSyncStatus->hide();
}
});
}
@@ -1246,12 +1244,30 @@ bool AccountSettings::event(QEvent *e)
// Expand the folder automatically only if there's only one, see #4283
// The 2 is 1 folder + 1 'add folder' button
if (_model->rowCount() <= 2) {
ui->_folderList->setExpanded(_model->index(0, 0), true);
_ui->_folderList->setExpanded(_model->index(0, 0), true);
}
}
return QWidget::event(e);
}
void AccountSettings::slotStyleChanged()
{
customizeStyle();
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
}
void AccountSettings::customizeStyle()
{
QString msg = _ui->connectLabel->text();
Theme::replaceLinkColorStringBackgroundAware(msg);
_ui->connectLabel->setText(msg);
QColor color = palette().highlight().color();
_ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
}
} // namespace OCC
#include "accountsettings.moc"

View File

@@ -63,11 +63,14 @@ signals:
void openFolderAlias(const QString &);
void showIssuesList(AccountState *account);
void requesetMnemonic();
void removeAccountFolders(AccountState *account);
void styleChanged();
public slots:
void slotOpenOC();
void slotUpdateQuota(qint64, qint64);
void slotAccountStateChanged();
void slotStyleChanged();
AccountState *accountsState() { return _accountState; }
@@ -128,11 +131,12 @@ private:
bool event(QEvent *) override;
void createAccountToolbox();
void openIgnoredFilesDialog(const QString & absFolderPath);
void customizeStyle();
/// Returns the alias of the selected folder, empty string if none
QString selectedFolderAlias() const;
Ui::AccountSettings *ui;
Ui::AccountSettings *_ui;
FolderStatusModel *_model;
QUrl _OCUrl;

View File

@@ -14,6 +14,7 @@
#include "accountstate.h"
#include "accountmanager.h"
#include "remotewipe.h"
#include "account.h"
#include "creds/abstractcredentials.h"
#include "creds/httpcredentials.h"
@@ -24,6 +25,11 @@
#include <QTimer>
#include <qfontmetrics.h>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkRequest>
#include <QBuffer>
namespace OCC {
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
@@ -36,11 +42,12 @@ AccountState::AccountState(AccountPtr account)
, _waitingForNewCredentials(false)
, _notificationsEtagResponseHeader("*")
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
, _remoteWipe(new RemoteWipe(_account))
{
qRegisterMetaType<AccountState *>("AccountState*");
connect(account.data(), &Account::invalidCredentials,
this, &AccountState::slotInvalidCredentials);
this, &AccountState::slotHandleRemoteWipeCheck);
connect(account.data(), &Account::credentialsFetched,
this, &AccountState::slotCredentialsFetched);
connect(account.data(), &Account::credentialsAsked,
@@ -303,7 +310,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
break;
case ConnectionValidator::CredentialsWrong:
case ConnectionValidator::CredentialsNotReady:
slotInvalidCredentials();
handleInvalidCredentials();
break;
case ConnectionValidator::SslError:
setState(SignedOut);
@@ -322,7 +329,20 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
}
}
void AccountState::slotInvalidCredentials()
void AccountState::slotHandleRemoteWipeCheck()
{
// make sure it changes account state and icons
signOutByUi();
qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString()
<< "checking for remote wipe request";
_waitingForNewCredentials = false;
setState(SignedOut);
}
void AccountState::handleInvalidCredentials()
{
if (isSignedOut() || _waitingForNewCredentials)
return;
@@ -343,6 +363,7 @@ void AccountState::slotInvalidCredentials()
account()->credentials()->askFromUser();
}
void AccountState::slotCredentialsFetched(AbstractCredentials *)
{
// Make a connection attempt, no matter whether the credentials are

View File

@@ -29,6 +29,7 @@ namespace OCC {
class AccountState;
class Account;
class RemoteWipe;
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
@@ -150,6 +151,9 @@ public:
*/
void setNavigationAppsEtagResponseHeader(const QByteArray &value);
///Asks for user credentials
void handleInvalidCredentials();
public slots:
/// Triggers a ping to the server to update state and
/// connection status and errors.
@@ -164,7 +168,11 @@ signals:
protected Q_SLOTS:
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
void slotInvalidCredentials();
/// When client gets a 401 or 403 checks if server requested remote wipe
/// before asking for user credentials again
void slotHandleRemoteWipeCheck();
void slotCredentialsFetched(AbstractCredentials *creds);
void slotCredentialsAsked(AbstractCredentials *creds);
@@ -190,6 +198,13 @@ private:
* Milliseconds for which to delay reconnection after 503/maintenance.
*/
int _maintenanceToConnectedDelay;
/**
* Connects remote wipe check with the account
* the log out triggers the check (loads app password -> create request)
*/
RemoteWipe *_remoteWipe;
};
}

View File

@@ -21,7 +21,7 @@ namespace OCC {
bool operator<(const Activity &rhs, const Activity &lhs)
{
return rhs._dateTime.toMSecsSinceEpoch() > lhs._dateTime.toMSecsSinceEpoch();
return rhs._dateTime > lhs._dateTime;
}
bool operator==(const Activity &rhs, const Activity &lhs)

View File

@@ -15,6 +15,7 @@
*/
#include "activityitemdelegate.h"
#include "activitylistmodel.h"
#include "folderstatusmodel.h"
#include "folderman.h"
#include "accountstate.h"
@@ -26,6 +27,12 @@
#include <QPainter>
#include <QApplication>
#define FIXME_USE_HIGH_DPI_RATIO
#ifdef FIXME_USE_HIGH_DPI_RATIO
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
#include <QDesktopWidget>
#endif
#define HASQT5_11 (QT_VERSION >= QT_VERSION_CHECK(5,11,0))
namespace OCC {
@@ -40,6 +47,12 @@ int ActivityItemDelegate::_buttonHeight = 0;
const QString ActivityItemDelegate::_remote_share("remote_share");
const QString ActivityItemDelegate::_call("call");
ActivityItemDelegate::ActivityItemDelegate()
: QStyledItemDelegate()
{
customizeStyle();
}
int ActivityItemDelegate::iconHeight()
{
if (_iconHeight == 0) {
@@ -89,10 +102,26 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
int iconSize = 16;
int iconOffset = qRound(fm.height() / 4.0 * 7.0);
int offset = 4;
const bool isSelected = (option.state & QStyle::State_Selected);
#ifdef FIXME_USE_HIGH_DPI_RATIO
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
const int device_pixel_ration = QApplication::desktop()->devicePixelRatio();
int pixel_ratio = (device_pixel_ration > 1 ? device_pixel_ration : 1);
#endif
// get the data
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
QIcon actionIcon = qvariant_cast<QIcon>(index.data(ActionIconRole));
QIcon actionIcon;
const ActivityListModel::ActionIcon icn = qvariant_cast<ActivityListModel::ActionIcon>(index.data(ActionIconRole));
switch(icn.iconType) {
case ActivityListModel::ActivityIconType::iconUseCached: actionIcon = icn.cachedIcon; break;
case ActivityListModel::ActivityIconType::iconActivity: actionIcon = (isSelected ? _iconActivity_sel : _iconActivity); break;
case ActivityListModel::ActivityIconType::iconBell: actionIcon = (isSelected ? _iconBell_sel : _iconBell); break;
case ActivityListModel::ActivityIconType::iconStateError: actionIcon = _iconStateError; break;
case ActivityListModel::ActivityIconType::iconStateWarning: actionIcon = _iconStateWarning; break;
case ActivityListModel::ActivityIconType::iconStateInfo: actionIcon = _iconStateInfo; break;
case ActivityListModel::ActivityIconType::iconStateSync: actionIcon = _iconStateSync; break;
}
QString objectType = qvariant_cast<QString>(index.data(ObjectTypeRole));
QString actionText = qvariant_cast<QString>(index.data(ActionTextRole));
QString messageText = qvariant_cast<QString>(index.data(MessageRole));
@@ -116,6 +145,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
actionTextBox.setTop(option.rect.top() + margin + offset/2);
actionTextBox.setHeight(fm.height());
actionTextBox.setLeft(actionIconRect.right() + margin);
#ifdef FIXME_USE_HIGH_DPI_RATIO
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
actionTextBoxWidth *= pixel_ratio;
#endif
actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin);
// message text rect
@@ -138,11 +171,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
// time box rect
QRect timeBox = messageTextBox;
QString timeStr = tr("%1").arg(timeText);
#if (HASQT5_11)
int timeTextWidth = fm.horizontalAdvance(timeStr);
int timeTextWidth = fm.horizontalAdvance(timeText);
#else
int timeTextWidth = fm.width(timeStr);
int timeTextWidth = fm.width(timeText);
#endif
int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2;
if(messageText.isEmpty() || actionText.isEmpty())
@@ -150,6 +182,10 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
timeBox.setTop(timeTop);
timeBox.setHeight(fm.height());
timeBox.setBottom(timeBox.top() + fm.height());
#ifdef FIXME_USE_HIGH_DPI_RATIO
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
timeTextWidth *= pixel_ratio;
#endif
timeBox.setRight(timeBox.left() + timeTextWidth + margin);
// buttons - default values
@@ -184,9 +220,9 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
if(activityType == Activity::Type::NotificationType){
// Secondary will be 'Dismiss' or '...' multiple options button
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/close.svg"));
secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose);
if(customList.size() > 1)
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/more.svg"));
secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore);
secondaryButton.iconSize = QSize(iconSize, iconSize);
// Primary button will be 'More Information' or 'Accept'
@@ -209,7 +245,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
} else if(activityType == Activity::SyncResultType){
// Secondary will be 'open file manager' with the folder icon
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg"));
secondaryButton.icon = _iconFolder;
secondaryButton.iconSize = QSize(iconSize, iconSize);
// Primary button will be 'open browser'
@@ -230,7 +266,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
} else if(activityType == Activity::SyncFileItemType){
// Secondary will be 'open file manager' with the folder icon
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/folder.svg"));
secondaryButton.icon = _iconFolder;
secondaryButton.iconSize = QSize(iconSize, iconSize);
// No primary button on this case
@@ -255,7 +291,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
p.setCurrentColorGroup(QPalette::Disabled);
// change pen color if the line is selected
if (option.state & QStyle::State_Selected)
if (isSelected)
painter->setPen(p.color(QPalette::HighlightedText));
else
painter->setPen(p.color(QPalette::Text));
@@ -269,8 +305,15 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
painter->drawText(actionTextBox, elidedAction);
// draw the buttons
if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType)
if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) {
primaryButton.palette = p;
if (isSelected)
primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::HighlightedText));
else
primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::Text));
QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter);
}
// Since they are errors on local syncing, there is nothing to do in the server
if(activityType != Activity::Type::ActivityType)
@@ -284,13 +327,13 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
}
// change pen color for the time
if (option.state & QStyle::State_Selected)
if (isSelected)
painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText));
else
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
// draw the time
const QString elidedTime = fm.elidedText(timeStr, Qt::ElideRight, spaceLeftForText);
const QString elidedTime = fm.elidedText(timeText, Qt::ElideRight, spaceLeftForText);
painter->drawText(timeBox, elidedTime);
painter->restore();
@@ -333,4 +376,32 @@ bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void ActivityItemDelegate::slotStyleChanged()
{
customizeStyle();
}
void ActivityItemDelegate::customizeStyle()
{
QPalette pal;
pal.setColor(QPalette::Base, QColor(0,0,0)); // use dark background colour to invert icons
_iconClose = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"));
_iconClose_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"), pal);
_iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"));
_iconMore_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"), pal);
_iconFolder = QIcon(QLatin1String(":/client/resources/folder.svg"));
_iconActivity = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"));
_iconActivity_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"), pal);
_iconBell = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"));
_iconBell_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"), pal);
_iconStateError = QIcon(QLatin1String(":/client/resources/state-error.svg"));
_iconStateWarning = QIcon(QLatin1String(":/client/resources/state-warning.svg"));
_iconStateInfo = QIcon(QLatin1String(":/client/resources/state-info.svg"));
_iconStateSync = QIcon(QLatin1String(":/client/resources/state-sync.svg"));
}
} // namespace OCC

View File

@@ -43,6 +43,8 @@ public:
AccountConnectedRole,
SyncFileStatusRole };
ActivityItemDelegate();
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
@@ -51,11 +53,16 @@ public:
static int rowHeight();
static int iconHeight();
public slots:
void slotStyleChanged();
signals:
void primaryButtonClickedOnItemView(const QModelIndex &index);
void secondaryButtonClickedOnItemView(const QModelIndex &index);
private:
void customizeStyle();
static int _margin;
static int _iconHeight;
static int _primaryButtonWidth;
@@ -65,6 +72,23 @@ private:
static int _buttonHeight;
static const QString _remote_share;
static const QString _call;
QIcon _iconClose;
QIcon _iconClose_sel;
QIcon _iconMore;
QIcon _iconMore_sel;
QIcon _iconFolder;
QIcon _iconActivity;
QIcon _iconActivity_sel;
QIcon _iconBell;
QIcon _iconBell_sel;
QIcon _iconStateError;
QIcon _iconStateWarning;
QIcon _iconStateInfo;
QIcon _iconStateSync;
};
} // namespace OCC

View File

@@ -71,13 +71,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return QVariant(list.at(0));
}
// File does not exist anymore? Let's try to open its path
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
if (list.count() > 0) {
return QVariant(list.at(0));
if(QFileInfo(relPath).exists()) {
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
if (list.count() > 0) {
return QVariant(list.at(0));
}
}
}
return QVariant();
break;
case ActivityItemDelegate::ActionsLinksRole:{
QList<QVariant> customList;
foreach (ActivityLink customItem, a._links) {
@@ -86,61 +87,61 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
customList << customVariant;
}
return customList;
break;
}
case ActivityItemDelegate::ActionIconRole:
case ActivityItemDelegate::ActionIconRole:{
ActionIcon actionIcon;
if(a._type == Activity::NotificationType){
QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id);
if(!cachedIcon.isNull())
return cachedIcon;
else return QIcon(QLatin1String(":/client/resources/bell.svg"));
if(!cachedIcon.isNull()) {
actionIcon.iconType = ActivityIconType::iconUseCached;
actionIcon.cachedIcon = cachedIcon;
} else {
actionIcon.iconType = ActivityIconType::iconBell;
}
} else if(a._type == Activity::SyncResultType){
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
actionIcon.iconType = ActivityIconType::iconStateError;
} else if(a._type == Activity::SyncFileItemType){
if(a._status == SyncFileItem::NormalError
|| a._status == SyncFileItem::FatalError
|| a._status == SyncFileItem::DetailError
|| a._status == SyncFileItem::BlacklistedError) {
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
actionIcon.iconType = ActivityIconType::iconStateError;
} else if(a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration
|| a._status == SyncFileItem::FileLocked){
return QIcon(QLatin1String(":/client/resources/state-warning.svg"));
actionIcon.iconType = ActivityIconType::iconStateWarning;
} else if(a._status == SyncFileItem::FileIgnored){
return QIcon(QLatin1String(":/client/resources/state-info.svg"));
actionIcon.iconType = ActivityIconType::iconStateInfo;
} else {
actionIcon.iconType = ActivityIconType::iconStateSync;
}
return QIcon(QLatin1String(":/client/resources/state-sync.svg"));
} else {
actionIcon.iconType = ActivityIconType::iconActivity;
}
return QIcon(QLatin1String(":/client/resources/activity.png"));
break;
QVariant icn;
icn.setValue(actionIcon);
return icn;
}
case ActivityItemDelegate::ObjectTypeRole:
return a._objectType;
break;
case ActivityItemDelegate::ActionRole:{
QVariant type;
type.setValue(a._type);
return type;
break;
}
case ActivityItemDelegate::ActionTextRole:
return a._subject;
break;
case ActivityItemDelegate::MessageRole:
return a._message;
break;
case ActivityItemDelegate::LinkRole:
return a._link;
break;
case ActivityItemDelegate::AccountRole:
return a._accName;
break;
case ActivityItemDelegate::PointInTimeRole:
return Utility::timeAgoInWords(a._dateTime);
break;
return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime()));
case ActivityItemDelegate::AccountConnectedRole:
return (ast && ast->isConnected());
break;
default:
return QVariant();
}
@@ -229,6 +230,29 @@ void ActivityListModel::addErrorToActivityList(Activity activity) {
combineActivityLists();
}
void ActivityListModel::addIgnoredFileToList(Activity newActivity) {
qCInfo(lcActivity) << "First checking for duplicates then add file to the notification list of ignored files: " << newActivity._file;
bool duplicate = false;
if(_listOfIgnoredFiles.size() == 0){
_notificationIgnoredFiles = newActivity;
_notificationIgnoredFiles._subject = tr("Files from the ignore list as well as symbolic links are not synced. This includes:");
_listOfIgnoredFiles.append(newActivity);
return;
}
foreach(Activity activity, _listOfIgnoredFiles){
if(activity._file == newActivity._file){
duplicate = true;
break;
}
}
if(!duplicate){
_notificationIgnoredFiles._message.append(", " + newActivity._file);
}
}
void ActivityListModel::addNotificationToActivityList(Activity activity) {
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
_notificationLists.prepend(activity);
@@ -242,7 +266,7 @@ void ActivityListModel::clearNotifications() {
}
void ActivityListModel::removeActivityFromActivityList(int row) {
Activity activity = _finalList.at(row);
Activity activity = _finalList.at(row);
removeActivityFromActivityList(activity);
combineActivityLists();
}
@@ -276,22 +300,31 @@ void ActivityListModel::removeActivityFromActivityList(Activity activity) {
}
}
void ActivityListModel::combineActivityLists()
{
ActivityList resultList;
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
resultList.append(_notificationErrorsLists);
if(_notificationErrorsLists.count() > 0) {
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
resultList.append(_notificationErrorsLists);
}
if(_listOfIgnoredFiles.size() > 0)
resultList.append(_notificationIgnoredFiles);
std::sort(_notificationLists.begin(), _notificationLists.end());
resultList.append(_notificationLists);
if(_notificationLists.count() > 0) {
std::sort(_notificationLists.begin(), _notificationLists.end());
resultList.append(_notificationLists);
}
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
resultList.append(_syncFileItemLists);
if(_syncFileItemLists.count() > 0) {
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
resultList.append(_syncFileItemLists);
}
std::sort(_activityLists.begin(), _activityLists.end());
resultList.append(_activityLists);
if(_activityLists.count() > 0) {
std::sort(_activityLists.begin(), _activityLists.end());
resultList.append(_activityLists);
}
beginResetModel();
_finalList.clear();

View File

@@ -38,6 +38,20 @@ class ActivityListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum ActivityIconType {
iconUseCached = 0,
iconActivity,
iconBell,
iconStateError,
iconStateWarning,
iconStateInfo,
iconStateSync
};
struct ActionIcon {
ActivityIconType iconType;
QIcon cachedIcon;
};
explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const override;
@@ -51,6 +65,7 @@ public:
void addNotificationToActivityList(Activity activity);
void clearNotifications();
void addErrorToActivityList(Activity activity);
void addIgnoredFileToList(Activity newActivity);
void addSyncFileItemToActivityList(Activity activity);
void removeActivityFromActivityList(int row);
void removeActivityFromActivityList(Activity activity);
@@ -73,6 +88,8 @@ private:
ActivityList _activityLists;
ActivityList _syncFileItemLists;
ActivityList _notificationLists;
ActivityList _listOfIgnoredFiles;
Activity _notificationIgnoredFiles;
ActivityList _notificationErrorsLists;
ActivityList _finalList;
AccountState *_accountState;
@@ -81,4 +98,7 @@ private:
int _currentItem = 0;
};
}
Q_DECLARE_METATYPE(OCC::ActivityListModel::ActionIcon)
#endif // ACTIVITYLISTMODEL_H

View File

@@ -89,6 +89,9 @@ ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent)
this, &ActivityWidget::addError);
_removeTimer.setInterval(1000);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &ActivityWidget::styleChanged, delegate, &ActivityItemDelegate::slotStyleChanged);
}
ActivityWidget::~ActivityWidget()
@@ -127,11 +130,12 @@ void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo
}
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);
continue;
}
if(!QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity);
continue;
@@ -175,7 +179,7 @@ void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItem
Activity activity;
activity._type = Activity::SyncFileItemType; //client activity
activity._status = item->_status;
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
activity._dateTime = QDateTime::currentDateTime();
activity._message = item->_originalFile;
activity._link = folderInstance->accountState()->account()->url();
activity._accName = folderInstance->accountState()->account()->displayName();
@@ -191,8 +195,12 @@ void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItem
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
activity._subject = item->_errorString;
// add 'protocol error' to activity list
_model->addErrorToActivityList(activity);
if(item->_status == SyncFileItem::Status::FileIgnored) {
_model->addIgnoredFileToList(activity);
} else {
// add 'protocol error' to activity list
_model->addErrorToActivityList(activity);
}
}
}
}
@@ -543,6 +551,12 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
}
void ActivityWidget::slotStyleChanged()
{
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
}
/* ==================================================================== */
ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
@@ -565,6 +579,9 @@ ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
// connect a model signal to stop the animation
connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation);
connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &ActivitySettings::styleChanged, _activityWidget, &ActivityWidget::slotStyleChanged);
}
void ActivitySettings::slotDisplayActivities(){
@@ -623,4 +640,14 @@ bool ActivitySettings::event(QEvent *e)
ActivitySettings::~ActivitySettings()
{
}
void ActivitySettings::slotStyleChanged()
{
if(_progressIndicator)
_progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text));
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
}
}

View File

@@ -78,12 +78,14 @@ public slots:
void addError(const QString &folderAlias, const QString &message, ErrorCategory category);
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
void slotStyleChanged();
signals:
void guiLog(const QString &, const QString &);
void rowsInserted();
void hideActivityTab(bool);
void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
void styleChanged();
private slots:
void slotBuildNotificationDisplay(const ActivityList &list);
@@ -96,6 +98,7 @@ private slots:
void slotSecondaryButtonClickedOnListView(const QModelIndex &index);
private:
void customizeStyle();
void showLabels();
QString timeString(QDateTime dt, QLocale::FormatType format) const;
Ui::ActivityWidget *_ui;
@@ -137,6 +140,7 @@ public slots:
void slotRefresh();
void slotRemoveAccount();
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
void slotStyleChanged();
private slots:
void slotRegularNotificationCheck();
@@ -144,6 +148,7 @@ private slots:
signals:
void guiLog(const QString &, const QString &);
void styleChanged();
private:
bool event(QEvent *e) override;

View File

@@ -254,6 +254,9 @@ Application::Application(int &argc, char **argv)
// Cleanup at Quit.
connect(this, &QCoreApplication::aboutToQuit, this, &Application::slotCleanup);
// Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example)
connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings);
}
Application::~Application()
@@ -648,5 +651,9 @@ void Application::showSettingsDialog()
_gui->slotShowSettings();
}
void Application::slotGuiIsShowingSettings()
{
emit isShowingSettingsDialog();
}
} // namespace OCC

View File

@@ -82,6 +82,7 @@ protected:
signals:
void folderRemoved();
void folderStateChanged(Folder *);
void isShowingSettingsDialog();
protected slots:
void slotParseMessage(const QString &, QObject *);
@@ -91,6 +92,7 @@ protected slots:
void slotAccountStateAdded(AccountState *accountState);
void slotAccountStateRemoved(AccountState *accountState);
void slotSystemOnlineConfigurationChanged(QNetworkConfiguration);
void slotGuiIsShowingSettings();
private:
void setHelp();

View File

@@ -14,10 +14,12 @@
*/
#include <QDesktopServices>
#include <QApplication>
#include <QClipboard>
#include <QTimer>
#include <QBuffer>
#include "account.h"
#include "creds/flow2auth.h"
#include "flow2auth.h"
#include <QJsonObject>
#include <QJsonDocument>
#include "theme.h"
@@ -28,6 +30,17 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcFlow2auth, "nextcloud.sync.credentials.flow2auth", QtInfoMsg)
Flow2Auth::Flow2Auth(Account *account, QObject *parent)
: QObject(parent)
, _account(account)
, _isBusy(false)
, _hasToken(false)
{
_pollTimer.setInterval(1000);
QObject::connect(&_pollTimer, &QTimer::timeout, this, &Flow2Auth::slotPollTimerTimeout);
}
Flow2Auth::~Flow2Auth()
{
}
@@ -47,7 +60,23 @@ QUrl Flow2Auth::authorisationLink() const
void Flow2Auth::openBrowser()
{
_pollTimer.stop();
fetchNewToken(TokenAction::actionOpenBrowser);
}
void Flow2Auth::copyLinkToClipboard()
{
fetchNewToken(TokenAction::actionCopyLinkToClipboard);
}
void Flow2Auth::fetchNewToken(const TokenAction action)
{
if(_isBusy)
return;
_isBusy = true;
_hasToken = false;
emit statusChanged(PollStatus::statusFetchToken, 0);
// Step 1: Initiate a login, do an anonymous POST request
QUrl url = Utility::concatUrlPath(_account->url().toString(), QLatin1String("/index.php/login/v2"));
@@ -59,14 +88,18 @@ void Flow2Auth::openBrowser()
auto job = _account->sendRequest("POST", url, req);
job->setTimeout(qMin(30 * 1000ll, job->timeoutMsec()));
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this, action](QNetworkReply *reply) {
auto jsonData = reply->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
QString pollToken, pollEndpoint, loginUrl;
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 = json.value("poll").toObject().value("token").toString();
pollEndpoint = json.value("poll").toObject().value("endpoint").toString();
loginUrl = json["login"].toString();
}
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|| json.isEmpty() || pollToken.isEmpty() || pollEndpoint.isEmpty() || loginUrl.isEmpty()) {
@@ -85,7 +118,9 @@ void Flow2Auth::openBrowser()
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);
emit result(Error, errorReason);
_pollTimer.stop();
_isBusy = false;
return;
}
@@ -99,23 +134,50 @@ void Flow2Auth::openBrowser()
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();
_secondsInterval = (polltime.count() / 1000);
_secondsLeft = _secondsInterval;
emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft);
// 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());
if(!_pollTimer.isActive()) {
_pollTimer.start();
}
switch(action)
{
case actionOpenBrowser:
// Try to open Browser
if (!QDesktopServices::openUrl(authorisationLink())) {
// We cannot open the browser, then we claim we don't support Flow2Auth.
// Our UI callee will ask the user to copy and open the link.
emit result(NotSupported);
}
break;
case actionCopyLinkToClipboard:
QApplication::clipboard()->setText(authorisationLink().toString(QUrl::FullyEncoded));
emit statusChanged(PollStatus::statusCopyLinkToClipboard, 0);
break;
}
_isBusy = false;
_hasToken = true;
});
}
void Flow2Auth::slotPollTimerTimeout()
{
_pollTimer.stop();
if(_isBusy || !_hasToken)
return;
_isBusy = true;
_secondsLeft--;
if(_secondsLeft > 0) {
emit statusChanged(PollStatus::statusPollCountdown, _secondsLeft);
_isBusy = false;
return;
}
emit statusChanged(PollStatus::statusPollNow, 0);
// Step 2: Poll
QNetworkRequest req;
@@ -132,10 +194,15 @@ void Flow2Auth::slotPollTimerTimeout()
auto jsonData = reply->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
QUrl serverUrl;
QString loginName, appPassword;
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 = json["server"].toString();
loginName = json["loginName"].toString();
appPassword = json["appPassword"].toString();
}
if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError
|| json.isEmpty() || serverUrl.isEmpty() || loginName.isEmpty() || appPassword.isEmpty()) {
@@ -155,26 +222,50 @@ void Flow2Auth::slotPollTimerTimeout()
}
qCDebug(lcFlow2auth) << "Error when polling for the appPassword" << json << errorReason;
// We get a 404 until authentication is done, so don't show this error in the GUI.
if(reply->error() != QNetworkReply::ContentNotFoundError)
emit result(Error, errorReason);
// Forget sensitive data
appPassword.clear();
loginName.clear();
// Failed: poll again
_pollTimer.start();
_secondsLeft = _secondsInterval;
_isBusy = false;
return;
}
_pollTimer.stop();
// Success
qCInfo(lcFlow2auth) << "Success getting the appPassword for user: " << loginName << ", server: " << serverUrl.toString();
_account->setUrl(serverUrl);
emit result(LoggedIn, loginName, appPassword);
emit result(LoggedIn, QString(), loginName, appPassword);
// Forget sensitive data
appPassword.clear();
loginName.clear();
_loginUrl.clear();
_pollToken.clear();
_pollEndpoint.clear();
_isBusy = false;
_hasToken = false;
});
}
void Flow2Auth::slotPollNow()
{
// poll now if we're not already doing so
if(_isBusy || !_hasToken)
return;
_secondsLeft = 1;
slotPollTimerTimeout();
}
} // namespace OCC

View File

@@ -25,17 +25,23 @@ 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)
{
}
enum TokenAction {
actionOpenBrowser = 1,
actionCopyLinkToClipboard
};
enum PollStatus {
statusPollCountdown = 1,
statusPollNow,
statusFetchToken,
statusCopyLinkToClipboard
};
Flow2Auth(Account *account, QObject *parent);
~Flow2Auth();
enum Result { NotSupported,
@@ -44,6 +50,7 @@ public:
Q_ENUM(Result);
void start();
void openBrowser();
void copyLinkToClipboard();
QUrl authorisationLink() const;
signals:
@@ -51,18 +58,29 @@ 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());
void result(Flow2Auth::Result result, const QString &errorString = QString(),
const QString &user = QString(), const QString &appPassword = QString());
void statusChanged(const PollStatus status, int secondsLeft);
public slots:
void slotPollNow();
private slots:
void slotPollTimerTimeout();
private:
void fetchNewToken(const TokenAction action);
Account *_account;
QUrl _loginUrl;
QString _pollToken;
QString _pollEndpoint;
QTimer _pollTimer;
int _secondsLeft;
int _secondsInterval;
bool _isBusy;
bool _hasToken;
};
} // namespace OCC

View File

@@ -0,0 +1,221 @@
/*
* 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 "account.h"
#include "keychainchunk.h"
#include "theme.h"
#include "networkjobs.h"
#include "configfile.h"
#include "creds/abstractcredentials.h"
using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcKeychainChunk, "nextcloud.sync.credentials.keychainchunk", QtInfoMsg)
namespace KeychainChunk {
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
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());
}
#endif
/*
* Job
*/
Job::Job(QObject *parent)
: QObject(parent)
{
_serviceName = Theme::instance()->appName();
}
/*
* WriteJob
*/
WriteJob::WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
// Windows workaround: Split the private key into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
_chunkBuffer = data;
_chunkCount = 0;
}
void WriteJob::start()
{
slotWriteJobDone(nullptr);
}
void WriteJob::slotWriteJobDone(QKeychain::Job *incomingJob)
{
QKeychain::WritePasswordJob *writeJob = static_cast<QKeychain::WritePasswordJob *>(incomingJob);
// errors?
if (writeJob) {
_error = writeJob->error();
_errorString = writeJob->errorString();
if (writeJob->error() != NoError) {
qCWarning(lcKeychainChunk) << "Error while writing" << writeJob->key() << "chunk" << writeJob->errorString();
_chunkBuffer.clear();
}
}
// write a chunk if there is any in the buffer
if (!_chunkBuffer.isEmpty()) {
#if defined(Q_OS_WIN)
// Windows workaround: Split the data into chunks of 2048 bytes,
// to allow 4k (4096 bit) keys to be saved (obey Windows's limits)
auto chunk = _chunkBuffer.left(KeychainChunk::ChunkSize);
_chunkBuffer = _chunkBuffer.right(_chunkBuffer.size() - chunk.size());
#else
// write full data in one chunk on non-Windows, as usual
auto chunk = _chunkBuffer;
_chunkBuffer.clear();
#endif
auto index = (_chunkCount++);
// keep the limit
if (_chunkCount > KeychainChunk::MaxChunks) {
qCWarning(lcKeychainChunk) << "Maximum chunk count exceeded while writing" << writeJob->key() << "chunk" << QString::number(index) << "cutting off after" << QString::number(KeychainChunk::MaxChunks) << "chunks";
writeJob->deleteLater();
_chunkBuffer.clear();
emit finished(this);
return;
}
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key + (index > 0 ? (QString(".") + QString::number(index)) : QString()),
_account->id());
QKeychain::WritePasswordJob *job = new QKeychain::WritePasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::WriteJob::slotWriteJobDone);
// only add the key's (sub)"index" after the first element, to stay compatible with older versions and non-Windows
job->setKey(kck);
job->setBinaryData(chunk);
job->start();
chunk.clear();
} else {
emit finished(this);
}
writeJob->deleteLater();
}
/*
* ReadJob
*/
ReadJob::ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent)
: Job(parent)
{
_account = account;
_key = key;
_keychainMigration = keychainMigration;
_chunkCount = 0;
_chunkBuffer.clear();
}
void ReadJob::start()
{
_chunkCount = 0;
_chunkBuffer.clear();
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key,
_keychainMigration ? QString() : _account->id());
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
}
void ReadJob::slotReadJobDone(QKeychain::Job *incomingJob)
{
// Errors or next chunk?
QKeychain::ReadPasswordJob *readJob = static_cast<QKeychain::ReadPasswordJob *>(incomingJob);
if (readJob) {
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
_chunkBuffer.append(readJob->binaryData());
_chunkCount++;
#if defined(Q_OS_WIN)
// try to fetch next chunk
if (_chunkCount < KeychainChunk::MaxChunks) {
const QString kck = AbstractCredentials::keychainKey(
_account->url().toString(),
_key + QString(".") + QString::number(_chunkCount),
_keychainMigration ? QString() : _account->id());
QKeychain::ReadPasswordJob *job = new QKeychain::ReadPasswordJob(_serviceName);
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(_insecureFallback);
job->setKey(kck);
connect(job, &QKeychain::Job::finished, this, &KeychainChunk::ReadJob::slotReadJobDone);
job->start();
readJob->deleteLater();
return;
} else {
qCWarning(lcKeychainChunk) << "Maximum chunk count for" << readJob->key() << "reached, ignoring after" << KeychainChunk::MaxChunks;
}
#endif
} else {
if (readJob->error() != QKeychain::Error::EntryNotFound ||
((readJob->error() == QKeychain::Error::EntryNotFound) && _chunkCount == 0)) {
_error = readJob->error();
_errorString = readJob->errorString();
qCWarning(lcKeychainChunk) << "Unable to read" << readJob->key() << "chunk" << QString::number(_chunkCount) << readJob->errorString();
}
}
readJob->deleteLater();
}
emit finished(this);
}
} // namespace KeychainChunk
} // namespace OCC

View File

@@ -0,0 +1,120 @@
/*
* 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
#ifndef KEYCHAINCHUNK_H
#define KEYCHAINCHUNK_H
#include <QObject>
#include <keychain.h>
#include "accountfwd.h"
// We don't support insecure fallback
// #define KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK
namespace OCC {
namespace KeychainChunk {
/*
* Workaround for Windows:
*
* Split the keychain entry's data into chunks of 2048 bytes,
* to allow 4k (4096 bit) keys / large certs to be saved (see limits in webflowcredentials.h)
*/
static constexpr int ChunkSize = 2048;
static constexpr int MaxChunks = 10;
/*
* @brief: Abstract base class for KeychainChunk jobs.
*/
class Job : public QObject {
Q_OBJECT
public:
Job(QObject *parent = nullptr);
const QKeychain::Error error() const {
return _error;
}
const QString errorString() const {
return _errorString;
}
QByteArray binaryData() const {
return _chunkBuffer;
}
const bool insecureFallback() const {
return _insecureFallback;
}
// If we use it but don't support insecure fallback, give us nice compilation errors ;p
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
void setInsecureFallback(const bool &insecureFallback)
{
_insecureFallback = insecureFallback;
}
#endif
protected:
QString _serviceName;
Account *_account;
QString _key;
bool _insecureFallback = false;
bool _keychainMigration = false;
QKeychain::Error _error = QKeychain::NoError;
QString _errorString;
int _chunkCount = 0;
QByteArray _chunkBuffer;
}; // class Job
/*
* @brief: Simple wrapper class for QKeychain::WritePasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class WriteJob : public KeychainChunk::Job {
Q_OBJECT
public:
WriteJob(Account *account, const QString &key, const QByteArray &data, QObject *parent = nullptr);
void start();
signals:
void finished(KeychainChunk::WriteJob *incomingJob);
private slots:
void slotWriteJobDone(QKeychain::Job *incomingJob);
}; // class WriteJob
/*
* @brief: Simple wrapper class for QKeychain::ReadPasswordJob, splits too large keychain entry's data into chunks on Windows
*/
class ReadJob : public KeychainChunk::Job {
Q_OBJECT
public:
ReadJob(Account *account, const QString &key, const bool &keychainMigration, QObject *parent = nullptr);
void start();
signals:
void finished(KeychainChunk::ReadJob *incomingJob);
private slots:
void slotReadJobDone(QKeychain::Job *incomingJob);
}; // class ReadJob
} // namespace KeychainChunk
} // namespace OCC
#endif // KEYCHAINCHUNK_H

View File

@@ -18,6 +18,7 @@
#include "theme.h"
#include "wizard/webview.h"
#include "webflowcredentialsdialog.h"
#include "keychainchunk.h"
using namespace QKeychain;
@@ -75,6 +76,7 @@ private:
QPointer<const WebFlowCredentials> _cred;
};
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
static void addSettingsToJob(Account *account, QKeychain::Job *job)
{
Q_UNUSED(account)
@@ -82,6 +84,7 @@ static void addSettingsToJob(Account *account, QKeychain::Job *job)
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
}
#endif
WebFlowCredentials::WebFlowCredentials()
: _ready(false)
@@ -147,25 +150,32 @@ void WebFlowCredentials::fetchFromKeychain() {
}
void WebFlowCredentials::askFromUser() {
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
bool useFlow2 = (_account->serverVersionInt() >= Account::makeServerVersion(16, 0, 0));
// Determine if the old flow has to be used (GS for now)
// Do a DetermineAuthTypeJob to make sure that the server is still using Flow2
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
connect(job, &DetermineAuthTypeJob::authType, [this](DetermineAuthTypeJob::AuthType type) {
// LoginFlowV2 > WebViewFlow > OAuth > Shib > Basic
bool useFlow2 = (type != DetermineAuthTypeJob::WebViewFlow);
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
_askDialog = new WebFlowCredentialsDialog(_account, useFlow2);
if (!useFlow2) {
QUrl url = _account->url();
QString path = url.path() + "/index.php/login/flow";
url.setPath(path);
_askDialog->setUrl(url);
}
if (!useFlow2) {
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")
.arg(_account->displayName(), _user);
_askDialog->setInfo(msg);
QString msg = tr("You have been logged out of %1 as user %2. Please login again")
.arg(_account->displayName(), _user);
_askDialog->setInfo(msg);
_askDialog->show();
_askDialog->show();
connect(_askDialog, &WebFlowCredentialsDialog::urlCatched, this, &WebFlowCredentials::slotAskFromUserCredentialsProvided);
connect(_askDialog, &WebFlowCredentialsDialog::urlCatched, this, &WebFlowCredentials::slotAskFromUserCredentialsProvided);
connect(_askDialog, &WebFlowCredentialsDialog::onClose, this, &WebFlowCredentials::slotAskFromUserCancelled);
});
job->start();
qCDebug(lcWebFlowCredentials()) << "User needs to reauth!";
}
@@ -199,10 +209,18 @@ void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user,
emit asked();
_askDialog->close();
delete _askDialog;
_askDialog->deleteLater();
_askDialog = nullptr;
}
void WebFlowCredentials::slotAskFromUserCancelled() {
qCDebug(lcWebFlowCredentials()) << "User cancelled reauth!";
emit asked();
_askDialog->deleteLater();
_askDialog = nullptr;
}
bool WebFlowCredentials::stillValid(QNetworkReply *reply) {
if (reply->error() != QNetworkReply::NoError) {
@@ -223,33 +241,32 @@ void WebFlowCredentials::persist() {
// 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());
auto *job = new KeychainChunk::WriteJob(_account,
_user + clientCertificatePEMC,
_clientSslCertificate.toPem());
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCertPEMJobDone);
job->start();
} else {
// no cert, just write credentials
slotWriteClientCertPEMJobDone();
slotWriteClientCertPEMJobDone(nullptr);
}
}
void WebFlowCredentials::slotWriteClientCertPEMJobDone()
void WebFlowCredentials::slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
if(writeJob)
writeJob->deleteLater();
// 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());
auto *job = new KeychainChunk::WriteJob(_account,
_user + clientKeyPEMC,
_clientSslKey.toPem());
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientKeyPEMJobDone);
job->start();
} else {
// no key, just write credentials
slotWriteClientKeyPEMJobDone();
slotWriteClientKeyPEMJobDone(nullptr);
}
}
@@ -264,7 +281,7 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
// 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";
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while writing slot" << QString::number(index) << "cutting off after" << QString::number(_clientSslCaCertificatesMaxCount) << "certs";
_clientSslCaCertificatesWriteQueue.clear();
@@ -272,20 +289,21 @@ void WebFlowCredentials::writeSingleClientCaCertPEM()
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());
auto *job = new KeychainChunk::WriteJob(_account,
_user + clientCaCertificatePEMC + QString::number(index),
cert.toPem());
connect(job, &KeychainChunk::WriteJob::finished, this, &WebFlowCredentials::slotWriteClientCaCertsPEMJobDone);
job->start();
} else {
slotWriteClientCaCertsPEMJobDone(nullptr);
}
}
void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
void WebFlowCredentials::slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
if(writeJob)
writeJob->deleteLater();
_clientSslCaCertificatesWriteQueue.clear();
// write ca certs if there are any
@@ -300,16 +318,16 @@ void WebFlowCredentials::slotWriteClientKeyPEMJobDone()
}
}
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob)
void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob)
{
// errors / next ca cert?
if (incomingJob && !_clientSslCaCertificates.isEmpty()) {
WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incomingJob);
if (writeJob && !_clientSslCaCertificates.isEmpty()) {
if (writeJob->error() != NoError) {
qCWarning(lcWebFlowCredentials) << "Error while writing client CA cert" << writeJob->errorString();
}
writeJob->deleteLater();
if (!_clientSslCaCertificatesWriteQueue.isEmpty()) {
// next ca cert
writeSingleClientCaCertPEM();
@@ -319,7 +337,9 @@ void WebFlowCredentials::slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomi
// done storing ca certs, time for the password
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(false);
connect(job, &Job::finished, this, &WebFlowCredentials::slotWriteJobDone);
job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
@@ -358,6 +378,8 @@ void WebFlowCredentials::forgetSensitiveData() {
fetchUser();
_account->deleteAppPassword();
const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
if (kck.isEmpty()) {
qCDebug(lcWebFlowCredentials()) << "InvalidateToken: User is empty, bailing out!";
@@ -367,18 +389,15 @@ void WebFlowCredentials::forgetSensitiveData() {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
djob->deleteLater();
});
job->start();
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();
deleteKeychainEntries();
}
void WebFlowCredentials::setAccount(Account *account) {
@@ -416,34 +435,31 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NoError) {
_credentialsValid = true;
/// Used later for remote wipe
_account->writeAppPasswordOnce(_password);
}
}
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);
auto *job = new KeychainChunk::ReadJob(_account,
_user + clientCertificatePEMC,
_keychainMigration);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCertPEMJobDone);
job->start();
}
void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob)
void WebFlowCredentials::slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob)
{
#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)) {
Q_ASSERT(!readJob->insecureFallback()); // If insecureFallback is set, the next test would be pointless
if (_retryOnKeyChainError && (readJob->error() == QKeychain::NoBackendAvailable
|| readJob->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();
qCInfo(lcWebFlowCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << readJob->errorString();
QTimer::singleShot(10000, this, &WebFlowCredentials::fetchFromKeychainHelper);
_retryOnKeyChainError = false;
return;
@@ -452,7 +468,6 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
#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) {
@@ -460,25 +475,19 @@ void WebFlowCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incomingJo
}
}
// Load key too
const QString kck = keychainKey(
_account->url().toString(),
_user + clientKeyPEMC,
_keychainMigration ? QString() : _account->id());
readJob->deleteLater();
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
// Load key too
auto *job = new KeychainChunk::ReadJob(_account,
_user + clientKeyPEMC,
_keychainMigration);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientKeyPEMJobDone);
job->start();
}
void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob)
void WebFlowCredentials::slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob)
{
// 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
@@ -493,8 +502,13 @@ void WebFlowCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob
if (_clientSslKey.isNull()) {
qCWarning(lcWebFlowCredentials) << "Could not load SSL key into Qt!";
}
clientKeyPEM.clear();
} else {
qCWarning(lcWebFlowCredentials) << "Unable to read client key" << readJob->errorString();
}
readJob->deleteLater();
// Start fetching client CA certs
_clientSslCaCertificates.clear();
@@ -505,28 +519,20 @@ 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);
auto *job = new KeychainChunk::ReadJob(_account,
_user + clientCaCertificatePEMC + QString::number(_clientSslCaCertificates.count()),
_keychainMigration);
connect(job, &KeychainChunk::ReadJob::finished, this, &WebFlowCredentials::slotReadClientCaCertsPEMJobDone);
job->start();
} else {
qCWarning(lcWebFlowCredentials) << "Maximum client CA cert count exceeded while reading, ignoring after " << _clientSslCaCertificatesMaxCount;
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);
void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob) {
// Store cert in memory
if (readJob) {
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
QList<QSslCertificate> sslCertificateList = QSslCertificate::fromData(readJob->binaryData(), QSsl::Pem);
@@ -534,15 +540,19 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
_clientSslCaCertificates.append(sslCertificateList.at(0));
}
readJob->deleteLater();
// 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();
qCWarning(lcWebFlowCredentials) << "Unable to read client CA cert slot" << QString::number(_clientSslCaCertificates.count()) << readJob->errorString();
}
}
readJob->deleteLater();
}
// Now fetch the actual server password
@@ -552,7 +562,9 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
#endif
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &Job::finished, this, &WebFlowCredentials::slotReadPasswordJobDone);
@@ -560,7 +572,7 @@ void WebFlowCredentials::slotReadClientCaCertsPEMJobDone(QKeychain::Job *incomin
}
void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob);
QKeychain::ReadPasswordJob *job = qobject_cast<ReadPasswordJob *>(incomingJob);
QKeychain::Error error = job->error();
// If we could not find the entry try the old entries
@@ -583,6 +595,8 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
}
emit fetched();
job->deleteLater();
// If keychain data was read from legacy location, wipe these entries and store new ones
if (_keychainMigration && _ready) {
_keychainMigration = false;
@@ -593,23 +607,60 @@ void WebFlowCredentials::slotReadPasswordJobDone(Job *incomingJob) {
}
void WebFlowCredentials::deleteKeychainEntries(bool oldKeychainEntries) {
auto startDeleteJob = [this, oldKeychainEntries](QString user) {
auto startDeleteJob = [this, oldKeychainEntries](QString key) {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
#if defined(KEYCHAINCHUNK_ENABLE_INSECURE_FALLBACK)
addSettingsToJob(_account, job);
job->setInsecureFallback(true);
#endif
job->setInsecureFallback(false);
job->setKey(keychainKey(_account->url().toString(),
user,
key,
oldKeychainEntries ? QString() : _account->id()));
connect(job, &Job::finished, this, [](QKeychain::Job *job) {
DeletePasswordJob *djob = qobject_cast<DeletePasswordJob *>(job);
djob->deleteLater();
});
job->start();
};
startDeleteJob(_user);
startDeleteJob(_user + clientKeyPEMC);
startDeleteJob(_user + clientCertificatePEMC);
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* 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.
*
* We introduce this dirty hack here, to allow deleting them upon Remote Wipe.
*/
if(_account->isRemoteWipeRequested_HACK()) {
// <-- FIXME MS@2019-12-07
startDeleteJob(_user + clientKeyPEMC);
startDeleteJob(_user + clientCertificatePEMC);
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
}
#if defined(Q_OS_WIN)
// Also delete key / cert sub-chunks (Windows workaround)
// The first chunk (0) has no suffix, to stay compatible with older versions and non-Windows
for (auto chunk = 1; chunk < KeychainChunk::MaxChunks; chunk++) {
const QString strChunkSuffix = QString(".") + QString::number(chunk);
startDeleteJob(_user + clientKeyPEMC + strChunkSuffix);
startDeleteJob(_user + clientCertificatePEMC + strChunkSuffix);
for (auto i = 0; i < _clientSslCaCertificates.count(); i++) {
startDeleteJob(_user + clientCaCertificatePEMC + QString::number(i));
}
}
#endif
// FIXME MS@2019-12-07 -->
}
// <-- FIXME MS@2019-12-07
}
}
} // namespace OCC

View File

@@ -19,6 +19,11 @@ namespace QKeychain {
namespace OCC {
namespace KeychainChunk {
class ReadJob;
class WriteJob;
}
class WebFlowCredentialsDialog;
class WebFlowCredentials : public AbstractCredentials
@@ -61,15 +66,16 @@ private slots:
void slotFinished(QNetworkReply *reply);
void slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host);
void slotAskFromUserCancelled();
void slotReadClientCertPEMJobDone(QKeychain::Job *incomingJob);
void slotReadClientKeyPEMJobDone(QKeychain::Job *incomingJob);
void slotReadClientCaCertsPEMJobDone(QKeychain::Job *incommingJob);
void slotReadClientCertPEMJobDone(KeychainChunk::ReadJob *readJob);
void slotReadClientKeyPEMJobDone(KeychainChunk::ReadJob *readJob);
void slotReadClientCaCertsPEMJobDone(KeychainChunk::ReadJob *readJob);
void slotReadPasswordJobDone(QKeychain::Job *incomingJob);
void slotWriteClientCertPEMJobDone();
void slotWriteClientKeyPEMJobDone();
void slotWriteClientCaCertsPEMJobDone(QKeychain::Job *incomingJob);
void slotWriteClientCertPEMJobDone(KeychainChunk::WriteJob *writeJob);
void slotWriteClientKeyPEMJobDone(KeychainChunk::WriteJob *writeJob);
void slotWriteClientCaCertsPEMJobDone(KeychainChunk::WriteJob *writeJob);
void slotWriteJobDone(QKeychain::Job *);
private:
@@ -83,7 +89,7 @@ private:
void writeSingleClientCaCertPEM();
/*
* Since we're limited by Windows limits we just create our own
* 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
@@ -121,6 +127,6 @@ protected:
WebFlowCredentialsDialog *_askDialog;
};
}
} // namespace OCC
#endif // WEBFLOWCREDENTIALS_H

View File

@@ -4,6 +4,9 @@
#include <QLabel>
#include "theme.h"
#include "application.h"
#include "owncloudgui.h"
#include "headerbanner.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/webview.h"
#include "wizard/flow2authwidget.h"
@@ -19,31 +22,59 @@ WebFlowCredentialsDialog::WebFlowCredentialsDialog(Account *account, bool useFlo
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
_layout = new QVBoxLayout(this);
int spacing = _layout->spacing();
int margin = _layout->margin();
_layout->setSpacing(0);
_layout->setMargin(0);
if(_useFlow2) {
_headerBanner = new HeaderBanner(this);
_layout->addWidget(_headerBanner);
Theme *theme = Theme::instance();
_headerBanner->setup(tr("Log in"), theme->wizardHeaderLogo(), theme->wizardHeaderBanner(),
Qt::AutoText, QString::fromLatin1("color:#fff;"));
}
_containerLayout = new QVBoxLayout(this);
_containerLayout->setSpacing(spacing);
_containerLayout->setMargin(margin);
//QString msg = tr("You have been logged out of %1 as user %2, please login again")
// .arg(_account->displayName(), _user);
_infoLabel = new QLabel();
_layout->addWidget(_infoLabel);
_containerLayout->addWidget(_infoLabel);
if (_useFlow2) {
_flow2AuthWidget = new Flow2AuthWidget(account);
_layout->addWidget(_flow2AuthWidget);
_flow2AuthWidget = new Flow2AuthWidget();
_containerLayout->addWidget(_flow2AuthWidget);
connect(_flow2AuthWidget, &Flow2AuthWidget::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &WebFlowCredentialsDialog::slotFlow2AuthResult);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &WebFlowCredentialsDialog::styleChanged, _flow2AuthWidget, &Flow2AuthWidget::slotStyleChanged);
// allow Flow2 page to poll on window activation
connect(this, &WebFlowCredentialsDialog::onActivate, _flow2AuthWidget, &Flow2AuthWidget::slotPollNow);
_flow2AuthWidget->startAuth(account);
} else {
_webView = new WebView();
_layout->addWidget(_webView);
_containerLayout->addWidget(_webView);
connect(_webView, &WebView::urlCatched, this, &WebFlowCredentialsDialog::urlCatched);
}
auto app = static_cast<Application *>(qApp);
connect(app, &Application::isShowingSettingsDialog, this, &WebFlowCredentialsDialog::slotShowSettingsDialog);
_errorLabel = new QLabel();
_errorLabel->hide();
_layout->addWidget(_errorLabel);
_containerLayout->addWidget(_errorLabel);
WizardCommon::initErrorLabel(_errorLabel);
_layout->addLayout(_containerLayout);
setLayout(_layout);
customizeStyle();
}
void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
@@ -52,11 +83,17 @@ void WebFlowCredentialsDialog::closeEvent(QCloseEvent* e) {
if (_webView) {
// Force calling WebView::~WebView() earlier so that _profile and _page are
// deleted in the correct order.
delete _webView;
_webView->deleteLater();
_webView = nullptr;
}
if (_flow2AuthWidget)
delete _flow2AuthWidget;
if (_flow2AuthWidget) {
_flow2AuthWidget->resetAuth();
_flow2AuthWidget->deleteLater();
_flow2AuthWidget = nullptr;
}
emit onClose();
}
void WebFlowCredentialsDialog::setUrl(const QUrl &url) {
@@ -69,6 +106,9 @@ void WebFlowCredentialsDialog::setInfo(const QString &msg) {
}
void WebFlowCredentialsDialog::setError(const QString &error) {
// bring window to top
slotShowSettingsDialog();
if (_useFlow2 && _flow2AuthWidget) {
_flow2AuthWidget->setError(error);
return;
@@ -82,4 +122,49 @@ void WebFlowCredentialsDialog::setError(const QString &error) {
}
}
void WebFlowCredentialsDialog::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::StyleChange:
case QEvent::PaletteChange:
case QEvent::ThemeChange:
customizeStyle();
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
break;
case QEvent::ActivationChange:
if(isActiveWindow())
emit onActivate();
break;
default:
break;
}
QDialog::changeEvent(e);
}
void WebFlowCredentialsDialog::customizeStyle()
{
// HINT: Customize dialog's own style here, if necessary in the future (Dark-/Light-Mode switching)
}
void WebFlowCredentialsDialog::slotShowSettingsDialog()
{
// bring window to top but slightly delay, to avoid being hidden behind the SettingsDialog
QTimer::singleShot(100, this, [this] {
ownCloudGui::raiseDialog(this);
});
}
void WebFlowCredentialsDialog::slotFlow2AuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword)
{
if(r == Flow2Auth::LoggedIn) {
emit urlCatched(user, appPassword, QString());
} else {
// bring window to top
slotShowSettingsDialog();
}
}
} // namespace OCC

View File

@@ -5,12 +5,14 @@
#include <QUrl>
#include "accountfwd.h"
#include "creds/flow2auth.h"
class QLabel;
class QVBoxLayout;
namespace OCC {
class HeaderBanner;
class WebView;
class Flow2AuthWidget;
@@ -30,11 +32,21 @@ public:
protected:
void closeEvent(QCloseEvent * e) override;
void changeEvent(QEvent *) override;
public slots:
void slotFlow2AuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword);
void slotShowSettingsDialog();
signals:
void urlCatched(const QString user, const QString pass, const QString host);
void styleChanged();
void onActivate();
void onClose();
private:
void customizeStyle();
bool _useFlow2;
Flow2AuthWidget *_flow2AuthWidget;
@@ -43,8 +55,10 @@ private:
QLabel *_errorLabel;
QLabel *_infoLabel;
QVBoxLayout *_layout;
QVBoxLayout *_containerLayout;
HeaderBanner *_headerBanner;
};
}
} // namespace OCC
#endif // WEBFLOWCREDENTIALSDIALOG_H

View File

@@ -902,13 +902,19 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item)
}
// add new directories or remove gone away dirs to the watcher
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_NEW) {
if (_folderWatcher)
_folderWatcher->addPath(path() + item->_file);
}
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
if (_folderWatcher)
_folderWatcher->removePath(path() + item->_file);
if (_folderWatcher && item->isDirectory()) {
switch (item->_instruction) {
case CSYNC_INSTRUCTION_NEW:
_folderWatcher->addPath(path() + item->_file);
break;
case CSYNC_INSTRUCTION_REMOVE:
_folderWatcher->removePath(path() + item->_file);
break;
case CSYNC_INSTRUCTION_RENAME:
_folderWatcher->removePath(path() + item->_file);
_folderWatcher->addPath(path() + item->destination());
break;
}
}
// Success and failure of sync items adjust what the next sync is

View File

@@ -75,7 +75,7 @@ FolderMan::FolderMan(QObject *parent)
this, &FolderMan::slotScheduleFolderByTime);
_timeScheduler.start();
connect(AccountManager::instance(), &AccountManager::accountRemoved,
connect(AccountManager::instance(), &AccountManager::removeAccountFolders,
this, &FolderMan::slotRemoveFoldersForAccount);
connect(_lockWatcher.data(), &LockWatcher::fileUnlocked,
@@ -443,7 +443,7 @@ void FolderMan::slotFolderSyncPaused(Folder *f, bool paused)
void FolderMan::slotFolderCanSyncChanged()
{
Folder *f = qobject_cast<Folder *>(sender());
ASSERT(f);
ASSERT(f);
if (f->canSync()) {
_socketApi->slotRegisterPath(f->alias());
} else {
@@ -1084,6 +1084,73 @@ bool FolderMan::startFromScratch(const QString &localFolder)
return true;
}
void FolderMan::slotWipeFolderForAccount(AccountState *accountState)
{
QVarLengthArray<Folder *, 16> foldersToRemove;
Folder::MapIterator i(_folderMap);
while (i.hasNext()) {
i.next();
Folder *folder = i.value();
if (folder->accountState() == accountState) {
foldersToRemove.append(folder);
}
}
bool success = false;
foreach (const auto &f, foldersToRemove) {
if (!f) {
qCCritical(lcFolderMan) << "Can not remove null folder";
return;
}
qCInfo(lcFolderMan) << "Removing " << f->alias();
const bool currentlyRunning = (_currentSyncFolder == f);
if (currentlyRunning) {
// abort the sync now
terminateSyncProcess();
}
if (_scheduledFolders.removeAll(f) > 0) {
emit scheduleQueueChanged();
}
// wipe database
f->wipe();
// wipe data
QDir userFolder(f->path());
if (userFolder.exists()) {
success = userFolder.removeRecursively();
if (!success) {
qCWarning(lcFolderMan) << "Failed to remove existing folder " << f->path();
} else {
qCInfo(lcFolderMan) << "wipe: Removed file " << f->path();
}
} else {
success = true;
qCWarning(lcFolderMan) << "folder does not exist, can not remove.";
}
f->setSyncPaused(true);
// remove the folder configuration
f->removeFromSettings();
unloadFolder(f);
if (currentlyRunning) {
delete f;
}
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
}
emit folderListChanged(_folderMap);
emit wipeDone(accountState, success);
}
void FolderMan::setDirtyProxy()
{
foreach (Folder *f, _folderMap.values()) {

View File

@@ -207,6 +207,11 @@ signals:
*/
void folderListChanged(const Folder::Map &);
/**
* Emitted once slotRemoveFoldersForAccount is done wiping
*/
void wipeDone(AccountState *account, bool success);
public slots:
/**
@@ -231,6 +236,9 @@ public slots:
// slot to schedule an ETag job (from Folder only)
void slotScheduleETagJob(const QString &alias, RequestEtagJob *job);
/** Wipe folder */
void slotWipeFolderForAccount(AccountState *accountState);
private slots:
void slotFolderSyncPaused(Folder *, bool paused);
void slotFolderCanSyncChanged();

View File

@@ -40,7 +40,7 @@ namespace OCC {
FolderStatusDelegate::FolderStatusDelegate()
: QStyledItemDelegate()
{
m_moreIcon = QIcon(QLatin1String(":/client/resources/more.svg"));
customizeStyle();
}
QString FolderStatusDelegate::addFolderText()
@@ -273,6 +273,11 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
rect.setHeight(texts.count() * subFm.height() + 2 * margin);
rect.setRight(option.rect.right() - margin);
// save previous state to not mess up colours with the background (fixes issue: https://github.com/nextcloud/desktop/issues/1237)
auto oldBrush = painter->brush();
auto oldPen = painter->pen();
auto oldFont = painter->font();
painter->setBrush(color);
painter->setPen(QColor(0xaa, 0xaa, 0xaa));
painter->drawRoundedRect(QStyle::visualRect(option.direction, option.rect, rect),
@@ -290,6 +295,11 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
textRect.translate(0, textRect.height());
}
// restore previous state
painter->setBrush(oldBrush);
painter->setPen(oldPen);
painter->setFont(oldFont);
h = rect.bottom() + margin;
};
@@ -349,7 +359,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
btnOpt.arrowType = Qt::NoArrow;
btnOpt.subControls = QStyle::SC_ToolButton;
btnOpt.rect = optionsButtonVisualRect;
btnOpt.icon = m_moreIcon;
btnOpt.icon = _iconMore;
int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize);
btnOpt.iconSize = QSize(e,e);
QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter);
@@ -423,5 +433,14 @@ QRect FolderStatusDelegate::errorsListRect(QRect within)
return within;
}
void FolderStatusDelegate::slotStyleChanged()
{
customizeStyle();
}
void FolderStatusDelegate::customizeStyle()
{
_iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"));
}
} // namespace OCC

View File

@@ -26,7 +26,6 @@ class FolderStatusDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
QIcon m_moreIcon;
FolderStatusDelegate();
enum datarole { FolderAliasRole = Qt::UserRole + 100,
@@ -62,9 +61,16 @@ public:
static QRect errorsListRect(QRect within);
static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm);
public slots:
void slotStyleChanged();
private:
void customizeStyle();
static QString addFolderText();
QPersistentModelIndex _pressedIndex;
QIcon _iconMore;
};
} // namespace OCC

View File

@@ -186,7 +186,7 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
return QVariant(tr("Error while loading the list of folders from the server.")
+ QString("\n") + x->_lastErrorString);
} else {
return tr("Fetching folder list from server...");
return tr("Fetching folder list from server");
}
break;
default:
@@ -1099,15 +1099,15 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
}
QString message;
if (pos <= 0) {
message = tr("Waiting...");
message = tr("Waiting");
} else {
message = tr("Waiting for %n other folder(s)...", "", pos);
message = tr("Waiting for %n other folder(s)", "", pos);
}
pi = SubFolderInfo::Progress();
pi._overallSyncString = message;
} else if (state == SyncResult::SyncPrepare) {
pi = SubFolderInfo::Progress();
pi._overallSyncString = tr("Preparing to sync...");
pi._overallSyncString = tr("Preparing to sync");
} else if (state == SyncResult::Problem || state == SyncResult::Success) {
// Reset the progress info after a sync.
pi = SubFolderInfo::Progress();

View File

@@ -57,6 +57,13 @@ GeneralSettings::GeneralSettings(QWidget *parent)
connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane);
// Rename 'Explorer' appropriately on non-Windows
#ifdef Q_OS_MAC
QString txt = _ui->showInExplorerNavigationPaneCheckBox->text();
txt.replace(QString::fromLatin1("Explorer"), QString::fromLatin1("Finder"));
_ui->showInExplorerNavigationPaneCheckBox->setText(txt);
#endif
_ui->autostartCheckBox->setChecked(Utility::hasLaunchOnStartup(Theme::instance()->appName()));
connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotToggleLaunchOnStartup);
@@ -70,7 +77,8 @@ GeneralSettings::GeneralSettings(QWidget *parent)
connect(_ui->legalNoticeButton, &QPushButton::clicked, this, &GeneralSettings::slotShowLegalNotice);
loadMiscSettings();
slotUpdateInfo();
// updater info now set in: customizeStyle
//slotUpdateInfo();
// misc
connect(_ui->monoIconsCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::saveMiscSettings);
@@ -109,6 +117,8 @@ GeneralSettings::GeneralSettings(QWidget *parent)
// accountAdded means the wizard was finished and the wizard might change some options.
connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings);
customizeStyle();
}
GeneralSettings::~GeneralSettings()
@@ -149,7 +159,11 @@ void GeneralSettings::slotUpdateInfo()
connect(updater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection);
connect(_ui->restartButton, &QAbstractButton::clicked, updater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection);
connect(_ui->restartButton, &QAbstractButton::clicked, qApp, &QApplication::quit, Qt::UniqueConnection);
_ui->updateStateLabel->setText(updater->statusString());
QString status = updater->statusString();
Theme::replaceLinkColorStringBackgroundAware(status);
_ui->updateStateLabel->setText(status);
_ui->restartButton->setVisible(updater->downloadState() == OCUpdater::DownloadComplete);
} else {
// can't have those infos from sparkle currently
@@ -211,4 +225,20 @@ void GeneralSettings::slotShowLegalNotice()
delete notice;
}
void GeneralSettings::slotStyleChanged()
{
customizeStyle();
}
void GeneralSettings::customizeStyle()
{
// setup about section
QString about = Theme::instance()->about();
Theme::replaceLinkColorStringBackgroundAware(about);
_ui->aboutLabel->setText(about);
// updater info
slotUpdateInfo();
}
} // namespace OCC

View File

@@ -39,6 +39,9 @@ public:
~GeneralSettings();
QSize sizeHint() const override;
public slots:
void slotStyleChanged();
private slots:
void saveMiscSettings();
void slotToggleLaunchOnStartup(bool);
@@ -50,6 +53,8 @@ private slots:
void slotShowLegalNotice();
private:
void customizeStyle();
Ui::GeneralSettings *_ui;
QPointer<IgnoreListEditor> _ignoreEditor;
QPointer<SyncLogDialog> _syncLogDialog;

146
src/gui/headerbanner.cpp Normal file
View File

@@ -0,0 +1,146 @@
/*
* 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.
*/
/****************************************************************************
**
** Based on Qt sourcecode:
** qt5/qtbase/src/widgets/dialogs/qwizard.cpp
**
** https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/dialogs/qwizard.cpp?h=v5.13.0
**
** Original license:
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWidgets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "headerbanner.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QStyle>
#include <QGuiApplication>
namespace OCC {
// These fudge terms were needed a few places to obtain pixel-perfect results
const int GapBetweenLogoAndRightEdge = 5;
const int ModernHeaderTopMargin = 2;
HeaderBanner::HeaderBanner(QWidget *parent)
: QWidget(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setBackgroundRole(QPalette::Base);
titleLabel = new QLabel(this);
titleLabel->setBackgroundRole(QPalette::Base);
logoLabel = new QLabel(this);
QFont font = titleLabel->font();
font.setBold(true);
titleLabel->setFont(font);
layout = new QGridLayout(this);
layout->setContentsMargins(QMargins());
layout->setSpacing(0);
layout->setRowMinimumHeight(3, 1);
layout->setRowStretch(4, 1);
layout->setColumnStretch(2, 1);
layout->setColumnMinimumWidth(4, 2 * GapBetweenLogoAndRightEdge);
layout->setColumnMinimumWidth(6, GapBetweenLogoAndRightEdge);
layout->addWidget(titleLabel, 1, 1, 5, 1);
layout->addWidget(logoLabel, 1, 5, 5, 1);
}
void HeaderBanner::setup(const QString &title, const QPixmap &logo, const QPixmap &banner,
const Qt::TextFormat titleFormat, const QString &styleSheet)
{
QStyle *style = parentWidget()->style();
//const int layoutHorizontalSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
int topLevelMarginLeft = style->pixelMetric(QStyle::PM_LayoutLeftMargin, 0, parentWidget());
int topLevelMarginRight = style->pixelMetric(QStyle::PM_LayoutRightMargin, 0, parentWidget());
int topLevelMarginTop = style->pixelMetric(QStyle::PM_LayoutTopMargin, 0, parentWidget());
//int topLevelMarginBottom = style->pixelMetric(QStyle::PM_LayoutBottomMargin, 0, parentWidget());
layout->setRowMinimumHeight(0, ModernHeaderTopMargin);
layout->setRowMinimumHeight(1, topLevelMarginTop - ModernHeaderTopMargin - 1);
layout->setRowMinimumHeight(6, 3);
int minColumnWidth0 = topLevelMarginLeft + topLevelMarginRight;
int minColumnWidth1 = topLevelMarginLeft + topLevelMarginRight + 1;
layout->setColumnMinimumWidth(0, minColumnWidth0);
layout->setColumnMinimumWidth(1, minColumnWidth1);
titleLabel->setTextFormat(titleFormat);
titleLabel->setText(title);
if(!styleSheet.isEmpty())
titleLabel->setStyleSheet(styleSheet);
logoLabel->setPixmap(logo);
bannerPixmap = banner;
if (bannerPixmap.isNull()) {
QSize size = layout->totalMinimumSize();
setMinimumSize(size);
setMaximumSize(QWIDGETSIZE_MAX, size.height());
} else {
setFixedHeight(banner.height() + 2);
}
updateGeometry();
}
void HeaderBanner::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.drawPixmap(0, 0, width(), bannerPixmap.height(), bannerPixmap);
int x = width() - 2;
int y = height() - 2;
const QPalette &pal = QGuiApplication::palette();
painter.setPen(pal.mid().color());
painter.drawLine(0, y, x, y);
painter.setPen(pal.base().color());
painter.drawPoint(x + 1, y);
painter.drawLine(0, y + 1, x + 1, y + 1);
}
} // namespace OCC

93
src/gui/headerbanner.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
/****************************************************************************
**
** Based on Qt sourcecode:
** qt5/qtbase/src/widgets/dialogs/qwizard.cpp
**
** https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/dialogs/qwizard.cpp?h=v5.13.0
**
** Original license:
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtWidgets module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef HEADERBANNER_H
#define HEADERBANNER_H
#include <QWidget>
class QLabel;
class QGridLayout;
class QPixmap;
namespace OCC {
class HeaderBanner : public QWidget
{
Q_OBJECT
public:
HeaderBanner(QWidget *parent = 0);
void setup(const QString &title, const QPixmap &logo, const QPixmap &banner,
const Qt::TextFormat titleFormat, const QString &styleSheet);
protected:
void paintEvent(QPaintEvent *event) override;
private:
QLabel *titleLabel;
QLabel *logoLabel;
QGridLayout *layout;
QPixmap bannerPixmap;
};
} // namespace OCC
#endif // HEADERBANNER_H

View File

@@ -24,7 +24,7 @@ LegalNotice::LegalNotice(QDialog *parent)
{
_ui->setupUi(this);
QString notice = tr("<p>Copyright 2017-2018 Nextcloud GmbH<br />"
QString notice = tr("<p>Copyright 2017-2019 Nextcloud GmbH<br />"
"Copyright 2012-2018 ownCloud GmbH</p>");
notice += tr("<p>Licensed under the GNU General Public License (GPL) Version 2.0 or any later version.</p>");

View File

@@ -20,12 +20,7 @@
#include "progressdispatcher.h"
#include "owncloudsetupwizard.h"
#include "sharedialog.h"
#if defined(Q_OS_MAC)
#include "settingsdialogmac.h"
#include "macwindow.h" // qtmacgoodies
#else
#include "settingsdialog.h"
#endif
#include "logger.h"
#include "logbrowser.h"
#include "account.h"
@@ -62,11 +57,7 @@ const char propertyAccountC[] = "oc_account";
ownCloudGui::ownCloudGui(Application *parent)
: QObject(parent)
, _tray(nullptr)
#if defined(Q_OS_MAC)
, _settingsDialog(new SettingsDialogMac(this))
#else
, _settingsDialog(new SettingsDialog(this))
#endif
, _logBrowser(nullptr)
#ifdef WITH_LIBCLOUDPROVIDERS
, _bus(QDBusConnection::sessionBus())
@@ -421,7 +412,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
}
if (accountState->isSignedOut()) {
QAction *signin = menu->addAction(tr("Log in..."));
QAction *signin = menu->addAction(tr("Log in"));
signin->setProperty(propertyAccountC, QVariant::fromValue(accountState));
connect(signin, &QAction::triggered, this, &ownCloudGui::slotLogin);
} else {
@@ -786,9 +777,9 @@ void ownCloudGui::setupActions()
_actionStatus->setEnabled(false);
_navLinksMenu = new QMenu(tr("Apps"));
_navLinksMenu->setEnabled(false);
_actionSettings = new QAction(tr("Settings..."), this);
_actionNewAccountWizard = new QAction(tr("New account..."), this);
_actionRecent = new QAction(tr("View more activity..."), this);
_actionSettings = new QAction(tr("Settings"), this);
_actionNewAccountWizard = new QAction(tr("New account"), this);
_actionRecent = new QAction(tr("View more activity"), this);
_actionRecent->setEnabled(true);
QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol);
@@ -827,6 +818,10 @@ void ownCloudGui::fetchNavigationApps(AccountStatePtr account){
void ownCloudGui::buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu){
auto navLinks = _navApps.value(account);
_navLinksMenu->clear();
_navLinksMenu->setEnabled(navLinks.size() > 0);
if(navLinks.size() > 0){
// when there is only one account add the nav links above the settings
QAction *actionBefore = _actionSettings;
@@ -853,7 +848,6 @@ void ownCloudGui::buildNavigationAppsMenu(AccountStatePtr account, QMenu *accoun
connect(action, &QAction::triggered, this, [href] { QDesktopServices::openUrl(href); });
_navLinksMenu->addAction(action);
}
_navLinksMenu->setEnabled(true);
}
}
@@ -940,7 +934,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
quint64 totalFileCount = qMax(progress.totalFiles(), currentFile);
QString msg;
if (progress.trustEta()) {
msg = tr("Syncing %1 of %2 (%3 left)")
msg = tr("Syncing %1 of %2 (%3 left)")
.arg(currentFile)
.arg(totalFileCount)
.arg(Utility::durationToDescriptiveString2(progress.totalProgress().estimatedEta));
@@ -1074,18 +1068,18 @@ void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &messag
void ownCloudGui::slotShowSettings()
{
if (_settingsDialog.isNull()) {
_settingsDialog =
#if defined(Q_OS_MAC)
new SettingsDialogMac(this);
#else
new SettingsDialog(this);
#endif
_settingsDialog = new SettingsDialog(this);
_settingsDialog->setAttribute(Qt::WA_DeleteOnClose, true);
_settingsDialog->show();
}
raiseDialog(_settingsDialog.data());
}
void ownCloudGui::slotSettingsDialogActivated()
{
emit isShowingSettingsDialog();
}
void ownCloudGui::slotShowSyncProtocol()
{
slotShowSettings();
@@ -1140,10 +1134,6 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
raiseWidget->raise();
raiseWidget->activateWindow();
#if defined(Q_OS_MAC)
// viel hilft viel ;-)
MacWindow::bringToFront(raiseWidget);
#endif
#if defined(Q_OS_X11)
WId wid = widget->winId();
NETWM::init();

View File

@@ -34,7 +34,6 @@ namespace OCC {
class Folder;
class SettingsDialog;
class SettingsDialogMac;
class ShareDialog;
class Application;
class LogBrowser;
@@ -71,6 +70,7 @@ public:
signals:
void setupProxy();
void serverError(int code, const QString &message);
void isShowingSettingsDialog();
public slots:
void setupContextMenu();
@@ -94,6 +94,7 @@ public slots:
void slotToggleLogBrowser();
void slotOpenOwnCloud();
void slotOpenSettingsDialog();
void slotSettingsDialogActivated();
void slotHelp();
void slotOpenPath(const QString &path);
void slotAccountStateChanged();
@@ -131,11 +132,7 @@ private:
void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu);
QPointer<Systray> _tray;
#if defined(Q_OS_MAC)
QPointer<SettingsDialogMac> _settingsDialog;
#else
QPointer<SettingsDialog> _settingsDialog;
#endif
QPointer<LogBrowser> _logBrowser;
// tray's menu
QScopedPointer<QMenu> _contextMenu;

View File

@@ -331,7 +331,7 @@ void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
AbstractCredentials *creds = _ocWizard->getCredentials();
_ocWizard->account()->setCredentials(creds);
_ocWizard->setField(QLatin1String("OCUrl"), url);
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2...")
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2")
.arg(Theme::instance()->appNameGUI())
.arg(url));
@@ -407,7 +407,8 @@ void OwncloudSetupWizard::slotAuthError()
errorMsg = tr("There was an invalid response to an authenticated webdav request");
}
_ocWizard->show();
// bring wizard to top
_ocWizard->bringToTop();
if (_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds || _ocWizard->currentId() == WizardCommon::Page_OAuthCreds || _ocWizard->currentId() == WizardCommon::Page_Flow2AuthCreds) {
_ocWizard->back();
}
@@ -452,7 +453,7 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString &localFo
tr("Local sync folder %1 already exists, setting it up for sync.<br/><br/>")
.arg(Utility::escape(localFolder)));
} else {
QString res = tr("Creating local sync folder %1...").arg(localFolder);
QString res = tr("Creating local sync folder %1").arg(localFolder);
if (fi.mkpath(localFolder)) {
FileSystem::setFolderMinimumPermissions(localFolder);
Utility::setupFavLink(localFolder);

173
src/gui/remotewipe.cpp Normal file
View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) by Camila Ayres <hello@camila.codes>
*
* 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 "remotewipe.h"
#include "folderman.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkRequest>
#include <QBuffer>
namespace OCC {
Q_LOGGING_CATEGORY(lcRemoteWipe, "nextcloud.gui.remotewipe", QtInfoMsg)
RemoteWipe::RemoteWipe(AccountPtr account, QObject *parent)
: _account(account),
_appPassword(QString()),
_accountRemoved(false),
_networkManager(nullptr),
_networkReplyCheck(nullptr),
_networkReplySuccess(nullptr)
{
QObject::connect(AccountManager::instance(), &AccountManager::accountRemoved,
this, [=](AccountState *) {
_accountRemoved = true;
});
QObject::connect(this, &RemoteWipe::authorized, FolderMan::instance(),
&FolderMan::slotWipeFolderForAccount);
QObject::connect(FolderMan::instance(), &FolderMan::wipeDone, this,
&RemoteWipe::notifyServerSuccessJob);
QObject::connect(_account.data(), &Account::appPasswordRetrieved, this,
&RemoteWipe::startCheckJobWithAppPassword);
}
void RemoteWipe::startCheckJobWithAppPassword(QString pwd){
if(pwd.isEmpty())
return;
_appPassword = pwd;
QUrl requestUrl = Utility::concatUrlPath(_account->url().toString(),
QLatin1String("/index.php/core/wipe/check"));
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
request.setUrl(requestUrl);
request.setSslConfiguration(_account->getOrCreateSslConfig());
auto requestBody = new QBuffer;
QUrlQuery arguments(QString("token=%1").arg(_appPassword));
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
_networkReplyCheck = _networkManager.post(request, requestBody);
QObject::connect(&_networkManager, SIGNAL(sslErrors(QNetworkReply *, QList<QSslError>)),
_account.data(), SLOT(slotHandleSslErrors(QNetworkReply *, QList<QSslError>)));
QObject::connect(_networkReplyCheck, &QNetworkReply::finished, this,
&RemoteWipe::checkJobSlot);
}
void RemoteWipe::checkJobSlot()
{
auto jsonData = _networkReplyCheck->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
bool wipe = false;
//check for errors
if (_networkReplyCheck->error() != QNetworkReply::NoError ||
jsonParseError.error != QJsonParseError::NoError) {
QString errorReason;
QString errorFromJson = json["error"].toString();
if (!errorFromJson.isEmpty()) {
qCWarning(lcRemoteWipe) << QString("Error returned from the server: <em>%1<em>")
.arg(errorFromJson.toHtmlEscaped());
} else if (_networkReplyCheck->error() != QNetworkReply::NoError) {
qCWarning(lcRemoteWipe) << QString("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
.arg(_networkReplyCheck->errorString().toHtmlEscaped());
} else if (jsonParseError.error != QJsonParseError::NoError) {
qCWarning(lcRemoteWipe) << QString("Could not parse the JSON returned from the server: <br><em>%1</em>")
.arg(jsonParseError.errorString());
} else {
qCWarning(lcRemoteWipe) << QString("The reply from the server did not contain all expected fields");
}
// check for wipe request
} else if(!json.value("wipe").isUndefined()){
wipe = json["wipe"].toBool();
}
auto manager = AccountManager::instance();
auto accountState = manager->account(_account->displayName()).data();
if(wipe){
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* 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.
*
* We introduce this dirty hack here, to allow deleting them upon Remote Wipe.
*/
_account->setRemoteWipeRequested_HACK();
// <-- FIXME MS@2019-12-07
// delete account
manager->deleteAccount(accountState);
manager->save();
// delete data
emit authorized(accountState);
} else {
// ask user for his credentials again
accountState->handleInvalidCredentials();
}
_networkReplyCheck->deleteLater();
}
void RemoteWipe::notifyServerSuccessJob(AccountState *accountState, bool dataWiped){
if(_accountRemoved && dataWiped && _account == accountState->account()){
QUrl requestUrl = Utility::concatUrlPath(_account->url().toString(),
QLatin1String("/index.php/core/wipe/success"));
QNetworkRequest request;
request.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
request.setUrl(requestUrl);
request.setSslConfiguration(_account->getOrCreateSslConfig());
auto requestBody = new QBuffer;
QUrlQuery arguments(QString("token=%1").arg(_appPassword));
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
_networkReplySuccess = _networkManager.post(request, requestBody);
QObject::connect(_networkReplySuccess, &QNetworkReply::finished, this,
&RemoteWipe::notifyServerSuccessJobSlot);
}
}
void RemoteWipe::notifyServerSuccessJobSlot()
{
auto jsonData = _networkReplySuccess->readAll();
QJsonParseError jsonParseError;
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
if (_networkReplySuccess->error() != QNetworkReply::NoError ||
jsonParseError.error != QJsonParseError::NoError) {
QString errorReason;
QString errorFromJson = json["error"].toString();
if (!errorFromJson.isEmpty()) {
qCWarning(lcRemoteWipe) << QString("Error returned from the server: <em>%1</em>")
.arg(errorFromJson.toHtmlEscaped());
} else if (_networkReplySuccess->error() != QNetworkReply::NoError) {
qCWarning(lcRemoteWipe) << QString("There was an error accessing the 'success' endpoint: <br><em>%1</em>")
.arg(_networkReplySuccess->errorString().toHtmlEscaped());
} else if (jsonParseError.error != QJsonParseError::NoError) {
qCWarning(lcRemoteWipe) << QString("Could not parse the JSON returned from the server: <br><em>%1</em>")
.arg(jsonParseError.errorString());
} else {
qCWarning(lcRemoteWipe) << QString("The reply from the server did not contain all expected fields.");
}
}
_networkReplySuccess->deleteLater();
}
}

61
src/gui/remotewipe.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef REMOTEWIPE_H
#define REMOTEWIPE_H
#include "accountmanager.h"
#include <QNetworkAccessManager>
class QJsonDocument;
class TestRemoteWipe;
namespace OCC {
class RemoteWipe : public QObject
{
Q_OBJECT
public:
explicit RemoteWipe(AccountPtr account, QObject *parent = nullptr);
signals:
/**
* Notify if wipe was requested
*/
void authorized(AccountState*);
/**
* Notify if user only needs to login again
*/
void askUserCredentials();
public slots:
/**
* Once receives a 401 or 403 status response it will do a fetch to
* <server>/index.php/core/wipe/check
*/
void startCheckJobWithAppPassword(QString);
private slots:
/**
* If wipe is requested, delete account and data, if not continue by asking
* the user to login again
*/
void checkJobSlot();
/**
* Once the client has wiped all the required data a POST to
* <server>/index.php/core/wipe/success
*/
void notifyServerSuccessJob(AccountState *accountState, bool);
void notifyServerSuccessJobSlot();
private:
AccountPtr _account;
QString _appPassword;
bool _accountRemoved;
QNetworkAccessManager _networkManager;
QNetworkReply *_networkReplyCheck;
QNetworkReply *_networkReplySuccess;
friend class ::TestRemoteWipe;
};
}
#endif // REMOTEWIPE_H

View File

@@ -70,7 +70,7 @@ SelectiveSyncWidget::SelectiveSyncWidget(AccountPtr account, QWidget *parent)
, _inserting(false)
, _folderTree(new QTreeWidget(this))
{
_loading = new QLabel(tr("Loading ..."), _folderTree);
_loading = new QLabel(tr("Loading "), _folderTree);
auto layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);

View File

@@ -56,10 +56,6 @@ namespace OCC {
#include "settingsdialogcommon.cpp"
//
// Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp !
//
SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
: QDialog(parent)
, _ui(new Ui::SettingsDialog)
@@ -108,6 +104,9 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
GeneralSettings *generalSettings = new GeneralSettings;
_ui->stack->addWidget(generalSettings);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &SettingsDialog::styleChanged, generalSettings, &GeneralSettings::slotStyleChanged);
QAction *networkAction = createColorAwareAction(QLatin1String(":/client/resources/network.png"), tr("Network"));
_actionGroup->addAction(networkAction);
_toolBar->addAction(networkAction);
@@ -128,6 +127,8 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
connect(showLogWindow, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser);
addAction(showLogWindow);
connect(this, &SettingsDialog::onActivate, gui, &ownCloudGui::slotSettingsDialogActivated);
customizeStyle();
cfg.restoreGeometry(this);
@@ -160,6 +161,13 @@ void SettingsDialog::changeEvent(QEvent *e)
case QEvent::PaletteChange:
case QEvent::ThemeChange:
customizeStyle();
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
break;
case QEvent::ActivationChange:
if(isActiveWindow())
emit onActivate();
break;
default:
break;
@@ -263,6 +271,10 @@ void SettingsDialog::accountAdded(AccountState *s)
// Refresh immediatly when getting online
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender);
// Connect styleChanged event, to adapt (Dark-/Light-Mode switching)
connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged);
connect(this, &SettingsDialog::styleChanged, _activitySettings[s], &ActivitySettings::slotStyleChanged);
activityAdded(s);
slotRefreshActivity(s);
}
@@ -344,13 +356,13 @@ void SettingsDialog::accountRemoved(AccountState *s)
void SettingsDialog::customizeStyle()
{
QString highlightColor(palette().highlight().color().name());
QString altBase(palette().alternateBase().color().name());
QString highlightTextColor(palette().highlightedText().color().name());
QString dark(palette().dark().color().name());
QString background(palette().base().color().name());
_toolBar->setStyleSheet(QString::fromLatin1(TOOLBAR_CSS).arg(background, dark, highlightColor, altBase));
_toolBar->setStyleSheet(QString::fromLatin1(TOOLBAR_CSS).arg(background, dark, highlightColor, highlightTextColor));
Q_FOREACH (QAction *a, _actionGroup->actions()) {
QIcon icon = createColorAwareIcon(a->property("iconPath").toString());
QIcon icon = Theme::createColorAwareIcon(a->property("iconPath").toString(), palette());
a->setIcon(icon);
QToolButton *btn = qobject_cast<QToolButton *>(_toolBar->widgetForAction(a));
if (btn)
@@ -358,34 +370,6 @@ void SettingsDialog::customizeStyle()
}
}
static bool isDarkColor(const QColor &color)
{
// account for different sensitivity of the human eye to certain colors
double treshold = 1.0 - (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0;
return treshold > 0.5;
}
QIcon SettingsDialog::createColorAwareIcon(const QString &name)
{
QPalette pal = palette();
QImage img(name);
QImage inverted(img);
inverted.invertPixels(QImage::InvertRgb);
QIcon icon;
if (isDarkColor(pal.color(QPalette::Base))) {
icon.addPixmap(QPixmap::fromImage(inverted));
} else {
icon.addPixmap(QPixmap::fromImage(img));
}
if (isDarkColor(pal.color(QPalette::HighlightedText))) {
icon.addPixmap(QPixmap::fromImage(img), QIcon::Normal, QIcon::On);
} else {
icon.addPixmap(QPixmap::fromImage(inverted), QIcon::Normal, QIcon::On);
}
return icon;
}
class ToolButtonAction : public QWidgetAction
{
public:
@@ -426,7 +410,7 @@ QAction *SettingsDialog::createActionWithIcon(const QIcon &icon, const QString &
QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const QString &text)
{
// all buttons must have the same size in order to keep a good layout
QIcon coloredIcon = createColorAwareIcon(iconPath);
QIcon coloredIcon = Theme::createColorAwareIcon(iconPath, palette());
return createActionWithIcon(coloredIcon, text, iconPath);
}

View File

@@ -63,6 +63,10 @@ public slots:
void slotAccountAvatarChanged();
void slotAccountDisplayNameChanged();
signals:
void styleChanged();
void onActivate();
protected:
void reject() override;
void accept() override;
@@ -76,7 +80,6 @@ private:
void customizeStyle();
void activityAdded(AccountState *);
QIcon createColorAwareIcon(const QString &name);
QAction *createColorAwareAction(const QString &iconName, const QString &fileName);
QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString());

View File

@@ -2,7 +2,7 @@ namespace SettingsDialogCommon
{
/** display name with two lines that is displayed in the settings
* If width is bigger than 0, the string will be ellided so it does not exceed that width
* If width is bigger than 0, the string will be elided so it does not exceed that width
*/
QString shortDisplayNameForSettings(Account* account, int width)
{

View File

@@ -1,239 +0,0 @@
/*
* Copyright (C) by Denis Dzyubenko
*
* 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 "settingsdialogmac.h"
#include "macstandardicon.h"
#include "folderman.h"
#include "theme.h"
#include "generalsettings.h"
#include "networksettings.h"
#include "accountsettings.h"
#include "accountstate.h"
#include "creds/abstractcredentials.h"
#include "configfile.h"
#include "progressdispatcher.h"
#include "owncloudgui.h"
#include "activitywidget.h"
#include "accountmanager.h"
#include <QLabel>
#include <QStandardItemModel>
#include <QPushButton>
#include <QSettings>
#include <QPainter>
#include <QPainterPath>
namespace OCC {
#include "settingsdialogcommon.cpp"
// Duplicate in settingsdialog.cpp
static QIcon circleMask(const QImage &avatar)
{
int dim = avatar.width();
QPixmap fixedImage(dim, dim);
fixedImage.fill(Qt::transparent);
QPainter imgPainter(&fixedImage);
QPainterPath clip;
clip.addEllipse(0, 0, dim, dim);
imgPainter.setClipPath(clip);
imgPainter.drawImage(0, 0, avatar);
imgPainter.end();
return QIcon(fixedImage);
}
//
// Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp !
//
SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
: MacPreferencesWindow(parent)
, _gui(gui)
{
// do not show minimize button. There is no use, and restoring the
// dialog from minimize is broken in MacPreferencesWindow
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint);
// Emulate dialog behavior: Escape means close
QAction *closeDialogAction = new QAction(this);
closeDialogAction->setShortcut(QKeySequence(Qt::Key_Escape));
connect(closeDialogAction, &QAction::triggered, this, &SettingsDialogMac::close);
addAction(closeDialogAction);
// People perceive this as a Window, so also make Ctrl+W work
QAction *closeWindowAction = new QAction(this);
closeWindowAction->setShortcut(QKeySequence("Ctrl+W"));
connect(closeWindowAction, &QAction::triggered, this, &SettingsDialogMac::close);
addAction(closeWindowAction);
// People perceive this as a Window, so also make Ctrl+H work
QAction *hideWindowAction = new QAction(this);
hideWindowAction->setShortcut(QKeySequence("Ctrl+H"));
connect(hideWindowAction, &QAction::triggered, this, &SettingsDialogMac::hide);
addAction(hideWindowAction);
setObjectName("SettingsMac"); // required as group for saveGeometry call
setWindowTitle(tr("%1").arg(Theme::instance()->appNameGUI()));
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &SettingsDialogMac::accountAdded);
connect(AccountManager::instance(), &AccountManager::accountRemoved,
this, &SettingsDialogMac::accountRemoved);
_actionsIdx = -1;
foreach (auto ai, AccountManager::instance()->accounts()) {
accountAdded(ai.data());
}
QIcon generalIcon = MacStandardIcon::icon(MacStandardIcon::PreferencesGeneral);
GeneralSettings *generalSettings = new GeneralSettings;
addPreferencesPanel(generalIcon, tr("General"), generalSettings);
QIcon networkIcon = MacStandardIcon::icon(MacStandardIcon::Network);
NetworkSettings *networkSettings = new NetworkSettings;
addPreferencesPanel(networkIcon, tr("Network"), networkSettings);
QAction *showLogWindow = new QAction(this);
showLogWindow->setShortcut(QKeySequence("F12"));
connect(showLogWindow, &QAction::triggered, gui, &ownCloudGui::slotToggleLogBrowser);
addAction(showLogWindow);
ConfigFile cfg;
cfg.restoreGeometry(this);
}
void SettingsDialogMac::closeEvent(QCloseEvent *event)
{
ConfigFile cfg;
cfg.saveGeometry(this);
MacPreferencesWindow::closeEvent(event);
}
void SettingsDialogMac::showActivityPage()
{
// Count backwards (0-based) from the last panel (multiple accounts can be on the left)
setCurrentPanelIndex(preferencePanelCount() - 1 - 2);
}
void SettingsDialogMac::accountAdded(AccountState *s)
{
QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts);
auto accountSettings = new AccountSettings(s, this);
QString displayName = Theme::instance()->multiAccount() ? SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), 0) : tr("Account");
// this adds the panel - nothing to add here just to fix the order
insertPreferencesPanel(++_actionsIdx, accountIcon, displayName, accountSettings);
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction);
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialogMac::slotAccountAvatarChanged);
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialogMac::slotAccountDisplayNameChanged);
// Refresh immediatly when getting online
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialogMac::slotRefreshActivityAccountStateSender);
// Add activity panel
QIcon activityIcon(QLatin1String(":/client/resources/activity.png"));
_activitySettings[s] = new ActivitySettings(s, this);
insertPreferencesPanel(++_actionsIdx, activityIcon, tr("Activity"), _activitySettings[s]);
connect(_activitySettings[s], SIGNAL(guiLog(QString, QString)), _gui,
SLOT(slotShowOptionalTrayMessage(QString, QString)));
// if this is not the first account, add separator 2 positions before int the toolbar
if(AccountManager::instance()->accounts().first().data() != s &&
AccountManager::instance()->accounts().size() >= 1){
_separators[s] = insertSeparator(_actionsIdx - 1);
++_actionsIdx; //we have one more item in the toolbar
}
ConfigFile cfg;
_activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
slotRefreshActivity(s);
setCurrentPanelIndex(0);
}
void SettingsDialogMac::accountRemoved(AccountState *s)
{
auto list = findChildren<AccountSettings *>(QString());
foreach (auto p, list) {
if (p->accountsState() == s) {
removePreferencesPanel(p);
// remove settings panel
if(_activitySettings.contains(s))
removePreferencesPanel(_activitySettings[s]);
// remove separator if there is any
if(_separators.contains(s)){
removeSeparator(_separators[s]);
_separators.remove(s);
}
}
}
}
void SettingsDialogMac::slotRefreshActivityAccountStateSender()
{
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
}
void SettingsDialogMac::slotRefreshActivity(AccountState *accountState)
{
if (accountState) {
_activitySettings[accountState]->slotRefresh();
}
}
void SettingsDialogMac::slotAccountAvatarChanged()
{
Account *account = static_cast<Account *>(sender());
auto list = findChildren<AccountSettings *>(QString());
foreach (auto p, list) {
if (p->accountsState()->account() == account) {
int idx = indexForPanel(p);
QImage pix = account->avatar();
if (!pix.isNull()) {
setPreferencesPanelIcon(idx, circleMask(pix));
}
}
}
}
void SettingsDialogMac::slotAccountDisplayNameChanged()
{
Account *account = static_cast<Account *>(sender());
auto list = findChildren<AccountSettings *>(QString());
foreach (auto p, list) {
if (p->accountsState()->account() == account) {
int idx = indexForPanel(p);
QString displayName = account->displayName();
if (!displayName.isNull()) {
displayName = Theme::instance()->multiAccount()
? SettingsDialogCommon::shortDisplayNameForSettings(account, 0)
: tr("Account");
setPreferencesPanelTitle(idx, displayName);
}
}
}
}
}

View File

@@ -1,73 +0,0 @@
/*
* Copyright (C) by Denis Dzyubenko
*
* 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 SETTINGSDIALOGMAC_H
#define SETTINGSDIALOGMAC_H
#include "progressdispatcher.h"
#include "macpreferenceswindow.h"
#include "owncloudgui.h"
class QStandardItemModel;
class QListWidgetItem;
namespace OCC {
class AccountSettings;
class Application;
class FolderMan;
class ownCloudGui;
class Folder;
class AccountState;
class ActivitySettings;
/**
* @brief The SettingsDialogMac class
* @ingroup gui
*/
class SettingsDialogMac : public MacPreferencesWindow
{
Q_OBJECT
public:
explicit SettingsDialogMac(ownCloudGui *gui, QWidget *parent = 0);
public slots:
void showActivityPage();
void slotRefreshActivity(AccountState *accountState);
void slotRefreshActivityAccountStateSender();
private slots:
void accountAdded(AccountState *);
void accountRemoved(AccountState *);
void slotAccountAvatarChanged();
void slotAccountDisplayNameChanged();
private:
void closeEvent(QCloseEvent *event);
QAction *_actionBefore;
int _actionsIdx;
QMap<AccountState *, QAction *> _separators;
QMap<AccountState *, ActivitySettings *> _activitySettings;
ownCloudGui *_gui;
int _protocolIdx;
};
}
#endif // SETTINGSDIALOGMAC_H
;

View File

@@ -156,6 +156,9 @@ void ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
connect(_linkWidgetList.at(index), &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
//connect(_linkWidgetList.at(index), &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareDialog::styleChanged, _linkWidgetList.at(index), &ShareLinkWidget::slotStyleChanged);
_ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _linkWidgetList.at(index));
_linkWidgetList.at(index)->setupUiOptions();
}
@@ -282,6 +285,10 @@ void ShareDialog::showSharingUi()
if (userGroupSharing) {
_userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged);
_ui->verticalLayout->insertWidget(1, _userGroupWidget);
_userGroupWidget->getShares();
}
@@ -335,4 +342,21 @@ void ShareDialog::slotAccountStateChanged(int state)
}
}
}
void ShareDialog::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::StyleChange:
case QEvent::PaletteChange:
case QEvent::ThemeChange:
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
break;
default:
break;
}
QDialog::changeEvent(e);
}
}

View File

@@ -68,6 +68,10 @@ private slots:
signals:
void toggleAnimation(bool);
void styleChanged();
protected:
void changeEvent(QEvent *) override;
private:
void showSharingUi();

View File

@@ -19,6 +19,7 @@
#include "capabilities.h"
#include "guiutility.h"
#include "sharemanager.h"
#include "theme.h"
#include "QProgressIndicator.h"
#include <QBuffer>
@@ -240,6 +241,8 @@ void ShareLinkWidget::setupUiOptions(){
//TO DO
//startAnimation(0, height());
customizeStyle();
}
void ShareLinkWidget::slotCopyLinkShare(bool clicked){
@@ -489,4 +492,32 @@ void ShareLinkWidget::displayError(const QString &errMsg)
_ui->errorLabel->setText(errMsg);
_ui->errorLabel->show();
}
void ShareLinkWidget::slotStyleChanged()
{
customizeStyle();
}
void ShareLinkWidget::customizeStyle()
{
_unshareLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/delete.png"));
_addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/add.png"));
_ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/resources/copy.svg"));
// only on master, not in stable-2.6 yet
// _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/resources/public.svg"));
_ui->createShareButton->setIcon(Theme::createColorAwareIcon(":/client/resources/public.svg"));
_ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg"));
// only on master, not in stable-2.6 yet
// _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
_ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
_ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
_ui->progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text));
}
}

View File

@@ -65,6 +65,7 @@ public slots:
void slotToggleAnimation(bool start);
void slotServerError(int code, const QString &message);
void slotCreateShareRequiresPassword(const QString &message);
void slotStyleChanged();
private slots:
void slotCreateShareLink(bool clicked);
@@ -107,6 +108,8 @@ private:
void startAnimation(const int start, const int end);
void customizeStyle();
Ui::ShareLinkWidget *_ui;
AccountPtr _account;
QString _sharePath;

View File

@@ -26,6 +26,7 @@
#include "thumbnailjob.h"
#include "sharee.h"
#include "sharemanager.h"
#include "theme.h"
#include "QProgressIndicator.h"
#include <QBuffer>
@@ -112,6 +113,8 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
//_ui->shareeHorizontalLayout->addWidget(&_pi_sharee);
_parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea");
customizeStyle();
}
ShareUserGroupWidget::~ShareUserGroupWidget()
@@ -211,6 +214,10 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged);
layout->addWidget(s);
x++;
@@ -255,7 +262,8 @@ void ShareUserGroupWidget::slotPrivateLinkShare()
auto menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(QIcon(":/client/resources/copy.svg"),
// this icon is not handled by slotStyleChanged() -> customizeStyle but we can live with that
menu->addAction(Theme::createColorAwareIcon(":/client/resources/copy.svg"),
tr("Copy link"),
this, SLOT(slotPrivateLinkCopy()));
@@ -358,6 +366,25 @@ void ShareUserGroupWidget::slotPrivateLinkEmail()
this);
}
void ShareUserGroupWidget::slotStyleChanged()
{
customizeStyle();
// Notify the other widgets (ShareUserLine in this case, Dark-/Light-Mode switching)
emit styleChanged();
}
void ShareUserGroupWidget::customizeStyle()
{
_ui->confirmShare->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
_pi_sharee.setColor(QGuiApplication::palette().color(QPalette::Text));
foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) {
pi->setColor(QGuiApplication::palette().color(QPalette::Text));;
}
}
ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
SharePermissions maxSharingPermissions,
bool isFile,
@@ -423,8 +450,9 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
_ui->permissionToolButton->setMenu(menu);
_ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
QIcon icon(QLatin1String(":/client/resources/more.svg"));
_ui->permissionToolButton->setIcon(icon);
// icon now set in: customizeStyle
/*QIcon icon(QLatin1String(":/client/resources/more.svg"));
_ui->permissionToolButton->setIcon(icon);*/
// Set the permissions checkboxes
displayPermissions();
@@ -451,6 +479,8 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
}
loadAvatar();
customizeStyle();
}
void ShareUserLine::loadAvatar()
@@ -645,4 +675,18 @@ void ShareUserLine::displayPermissions()
_permissionDelete->setChecked(perm & SharePermissionDelete);
}
}
void ShareUserLine::slotStyleChanged()
{
customizeStyle();
}
void ShareUserLine::customizeStyle()
{
_ui->permissionToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg"));
QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),Theme::createColorAwareIcon(QLatin1String(":/client/resources/delete.png")));
_deleteShareButton->setIcon(deleteicon);
}
}

View File

@@ -64,9 +64,11 @@ public:
signals:
void togglePublicLinkShare(bool);
void styleChanged();
public slots:
void getShares();
void slotStyleChanged();
private slots:
void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
@@ -88,6 +90,8 @@ private slots:
void slotPrivateLinkEmail();
private:
void customizeStyle();
Ui::ShareUserGroupWidget *_ui;
QScrollArea *_parentScrollArea;
AccountPtr _account;
@@ -127,6 +131,9 @@ signals:
void visualDeletionDone();
void resizeRequested();
public slots:
void slotStyleChanged();
private slots:
void on_deleteShareButton_clicked();
void slotPermissionsChanged();
@@ -141,6 +148,7 @@ private slots:
private:
void displayPermissions();
void loadAvatar();
void customizeStyle();
Ui::ShareUserLine *_ui;
QSharedPointer<Share> _share;

View File

@@ -623,9 +623,9 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis
{
static std::array<std::pair<const char *, QString>, 5> strings { {
{ "SHARE_MENU_TITLE", tr("Share options") },
{ "CONTEXT_MENU_TITLE", tr("Share via ") + Theme::instance()->appNameGUI()},
{ "CONTEXT_MENU_TITLE", tr("Share via %1").arg(Theme::instance()->appNameGUI())},
{ "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
{ "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email...") },
{ "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email") },
} };
listener->sendMessage(QString("GET_STRINGS:BEGIN"));
for (const auto& key_value : strings) {
@@ -673,7 +673,7 @@ void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketLi
// Disabled: only providing email option for private links would look odd,
// and the copy option is more general.
//listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email..."));
//listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email"));
}
SocketApi::FileData SocketApi::FileData::get(const QString &localFile)

View File

@@ -142,7 +142,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
// create label first
QLabel *label = new QLabel(parent);
label->setStyleSheet(QLatin1String("QLabel { padding: 8px; background-color: #fff; }"));
label->setStyleSheet(QLatin1String("QLabel { padding: 8px; }"));
label->setTextFormat(Qt::RichText);
label->setText(details);

View File

@@ -140,7 +140,7 @@ QString OCUpdater::statusString() const
switch (downloadState()) {
case Downloading:
return tr("Downloading version %1. Please wait...").arg(updateVersion);
return tr("Downloading version %1. Please wait").arg(updateVersion);
case DownloadComplete:
return tr("%1 version %2 available. Restart application to start the update.").arg(Theme::instance()->appNameGUI(), updateVersion);
case DownloadFailed:
@@ -148,9 +148,9 @@ QString OCUpdater::statusString() const
case DownloadTimedOut:
return tr("Could not check for new updates.");
case UpdateOnlyAvailableThroughSystem:
return tr("New %1 version %2 available. Please use the system's update tool to install it.").arg(Theme::instance()->appNameGUI(), updateVersion);
return tr("New %1 version %2 is available. Please click <a href='%3'>here</a> to download the update.").arg(Theme::instance()->appNameGUI(), updateVersion, _updateInfo.web());
case CheckingServer:
return tr("Checking update server...");
return tr("Checking update server");
case Unknown:
return tr("Update status is unknown: Did not check for new updates.");
case UpToDate:

View File

@@ -14,40 +14,39 @@
*/
#include <QVariant>
#include <QMenu>
#include <QClipboard>
#include <QVBoxLayout>
#include "wizard/flow2authcredspage.h"
#include "flow2authcredspage.h"
#include "theme.h"
#include "account.h"
#include "cookiejar.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/owncloudwizard.h"
#include "wizard/flow2authwidget.h"
#include "creds/credentialsfactory.h"
#include "creds/webflowcredentials.h"
namespace OCC {
Flow2AuthCredsPage::Flow2AuthCredsPage()
: AbstractCredentialsWizardPage()
: AbstractCredentialsWizardPage(),
_flow2AuthWidget(nullptr)
{
_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);
_layout = new QVBoxLayout(this);
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);
_flow2AuthWidget = new Flow2AuthWidget();
_layout->addWidget(_flow2AuthWidget);
connect(_flow2AuthWidget, &Flow2AuthWidget::authResult, this, &Flow2AuthCredsPage::slotFlow2AuthResult);
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &Flow2AuthCredsPage::styleChanged, _flow2AuthWidget, &Flow2AuthWidget::slotStyleChanged);
// allow Flow2 page to poll on window activation
connect(this, &Flow2AuthCredsPage::pollNow, _flow2AuthWidget, &Flow2AuthWidget::slotPollNow);
}
void Flow2AuthCredsPage::initializePage()
@@ -55,9 +54,9 @@ 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();
if(_flow2AuthWidget)
_flow2AuthWidget->startAuth(ocWizard->account().data());
// Don't hide the wizard (avoid user confusion)!
//wizard()->hide();
@@ -67,21 +66,20 @@ void OCC::Flow2AuthCredsPage::cleanupPage()
{
// The next or back button was activated, show the wizard again
wizard()->show();
_asyncAuth.reset();
if(_flow2AuthWidget)
_flow2AuthWidget->resetAuth();
// Forget sensitive data
_appPassword.clear();
_user.clear();
}
void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &user,
const QString &appPassword)
void Flow2AuthCredsPage::slotFlow2AuthResult(Flow2Auth::Result r, const QString &errorString, 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();
wizard()->show();
/* Don't fallback to HTTP credentials */
/*OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
@@ -91,7 +89,6 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use
}
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: {
@@ -99,6 +96,7 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use
_appPassword = appPassword;
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
emit connectToOCUrl(ocWizard->account()->url().toString());
break;
}
@@ -112,7 +110,11 @@ int Flow2AuthCredsPage::nextId() const
void Flow2AuthCredsPage::setConnected()
{
wizard()->show();
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
Q_ASSERT(ocWizard);
// bring wizard to top
ocWizard->bringToTop();
}
AbstractCredentials *Flow2AuthCredsPage::getCredentials() const
@@ -133,19 +135,14 @@ bool Flow2AuthCredsPage::isComplete() const
return false; /* We can never go forward manually */
}
void Flow2AuthCredsPage::slotOpenBrowser()
void Flow2AuthCredsPage::slotPollNow()
{
if (_ui.errorLabel)
_ui.errorLabel->hide();
if (_asyncAuth)
_asyncAuth->openBrowser();
emit pollNow();
}
void Flow2AuthCredsPage::slotCopyLinkToClipboard()
void Flow2AuthCredsPage::slotStyleChanged()
{
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
emit styleChanged();
}
} // namespace OCC

View File

@@ -25,11 +25,12 @@
#include "accountfwd.h"
#include "creds/flow2auth.h"
#include "ui_flow2authcredspage.h"
class QVBoxLayout;
class QProgressIndicator;
namespace OCC {
class Flow2AuthWidget;
class Flow2AuthCredsPage : public AbstractCredentialsWizardPage
{
@@ -46,20 +47,22 @@ public:
bool isComplete() const override;
public Q_SLOTS:
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
void slotFlow2AuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword);
void slotPollNow();
void slotStyleChanged();
signals:
void connectToOCUrl(const QString &);
void pollNow();
void styleChanged();
public:
QString _user;
QString _appPassword;
QScopedPointer<Flow2Auth> _asyncAuth;
Ui_Flow2AuthCredsPage _ui;
protected slots:
void slotOpenBrowser();
void slotCopyLinkToClipboard();
private:
Flow2AuthWidget *_flow2AuthWidget;
QVBoxLayout *_layout;
};
} // namespace OCC

View File

@@ -1,101 +0,0 @@
<?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>Browser Authentication</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>
<family>Segoe UI</family>
<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

@@ -14,53 +14,66 @@
#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"
#include "QProgressIndicator.h"
namespace OCC {
Q_LOGGING_CATEGORY(lcFlow2AuthWidget, "gui.wizard.flow2authwidget", QtInfoMsg)
Flow2AuthWidget::Flow2AuthWidget(Account *account, QWidget *parent)
: QWidget(parent),
_account(account),
_ui()
Flow2AuthWidget::Flow2AuthWidget(QWidget *parent)
: QWidget(parent)
, _account(nullptr)
, _ui()
, _progressIndi(new QProgressIndicator(this))
, _statusUpdateSkipCount(0)
{
_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);
_ui.errorLabel->setTextFormat(Qt::RichText);
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();
_ui.horizontalLayout->addWidget(_progressIndi);
stopSpinner(false);
customizeStyle();
}
void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user,
const QString &appPassword)
void Flow2AuthWidget::startAuth(Account *account)
{
Flow2Auth *oldAuth = _asyncAuth.take();
if(oldAuth)
oldAuth->deleteLater();
_statusUpdateSkipCount = 0;
if(account) {
_account = account;
_asyncAuth.reset(new Flow2Auth(_account, this));
connect(_asyncAuth.data(), &Flow2Auth::result, this, &Flow2AuthWidget::slotAuthResult, Qt::QueuedConnection);
connect(_asyncAuth.data(), &Flow2Auth::statusChanged, this, &Flow2AuthWidget::slotStatusChanged);
connect(this, &Flow2AuthWidget::pollNow, _asyncAuth.data(), &Flow2Auth::slotPollNow);
_asyncAuth->start();
}
}
void Flow2AuthWidget::resetAuth(Account *account)
{
startAuth(account);
}
void Flow2AuthWidget::slotAuthResult(Flow2Auth::Result r, const QString &errorString, const QString &user, const QString &appPassword)
{
stopSpinner(false);
switch (r) {
case Flow2Auth::NotSupported:
/* Flow2Auth can't open browser */
@@ -69,15 +82,16 @@ void Flow2AuthWidget::asyncAuthResult(Flow2Auth::Result r, const QString &user,
break;
case Flow2Auth::Error:
/* Error while getting the access token. (Timeout, or the server did not accept our client credentials */
_ui.errorLabel->setText(errorString);
_ui.errorLabel->show();
break;
case Flow2Auth::LoggedIn: {
_user = user;
_appPassword = appPassword;
emit urlCatched(_user, _appPassword, QString());
_ui.errorLabel->hide();
break;
}
}
emit authResult(r, errorString, user, appPassword);
}
void Flow2AuthWidget::setError(const QString &error) {
@@ -90,11 +104,8 @@ void Flow2AuthWidget::setError(const QString &error) {
}
Flow2AuthWidget::~Flow2AuthWidget() {
_asyncAuth.reset();
// Forget sensitive data
_appPassword.clear();
_user.clear();
_asyncAuth.reset();
}
void Flow2AuthWidget::slotOpenBrowser()
@@ -108,8 +119,79 @@ void Flow2AuthWidget::slotOpenBrowser()
void Flow2AuthWidget::slotCopyLinkToClipboard()
{
if (_ui.errorLabel)
_ui.errorLabel->hide();
if (_asyncAuth)
QApplication::clipboard()->setText(_asyncAuth->authorisationLink().toString(QUrl::FullyEncoded));
_asyncAuth->copyLinkToClipboard();
}
void Flow2AuthWidget::slotPollNow()
{
emit pollNow();
}
void Flow2AuthWidget::slotStatusChanged(Flow2Auth::PollStatus status, int secondsLeft)
{
switch(status)
{
case Flow2Auth::statusPollCountdown:
if(_statusUpdateSkipCount > 0) {
_statusUpdateSkipCount--;
break;
}
_ui.statusLabel->setText(tr("Waiting for authorization") + QString("… (%1)").arg(secondsLeft));
stopSpinner(true);
break;
case Flow2Auth::statusPollNow:
_statusUpdateSkipCount = 0;
_ui.statusLabel->setText(tr("Polling for authorization") + "");
startSpinner();
break;
case Flow2Auth::statusFetchToken:
_statusUpdateSkipCount = 0;
_ui.statusLabel->setText(tr("Starting authorization") + "");
startSpinner();
break;
case Flow2Auth::statusCopyLinkToClipboard:
_ui.statusLabel->setText(tr("Link copied to clipboard."));
_statusUpdateSkipCount = 3;
stopSpinner(true);
break;
}
}
void Flow2AuthWidget::startSpinner()
{
_ui.horizontalLayout->setEnabled(true);
_ui.statusLabel->setVisible(true);
_progressIndi->setVisible(true);
_progressIndi->startAnimation();
_ui.openLinkButton->setEnabled(false);
_ui.copyLinkButton->setEnabled(false);
}
void Flow2AuthWidget::stopSpinner(bool showStatusLabel)
{
_ui.horizontalLayout->setEnabled(false);
_ui.statusLabel->setVisible(showStatusLabel);
_progressIndi->setVisible(false);
_progressIndi->stopAnimation();
_ui.openLinkButton->setEnabled(_statusUpdateSkipCount == 0);
_ui.copyLinkButton->setEnabled(_statusUpdateSkipCount == 0);
}
void Flow2AuthWidget::slotStyleChanged()
{
customizeStyle();
}
void Flow2AuthWidget::customizeStyle()
{
if(_progressIndi)
_progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text));
}
} // namespace OCC

View File

@@ -22,35 +22,49 @@
#include "ui_flow2authwidget.h"
class QProgressIndicator;
namespace OCC {
class Flow2AuthWidget : public QWidget
{
Q_OBJECT
public:
Flow2AuthWidget(Account *account, QWidget *parent = nullptr);
Flow2AuthWidget(QWidget *parent = nullptr);
virtual ~Flow2AuthWidget();
void startAuth(Account *account);
void resetAuth(Account *account = nullptr);
void setError(const QString &error);
public Q_SLOTS:
void asyncAuthResult(Flow2Auth::Result, const QString &user, const QString &appPassword);
void slotAuthResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword);
void slotPollNow();
void slotStatusChanged(Flow2Auth::PollStatus status, int secondsLeft);
void slotStyleChanged();
signals:
void urlCatched(const QString user, const QString pass, const QString host);
void authResult(Flow2Auth::Result, const QString &errorString, const QString &user, const QString &appPassword);
void pollNow();
private:
Account *_account;
QString _user;
QString _appPassword;
QScopedPointer<Flow2Auth> _asyncAuth;
Ui_Flow2AuthWidget _ui;
protected slots:
void slotOpenBrowser();
void slotCopyLinkToClipboard();
private:
void startSpinner();
void stopSpinner(bool showStatusLabel);
void customizeStyle();
QProgressIndicator *_progressIndi;
int _statusUpdateSkipCount;
};
}
} // namespace OCC
#endif // FLOW2AUTHWIDGET_H

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>280</height>
<width>580</width>
<height>330</height>
</rect>
</property>
<property name="sizePolicy">
@@ -26,22 +26,6 @@
<string>Browser Authentication</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">
@@ -73,7 +57,6 @@
<widget class="QCommandLinkButton" name="copyLinkButton">
<property name="font">
<font>
<family>Segoe UI</family>
<weight>50</weight>
<bold>false</bold>
</font>
@@ -83,6 +66,32 @@
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@@ -91,21 +100,11 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>127</height>
<height>107</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/>

View File

@@ -58,7 +58,7 @@ OwncloudAdvancedSetupPage::OwncloudAdvancedSetupPage()
setupCustomization();
connect(_ui.pbSelectLocalFolder, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectFolder);
setButtonText(QWizard::NextButton, tr("Connect..."));
setButtonText(QWizard::NextButton, tr("Connect"));
connect(_ui.rSyncEverything, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSyncEverythingClicked);
connect(_ui.rSelectiveSync, &QAbstractButton::clicked, this, &OwncloudAdvancedSetupPage::slotSelectiveSyncClicked);
@@ -389,4 +389,15 @@ QString OwncloudAdvancedSetupPage::checkLocalSpace(qint64 remoteSize) const
return (availableLocalSpace()>remoteSize) ? QString() : tr("There isn't enough free space in the local folder!");
}
void OwncloudAdvancedSetupPage::slotStyleChanged()
{
customizeStyle();
}
void OwncloudAdvancedSetupPage::customizeStyle()
{
if(_progressIndi)
_progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text));
}
} // namespace OCC

View File

@@ -51,6 +51,7 @@ signals:
public slots:
void setErrorString(const QString &);
void slotStyleChanged();
private slots:
void slotSelectFolder();
@@ -67,6 +68,7 @@ private:
QUrl serverUrl() const;
qint64 availableLocalSpace() const;
QString checkLocalSpace(qint64 remoteSize) const;
void customizeStyle();
Ui_OwncloudAdvancedSetupPage _ui;
bool _checking;

View File

@@ -194,5 +194,15 @@ AbstractCredentials *OwncloudHttpCredsPage::getCredentials() const
return new HttpCredentialsGui(_ui.leUsername->text(), _ui.lePassword->text(), _ocWizard->_clientSslCertificate, _ocWizard->_clientSslKey);
}
void OwncloudHttpCredsPage::slotStyleChanged()
{
customizeStyle();
}
void OwncloudHttpCredsPage::customizeStyle()
{
if(_progressIndi)
_progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text));
}
} // namespace OCC

View File

@@ -46,10 +46,14 @@ public:
Q_SIGNALS:
void connectToOCUrl(const QString &);
public slots:
void slotStyleChanged();
private:
void startSpinner();
void stopSpinner();
void setupCustomization();
void customizeStyle();
Ui_OwncloudHttpCredsPage _ui;
bool _connected;

View File

@@ -88,29 +88,15 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
connect(_ui.nextButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::nextSlide);
connect(_ui.prevButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::prevSlide);
auto widgetBgLightness = OwncloudSetupPage::palette().color(OwncloudSetupPage::backgroundRole()).lightness();
bool widgetHasDarkBg =
(widgetBgLightness >= 125)
? false
: true;
_ui.nextButton->setIcon(theme->uiThemeIcon(QString("control-next.svg"), widgetHasDarkBg));
_ui.prevButton->setIcon(theme->uiThemeIcon(QString("control-prev.svg"), widgetHasDarkBg));
// QPushButtons are a mess when it comes to consistent background coloring without stylesheets,
// so we do it here even though this is an exceptional styling method here
_ui.createAccountButton->setStyleSheet("QPushButton {background-color: #0082C9; color: white}");
_ui.slideShow->startShow();
QPalette pal = _ui.slideShow->palette();
pal.setColor(QPalette::WindowText, theme->wizardHeaderBackgroundColor());
_ui.slideShow->setPalette(pal);
#else
_ui.createAccountButton->hide();
_ui.loginButton->hide();
_ui.installLink->hide();
_ui.slideShow->hide();
#endif
customizeStyle();
}
void OwncloudSetupPage::setServerUrl(const QString &newUrl)
@@ -433,4 +419,31 @@ OwncloudSetupPage::~OwncloudSetupPage()
{
}
void OwncloudSetupPage::slotStyleChanged()
{
customizeStyle();
}
void OwncloudSetupPage::customizeStyle()
{
#ifdef WITH_PROVIDERS
Theme *theme = Theme::instance();
bool widgetHasDarkBg = Theme::isDarkColor(QGuiApplication::palette().base().color());
_ui.nextButton->setIcon(theme->uiThemeIcon(QString("control-next.svg"), widgetHasDarkBg));
_ui.prevButton->setIcon(theme->uiThemeIcon(QString("control-prev.svg"), widgetHasDarkBg));
// QPushButtons are a mess when it comes to consistent background coloring without stylesheets,
// so we do it here even though this is an exceptional styling method here
_ui.createAccountButton->setStyleSheet("QPushButton {background-color: #0082C9; color: white}");
QPalette pal = _ui.slideShow->palette();
pal.setColor(QPalette::WindowText, theme->wizardHeaderBackgroundColor());
_ui.slideShow->setPalette(pal);
#endif
if(_progressIndi)
_progressIndi->setColor(QGuiApplication::palette().color(QPalette::Text));
}
} // namespace OCC

View File

@@ -62,6 +62,7 @@ public slots:
void startSpinner();
void stopSpinner();
void slotCertificateAccepted();
void slotStyleChanged();
protected slots:
void slotUrlChanged(const QString &);
@@ -78,6 +79,7 @@ signals:
private:
bool urlHasChanged();
void customizeStyle();
Ui_OwncloudSetupPage _ui;

View File

@@ -16,6 +16,7 @@
#include "account.h"
#include "configfile.h"
#include "theme.h"
#include "owncloudgui.h"
#include "wizard/owncloudwizard.h"
#include "wizard/owncloudsetuppage.h"
@@ -100,6 +101,17 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
setTitleFormat(Qt::RichText);
setSubTitleFormat(Qt::RichText);
setButtonText(QWizard::CustomButton1, tr("Skip folders configuration"));
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
connect(this, &OwncloudWizard::styleChanged, _setupPage, &OwncloudSetupPage::slotStyleChanged);
connect(this, &OwncloudWizard::styleChanged, _advancedSetupPage, &OwncloudAdvancedSetupPage::slotStyleChanged);
connect(this, &OwncloudWizard::styleChanged, _flow2CredsPage, &Flow2AuthCredsPage::slotStyleChanged);
customizeStyle();
// allow Flow2 page to poll on window activation
connect(this, &OwncloudWizard::onActivate, _flow2CredsPage, &Flow2AuthCredsPage::slotPollNow);
}
void OwncloudWizard::setAccount(AccountPtr account)
@@ -277,4 +289,37 @@ AbstractCredentials *OwncloudWizard::getCredentials() const
return nullptr;
}
void OwncloudWizard::changeEvent(QEvent *e)
{
switch (e->type()) {
case QEvent::StyleChange:
case QEvent::PaletteChange:
case QEvent::ThemeChange:
customizeStyle();
// Notify the other widgets (Dark-/Light-Mode switching)
emit styleChanged();
break;
case QEvent::ActivationChange:
if(isActiveWindow())
emit onActivate();
break;
default:
break;
}
QWizard::changeEvent(e);
}
void OwncloudWizard::customizeStyle()
{
// HINT: Customize wizard's own style here, if necessary in the future (Dark-/Light-Mode switching)
}
void OwncloudWizard::bringToTop()
{
// bring wizard to top
ownCloudGui::raiseDialog(this);
}
} // end namespace

View File

@@ -74,6 +74,8 @@ public:
void displayError(const QString &, bool retryHTTPonly);
AbstractCredentials *getCredentials() const;
void bringToTop();
// FIXME: Can those be local variables?
// Set from the OwncloudSetupPage, later used from OwncloudHttpCredsPage
QSslKey _clientSslKey;
@@ -96,8 +98,15 @@ signals:
void basicSetupFinished(int);
void skipFolderConfiguration();
void needCertificate();
void styleChanged();
void onActivate();
protected:
void changeEvent(QEvent *) override;
private:
void customizeStyle();
AccountPtr _account;
OwncloudSetupPage *_setupPage;
OwncloudHttpCredsPage *_httpCredsPage;

View File

@@ -160,6 +160,39 @@ void AbstractNetworkJob::slotFinished()
qCWarning(lcNetworkJob) << "SslHandshakeFailedError: " << errorString() << " : can be caused by a webserver wanting SSL client certificates";
}
#if (QT_VERSION >= 0x050800)
// Qt doesn't yet transparently resend HTTP2 requests, do so here
const auto maxHttp2Resends = 5;
QByteArray verb = requestVerb(*reply());
if (_reply->error() == QNetworkReply::ContentReSendError
&& _reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool()) {
if ((_requestBody && !_requestBody->isSequential()) || verb.isEmpty()) {
qCWarning(lcNetworkJob) << "Can't resend HTTP2 request, verb or body not suitable"
<< _reply->request().url() << verb << _requestBody;
} else if (_http2ResendCount >= maxHttp2Resends) {
qCWarning(lcNetworkJob) << "Not resending HTTP2 request, number of resends exhausted"
<< _reply->request().url() << _http2ResendCount;
} else {
qCInfo(lcNetworkJob) << "HTTP2 resending" << _reply->request().url();
_http2ResendCount++;
resetTimeout();
if (_requestBody) {
if(!_requestBody->isOpen())
_requestBody->open(QIODevice::ReadOnly);
_requestBody->seek(0);
}
sendRequest(
verb,
_reply->request().url(),
_reply->request(),
_requestBody);
return;
}
}
#endif
if (_reply->error() != QNetworkReply::NoError) {
if (!_ignoreCredentialFailure || _reply->error() != QNetworkReply::AuthenticationRequiredError) {
qCWarning(lcNetworkJob) << _reply->error() << errorString()

View File

@@ -188,7 +188,10 @@ private:
QPointer<QNetworkReply> _reply; // (QPointer because the NetworkManager may be destroyed before the jobs at exit)
QString _path;
QTimer _timer;
int _redirectCount;
int _redirectCount = 0;
#if (QT_VERSION >= 0x050800)
int _http2ResendCount = 0;
#endif
// Set by the xyzRequest() functions and needed to be able to redirect
// requests, should it be required.

View File

@@ -36,9 +36,15 @@
#include <QAuthenticator>
#include <QStandardPaths>
#include <keychain.h>
#include "creds/abstractcredentials.h"
using namespace QKeychain;
namespace OCC {
Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
const char app_password[] = "_app-password";
Account::Account(QObject *parent)
: QObject(parent)
@@ -53,16 +59,17 @@ AccountPtr Account::create()
AccountPtr acc = AccountPtr(new Account);
acc->setSharedThis(acc);
//TODO: This probably needs to have a better
// coupling, but it should work for now.
acc->e2e()->setAccount(acc);
//TODO: This probably needs to have a better
// coupling, but it should work for now.
acc->e2e()->setAccount(acc);
return acc;
}
ClientSideEncryption* Account::e2e()
{
// Qt expects everything in the connect to be a pointer, so return a pointer.
return &_e2e;
// Qt expects everything in the connect to be a pointer, so return a pointer.
return &_e2e;
}
Account::~Account()
@@ -119,7 +126,7 @@ void Account::setAvatar(const QImage &img)
QString Account::displayName() const
{
QString dn = QString("%1@%2").arg(davUser(), _url.host());
QString dn = QString("%1@%2").arg(credentials()->user(), _url.host());
int port = url().port();
if (port > 0 && port != 80 && port != 443) {
dn.append(QLatin1Char(':'));
@@ -432,6 +439,9 @@ void Account::slotCredentialsAsked()
void Account::handleInvalidCredentials()
{
// Retrieving password will trigger remote wipe check job
retrieveAppPassword();
emit invalidCredentials();
}
@@ -503,4 +513,91 @@ void Account::setNonShib(bool nonShib)
}
}
void Account::writeAppPasswordOnce(QString appPassword){
if(_wroteAppPassword)
return;
// Fix: Password got written from Account Wizard, before finish.
// Only write the app password for a connected account, else
// there'll be a zombie keychain slot forever, never used again ;p
//
// Also don't write empty passwords (Log out -> Relaunch)
if(id().isEmpty() || appPassword.isEmpty())
return;
const QString kck = AbstractCredentials::keychainKey(
url().toString(),
davUser() + app_password,
id()
);
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
job->setBinaryData(appPassword.toLatin1());
connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
WritePasswordJob *writeJob = static_cast<WritePasswordJob *>(incoming);
if (writeJob->error() == NoError)
qCInfo(lcAccount) << "appPassword stored in keychain";
else
qCWarning(lcAccount) << "Unable to store appPassword in keychain" << writeJob->errorString();
// We don't try this again on error, to not raise CPU consumption
_wroteAppPassword = true;
});
job->start();
}
void Account::retrieveAppPassword(){
const QString kck = AbstractCredentials::keychainKey(
url().toString(),
credentials()->user() + app_password,
id()
);
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &ReadPasswordJob::finished, [this](Job *incoming) {
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incoming);
QString pwd("");
// Error or no valid public key error out
if (readJob->error() == NoError &&
readJob->binaryData().length() > 0) {
pwd = readJob->binaryData();
}
emit appPasswordRetrieved(pwd);
});
job->start();
}
void Account::deleteAppPassword(){
const QString kck = AbstractCredentials::keychainKey(
url().toString(),
credentials()->user() + app_password,
id()
);
if (kck.isEmpty()) {
qCDebug(lcAccount) << "appPassword is empty";
return;
}
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setInsecureFallback(false);
job->setKey(kck);
connect(job, &DeletePasswordJob::finished, [this](Job *incoming) {
DeletePasswordJob *deleteJob = static_cast<DeletePasswordJob *>(incoming);
if (deleteJob->error() == NoError)
qCInfo(lcAccount) << "appPassword deleted from keychain";
else
qCWarning(lcAccount) << "Unable to delete appPassword from keychain" << deleteJob->errorString();
// Allow storing a new app password on re-login
_wroteAppPassword = false;
});
job->start();
}
} // namespace OCC

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