1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2026-04-03 18:11:32 +02:00

Compare commits

..

188 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
Camila San
b6893aad16 Bump version to 2.6.1.
Signed-off-by: Camila San <hello@camila.codes>
2019-10-17 22:35:28 +02:00
kilian.pfeiffer
59d1624ce5 changed max GUI bandwith limits 2019-10-17 21:48:21 +02:00
Ivan Čukić
a0faf1f54d Race condition in the remote size loading logic
The quota retrieval process might not be finished by the time
the used space on the server (`_rSize`) is compared against
the locally available disk space which might end up in
a "There isn't enough free space in the local folder!" message
even if there is enough free space.

This patch updates the status after the quota has been retrieved.

It also initializes `_rSize` to `-1` so that errors like this
are easier to catch in the future.
2019-10-17 21:44:58 +02:00
Michael Schuster
caa7c845c2 fix comment typo
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 21:38:18 +02:00
Michael Schuster
e43a80d0be Fix double slashes in WebDAV URLs (account setup wizard)
Sanitize URL paths to elaminate double-slashes in the URL path string,
used for the first connection by the account setup wizard.

Example: https://cloud.example.com/remote.php/webdav//

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 21:37:41 +02:00
Michael Schuster
f060a92563 Fix outdated link to server admin docs
Sets the target version from "15" to "latest" and removes
"index.html" because this could get obsolete in the future too.

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 21:34:05 +02:00
Dominique Fuchs
483696261d Fixed some missing 'translatable' exclusions, added missing window titles in flow dialogs
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:33:34 +02:00
Dominique Fuchs
610e35ec64 Fixed unused var
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:22:48 +02:00
Dominique Fuchs
5fa5526ea2 Added slight svg transparency as requested in ref issue, fixed bg detection logic
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:22:40 +02:00
Dominique Fuchs
42d9d99a92 (Maybe) finished implementation of themed wizard buttons and accessibility refinements and thus implementation of helper fct. to retrieve themed QIcons.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:22:30 +02:00
Dominique Fuchs
c3ff9ca917 [WIP] themed button implementation
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:22:21 +02:00
Dominique Fuchs
875f123d5b [WIP] Resource file and include changes as well as new control icons for wizard slide buttons
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:21:58 +02:00
Dominique Fuchs
a5f053afe4 Layout optimizations and tab access for self-hosting link
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:18:03 +02:00
Dominique Fuchs
63a6992f97 fix naming for slide navigation, adapted everywhere to be consistent
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:17:34 +02:00
Dominique Fuchs
54740378f0 Restructured layout, as the initial change were too broken (after additions through the last months)
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:16:51 +02:00
Dominique Fuchs
1dc443bc06 Fixed wrong resource paths
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:15:57 +02:00
Dominique Fuchs
e541109d7c Added newly created next/prev svg's and fixed reduntant layout parts in wizard
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
2019-10-17 21:13:47 +02:00
Izabela Bakollari
993f124120 Add files via upload 2019-10-17 21:12:05 +02:00
Camila San
0a373ea708 Checks if exclude file is empty before creating the regular expressions.
The default file created by the application it is not empty.

Signed-off-by: Camila San <hello@camila.codes>
2019-10-17 21:03:42 +02:00
Daniel Kesselberg
0fae01495e Add server info to menu
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
2019-10-17 21:03:23 +02:00
rakekniven
97867384b1 Fixed grammar
Reported at Transifex.
See https://www.transifex.com/nextcloud/nextcloud/translate/#nl/client/182396083

Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2019-10-17 21:03:05 +02:00
Sergey Zolotarev
c54f6e83ed Prevent jumping of tray menu
Instead of adding the "Apps" menu after the apps are fetched, add it
from the start (together with other actions) but in a disabled state,
and enable it after the apps data is ready.

Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-10-17 21:02:47 +02:00
Sergey Zolotarev
a9a731dfc0 Don't run connection wizard when quitting the application
Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-10-17 21:02:28 +02:00
Sergey Zolotarev
d0f469bd90 Replace isQuitting flag with disconnect()
Signed-off-by: Sergey Zolotarev <sryze@protonmail.com>
2019-10-17 21:02:09 +02:00
asapelkin
374375ce3f little loops optimization
Signed-off-by: asapelkin <asapelkin0x01@ya.ru>
2019-10-17 20:55:56 +02:00
Michael Schuster
89ef03412e Pick from upstream: Update qtmacgoodies for an OSX crash fix #6930
With Qt 5.12.5 this OC crash also applies to the Nextcloud Desktop Client on macOS.

For reference please see:
- https://github.com/owncloud/client/issues/6930
- 0dc7bc3328

For the required changes in the qtmacgoodies submodule see:
https://github.com/camilasan/qtmacgoodies/pull/1

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:52:42 +02:00
Nextcloud bot
07d3fe3a79 [tx-robot] updated from transifex
(cherry picked from commit 04f2bd4baa)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:56 +02:00
Nextcloud bot
24107040cc [tx-robot] updated from transifex
(cherry picked from commit 4f7d7e3601)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:49 +02:00
Nextcloud bot
9d9fc6d0bf [tx-robot] updated from transifex
(cherry picked from commit 13b2b5253e)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:41 +02:00
Nextcloud bot
51f5991f1e [tx-robot] updated from transifex
(cherry picked from commit 2121e7116e)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:32 +02:00
Javier Llorente
a8b93516cc Add sync date next to "Synchronized with local folder"
(cherry picked from commit cbc19e86fb)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:21 +02:00
Sebastian Grund
a85c228e59 issue1216: added sync-exclude entry for emacs recovery files
Signed-off-by: Sebastian Grund <grund92@gmx.de>
(cherry picked from commit a9bea53c89)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:37:11 +02:00
Michael Schuster
04a75eaca2 Add warning for failed chown in libsync/propagatedownload.cpp
In addition to PR 1409 generate warnings if chown fails.

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

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 207de071f4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:36:41 +02:00
Michael Schuster
2f46601396 Replace old NSI Windows setup wizard graphics
New UI resources based on current https://github.com/nextcloud/promo

New:
- Icon based on https://github.com/nextcloud/desktop/pull/1401
- Wizard image & header image (dotted background replaces old cloud background)

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 819a006a17)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 20:33:28 +02:00
Dominique Fuchs
18fc6a9e0e fixed wrongly assigned pointer, didn't recognize class
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit c662ff1902)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:55 +02:00
Dominique Fuchs
34675e03a8 Use -Wno-gnu-zero-variadic-macro-arguments only for Clang
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit a3825080db)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:55 +02:00
Dominique Fuchs
4b5cf94a29 Q_UNUSED for atm unused parameters
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit a237493def)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:55 +02:00
Dominique Fuchs
1729e1a94c Declared Q_UNUSED for as-of-now unused parameters.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 3a0cd45782)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:55 +02:00
Dominique Fuchs
60859714ae Prevented warning regarding operator precedence - enhanced clarity by adding parentheses
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit f08cc08eb2)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:55 +02:00
Dominique Fuchs
b5fcfd918b removed reduntant /* within a comment
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit e3685b951c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
44176be964 Remove unnecessary argument
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 6b04e2f77b)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
3935866052 Prevent use of uninitialized folder pointer.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 2e8b7771b0)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
1e9c45222c Added forgotten case when parsing log through gui. LockedFiles were not communicated.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 26e98d35e6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
adc3b1a25c initialize _modtime to prevent undefined usage
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit ab3d0141ec)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
9ae0417cad Use return type to prevent warning and determine chown success. Added TODO
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 39df36c247)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:54 +02:00
Dominique Fuchs
03453d6800 Removed disabling of msvc warning to prevent generating a unknown option for other compilers in turn. Now detect specifically GCC in ifdef
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 7473cdf184)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:38:53 +02:00
Dominique Fuchs
1ac9c4ea8d Moved macro definition due to timing issues while compiling when relying on header inheritance
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit c585e81530)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:19 +02:00
Dominique Fuchs
986bb49a88 Conditional (based on Qt version) use of 'horizontalAdvance' to provide better UI experience. See https://doc.qt.io/qt-5/qfontmetrics-obsolete.html#width
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit c779098772)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:19 +02:00
Dominique Fuchs
8f39c4140e commit 222b2d did the trick. now streamlined use of https://doc.qt.io/qt-5/qtglobal.html#QT_VERSION_CHECK
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit cc07ed1ee8)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
9c7903868f Further testing of cond. include of Qt library > 5.9. Utilized different macro.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 222b2d8645)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
8ee1adf058 Fixed another logic error -> logical to bitwise OR for QTLEGACY
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit f41eeaf6ec)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
27fb1fcd53 Fixed logic error in QTLEGACY macro and added forgottin #if clause for header file
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 48097801e8)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
29cc5c1e7f Added macro definition and compile-time condition to support Qt < 5.9
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit dca83aad45)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
29bb76019f Indeed, DWORD is a special snowflake - only when on _WIN32
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit be7a524557)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:18 +02:00
Dominique Fuchs
bf6d57f327 Fixed wrongly formatted args for win32 linker flags resulting in 'unrecognized option' for all of them. Remark: /WL is for VS only, useless (and not necessary for msvc cmd)
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 0827ff0995)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:17 +02:00
Dominique Fuchs
eb5ec05ef8 Fixed missing braces
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 503b9de2a0)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:17 +02:00
Dominique Fuchs
c723028eae Qt: Fixed numerous deprecated calls by adapting newer ones
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit a2d47cdec4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:17 +02:00
Dominique Fuchs
5d024fdf33 Added cmake preprocessor definitions when using msvc regarding the 'safe' versions of CRT functions
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit ba74c24d8f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:17 +02:00
Dominique Fuchs
ed99cb297b Use existing fct for RegKeyQuery instead of redundant subroutine
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit d60a216982)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:17 +02:00
Dominique Fuchs
a0e794a7f1 Numoerous safe conversions implemented. Added additional Utility::convertSizeToDWORD for windows builds.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit d6af025a46)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
0761342840 Corrected namespace when calling convertSizetoUint
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 82fa10c227)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
4da9123b67 Renamed conversion function to make intention more clear. Also defaulted to 'controlled truncation' to not stupidly crash. TBD/TODO: Better handling for such things.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 46e0a05078)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
c7158e2c7c Selectively and temporary disabled warning about unknown preprocessor declarative for msvc when using specific GCC instruction.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 8329de4cee)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
4adc45483a Implemented Utility::convert function to convert size_t -> uint safely and on the fly. Often necessary for Qt and WIN32 functions. Using this will not generate compiler warnings of possible truncation. First call implemented in ownsql.cpp
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit b4dee67bf5)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
ae0ff6b3e3 Fixed broken overloading mechanism of variadic templates. See code comment for further information.
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 5ae3435fe6)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:16 +02:00
Dominique Fuchs
dc6d2e6a6d usage of UINT as iterator here because comparing with UINT retval from DragQueryFile
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 9a256fcbfe)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:15 +02:00
Dominique Fuchs
5127f50d1e Removed redundant (and wrong in terms of it's value) definitions for WINVER/_WIN32_WINNT
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 112d2bfe11)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:15 +02:00
Dominique Fuchs
a26f2a7359 Removed redundant (and wrong in terms of it's value) definitions for WINVER/_WIN32_WINNT
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 69a11a7ec1)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:15 +02:00
Dominique Fuchs
42f1f445a9 Removed redundant (and wrong in terms of it's value) definitions for WINVER/_WIN32_WINNT
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 3960ffea3f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:15 +02:00
Dominique Fuchs
51304485c3 Updated WINVER/_WIN32_WINNT from 0x0600 to 0x0601 (e.g. Server 2008/Vista to 7) as 7 is reasonable and noted everywhere as requirement
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit 78543deee4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 19:34:12 +02:00
Björn Bidar
63cc6edddd fix qt warning about registering a URL sheme first.
Qt recommends to register a URL scheme before installing it.
I don't know the impact of the not registering before instaling but I
think the change is pretty harmles.

See:
https://doc.qt.io/qt-5/qwebengineurlscheme.html#registerScheme
Signed-off-by: Björn Bidar <theodorstormgrade@gmail.com>
(cherry picked from commit cea0d519a4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 18:08:39 +02:00
Dominique Fuchs
58abebe9ac Fixed e2e key transmission issue after generation (forgotten content type on sendrequest())
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
(cherry picked from commit a35b346e62)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-17 17:33:41 +02:00
Michael Schuster
1182ae9e26 Merge pull request #1515 from nextcloud/qt5-mac-5.12-prepare
Add new "styles" plugin to macOS deployment script for Qt 5.12.5
2019-10-15 23:58:26 +02:00
Michael Schuster
3407174c2f Add new "styles" plugin to macOS deployment script for Qt 5.12.5
Qt 5.12 needs this library to use the correct style for Light / Dark Mode:

  styles/libqmacstyle.dylib

The interface looks like from the 1990's without this library ;-)

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-14 02:01:56 +02:00
Michael Schuster
913894eaa5 Merge pull request #1496 from nextcloud/backport/1495/stable-2.6
[stable-2.6] Add a 'Content-Length: 0' header to initial POST requests
2019-10-10 08:11:43 +02:00
Michael Schuster
db91552578 Add a 'Content-Length: 0' header to initial POST requests
The webserver lighttpd rejected POST requests without a Content-length
header with "411 Length Required".

See: https://github.com/nextcloud/desktop/issues/1473

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-10 06:09:59 +00:00
Camila San
286e45bafe Bump version to 2.6.0
Signed-off-by: Camila San <hello@camila.codes>
(cherry picked from commit e0b32c19e4)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-05 15:13:59 +02:00
Michael Schuster
aa1bb470e6 fix comment typo
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 905c1532fe)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-05 14:56:48 +02:00
Michael Schuster
3be9adde4b Fix double slashes in WebDAV URLs (account setup wizard)
Sanitize URL paths to elaminate double-slashes in the URL path string,
used for the first connection by the account setup wizard.

Example: https://cloud.example.com/remote.php/webdav//

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 67107a4f5d)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-10-05 14:56:15 +02:00
István Váradi
41d97abd08 Merge pull request #1475 from ivaradi/stable-2.6
Remove kdelibs5-dev from the build dependencies for Eoan
2019-10-05 09:33:00 +02:00
István Váradi
6bc232c9b4 Remove kdelibs5-dev from the build dependencies for Eoan
Signed-off-by: István Váradi <ivaradi@varadiistvan.hu>
2019-10-05 09:31:55 +02:00
István Váradi
d4a0be92ae Merge pull request #1466 from ivaradi/stable-2.6.0
Trigger builds for tagging on the 2.6.0 branch
2019-10-04 20:19:38 +02:00
István Váradi
75bf41fba1 Trigger builds for tagging on the 2.6.0 branch
Signed-off-by: István Váradi <ivaradi@varadiistvan.hu>
2019-10-02 19:18:13 +02:00
Camila San
a2bfd5039c Revert "Fix White Window issue on Windows after Qt 5.12.4 upgrade"
This reverts commit aeba2e4de6.
2019-09-27 15:04:14 +02:00
Camila San
a9ee7472b9 Improve wording of the context menu in the file manager extension.
'Share...' -> 'Share options'
'private link' -> 'internal link'
Removes 'to clipboard' from 'copy link' options.

Signed-off-by: Camila San <hello@camila.codes>
2019-09-26 13:24:10 +02:00
Michael Schuster
211d6cb162 UI improvement: Message box: Delete / Keep all files
Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-09-26 12:05:34 +02:00
Mariusz Wasak
501c353291 Fix for #1382 "linux client crashes for no discernable reason"
There in no "return" in
PropagateUploadFileCommon::slotStartUpload in if (prevModtime != _item-
>_modtime) {... }

There is possibility that
PropagateItemJob::done(status, errorString)
maybe called two times from PropagateUploadFileCommon::slotStartUpload
1. in if (prevModtime != _item->_modtime) {... }
2. in if (fileIsStillChanging(*_item)) {..}
if changes in files are frequent the second call is possible.

This two calls has effect in PropagatorCompositeJob::slotSubJobFinished
and job is removed two times in _runningJobs.remove(i);
(the second time with argumetnt -1 (because first call removed job).

This return was removed in commit
efc039863b - by accident I think.

Good simulation is to synchronize firefox profile with frequent page
refresh.

Signed-off-by: Mariusz Wasak <mawasak@gmail.com>
2019-09-26 12:03:20 +02:00
Michael Schuster
aeba2e4de6 Fix White Window issue on Windows after Qt 5.12.4 upgrade
Qt 5.12.4 seems to introduce a new bug on Windows, causing the settings window
to not be redrawn when re-opening it, for example by clicking at the tray icon.

As a workaround this fix starts a 100 ms timer to be fired once upon
QDialog::showEvent is called.

Signed-off-by: Michael Schuster <michael@schuster.ms>
2019-09-26 11:29:49 +02:00
Camila Ayres
2f9f84c1f2 Merge pull request #1447 from nextcloud/backport/1438/stable-2.6
[stable-2.6] Changes wording in the share context menu.
2019-09-26 11:16:17 +02:00
Camila San
33646b1775 Changes wording in the share context menu.
Instead of only Nextcloud it says "Share via Nextcloud".

Signed-off-by: Camila San <hello@camila.codes>
2019-09-26 09:12:28 +00:00
176 changed files with 24412 additions and 18617 deletions

View File

@@ -22,11 +22,11 @@ steps:
source /opt/qt57/bin/qt57-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -59,11 +59,11 @@ steps:
source /opt/qt58/bin/qt58-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -96,11 +96,11 @@ steps:
source /opt/qt59/bin/qt59-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -137,11 +137,11 @@ steps:
source /opt/qt510/bin/qt510-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -178,11 +178,11 @@ steps:
source /opt/qt511/bin/qt511-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -219,11 +219,11 @@ steps:
source /opt/qt511/bin/qt511-env.sh &&
mkdir build &&
cd build &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -268,11 +268,11 @@ steps:
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 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -317,11 +317,11 @@ steps:
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 -DSANITIZE_ADDRESS=ON ../ &&
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
make &&
useradd -m -s /bin/bash test &&
chown -R test:test . &&
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
su -c 'ctest --output-on-failure' test"
trigger:
branch:
- master
@@ -361,10 +361,9 @@ steps:
from_secret: DEBIAN_SECRET_IV
trigger:
branch:
- master
- stable-2.6
event:
- pull_request
- push
- tag
---
kind: pipeline
name: Documentation

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
# You can add one username per supported platform and one custom link
custom: https://www.bountysource.com/teams/nextcloud/issues?tracker_ids=74294474

67
.github/stale.yml vendored
View File

@@ -1,67 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 28
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- 1. to develop
- 2. to review
- 3. to release
- 4. to test
- accessibility
- backport-request
- bug
- design
- enhancement
- epic
- discussion
- documentation
- overview
- good first issue
- feature-request
- feature: :arrows_counterclockwise: sync engine
- feature: :busts_in_silhouette: sharing
- feature: :cloud: system tray
- feature: :gear: settings
- feature: :inbox_tray: install and update
- feature: :key: authentication
- feature: :lock: end to end encryption
- feature: :memo: versions
- feature: :minidisc: external storage
- feature: :minidisc: virtual drive
- feature: :new: versions
- feature: :tongue: language l10n and translations
- feature: :wheelchair: accessibility
- feature: :white_square_button: nextcloudcmd
- feature: :zap: activity and :bell: notification
- good first issue
- help wanted
- high
- integration
- low
- medium
- needs info
- os: :apple: macOS
- os: :door: Windows
- os: :penguin: Linux
- os: :smiling_imp: FreeBSD
- overview
- package: appimage
- package: debian
- package: snap
- papercut
- regression
- security
- server
- spec
- technical debt
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This request did not receive an update in the last 4 weeks.
Please take a look again and update the issue with new details,
otherwise the issue will be automatically closed in 2 weeks. Thank you!
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[cs_CZ]=@APPLICATION_ICON_NAME@
Icon[cs_CZ]=@NAZEV_IKONY_APLIKACE@
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
GenericName[cs_CZ]=Synchronizace složek

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

@@ -1,204 +0,0 @@
[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[es_AR]=@APPLICATION_ICON_NAME@
Name[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
Comment[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
GenericName[es_AR]=Sincronización de carpetas

View File

@@ -199,6 +199,4 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[sv]=@APPLICATION_ICON_NAME@
Name[sv]=@APPLICATION_NAME@ desktopssynkklient
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
GenericName[sv]=Mappsynkronisering

View File

@@ -219,12 +219,6 @@ if (APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
option(SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
if (SANITIZE_ADDRESS)
include(SanitizerFlags)
enable_sanitizer()
endif ()
# Handle Translations, pick all client_* files from trans directory.
file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts)
set(TRANSLATIONS ${TRANS_FILES})

View File

@@ -1,6 +1,3 @@
Will be tracked going forward here:
https://github.com/nextcloud/desktop/releases
2.5 Series ChangeLog
====================

View File

@@ -7,12 +7,11 @@ set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
set( APPLICATION_ICON_NAME "Nextcloud" )
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}")
set( THEME_CLASS "NextcloudTheme" )
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" )
set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image")

View File

@@ -1,7 +1,7 @@
set( MIRALL_VERSION_MAJOR 2 )
set( MIRALL_VERSION_MINOR 7 )
set( MIRALL_VERSION_PATCH 0 )
set( MIRALL_VERSION_YEAR 2020 )
set( MIRALL_VERSION_MINOR 6 )
set( MIRALL_VERSION_PATCH 2 )
set( MIRALL_VERSION_YEAR 2019 )
set( MIRALL_SOVERSION 0 )
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )

View File

@@ -12,20 +12,17 @@ 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
export PKG_CONFIG_PATH=$QT_BASE_DIR/lib/pkgconfig:$PKG_CONFIG_PATH
#Set APPID for .desktop file processing
export LINUX_APPLICATION_ID=com.nextcloud.desktopclient.nextcloud
#set defaults
export SUFFIX=${DRONE_PULL_REQUEST:=master}
if [ $SUFFIX != "master" ]; then
SUFFIX="PR-$SUFFIX"
fi
#QtKeyChain master
#QtKeyChain 0.9.1
cd /build
git clone https://github.com/frankosterfeld/qtkeychain.git
cd qtkeychain
git checkout master
git checkout v0.9.1
mkdir build
cd build
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
@@ -65,7 +62,7 @@ rm -rf ./usr/share/caja-python/
rm -rf ./usr/share/nautilus-python/
rm -rf ./usr/share/nemo-python/
# Move sync exclude to right location
# Move sync exlucde to right location
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
rm -rf ./etc
@@ -91,7 +88,7 @@ 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 ${DESKTOP_FILE} -bundle-non-qt-libs -qmldir=$DRONE_WORKSPACE/src/gui
./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

View File

@@ -1,8 +1,16 @@
<RCC>
<qresource prefix="/client">
<file>resources/dialog-close.png</file>
<file>resources/dialog-ok.png</file>
<file>resources/dialog-cancel.png</file>
<file>resources/folder-sync.png</file>
<file>resources/folder-sync@2x.png</file>
<file>resources/task-ongoing.png</file>
<file>resources/view-refresh.png</file>
<file>resources/warning.png</file>
<file>resources/warning@2x.png</file>
<file>resources/settings.png</file>
<file>resources/settings@2x.png</file>
<file>resources/activity.svg</file>
<file>resources/activity.png</file>
<file>resources/activity@2x.png</file>
<file>resources/network.png</file>
@@ -12,13 +20,13 @@
<file>resources/lock-https.png</file>
<file>resources/lock-https@2x.png</file>
<file>resources/account.png</file>
<file>resources/account.svg</file>
<file>resources/more.svg</file>
<file>resources/delete.png</file>
<file>resources/close.svg</file>
<file>resources/bell.svg</file>
<file>resources/link.svg</file>
<file>resources/files.svg</file>
<file>resources/folder-grey.png</file>
<file>resources/state-error.svg</file>
<file>resources/state-warning.svg</file>
<file>resources/folder.svg</file>
@@ -30,14 +38,7 @@
<file>resources/copy.svg</file>
<file>resources/state-sync.svg</file>
<file>resources/add.png</file>
<file>resources/add-color.svg</file>
<file>resources/state-info.svg</file>
<file>resources/change.svg</file>
<file>resources/delete-color.svg</file>
</qresource>
<qresource prefix="/"/>
<qresource prefix="/qml">
<file>src/gui/tray/Window.qml</file>
<file>src/gui/tray/UserLine.qml</file>
</qresource>
</RCC>

View File

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

View File

@@ -1,17 +0,0 @@
# Enable address sanitizer (gcc/clang only)
macro(ENABLE_SANITIZER)
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
message(FATAL_ERROR "Sanitizer supported only for gcc/clang")
endif()
set(SANITIZER_FLAGS "-fsanitize=address -fsanitize=leak -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
set(LINKER_FLAGS "-fsanitize=address,undefined -fuse-ld=gold")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
endmacro()

View File

@@ -8,12 +8,12 @@
#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@"
#cmakedefine APPLICATION_DOMAIN @APPLICATION_DOMAIN@
#cmakedefine THEME_CLASS @THEME_CLASS@
#cmakedefine THEME_INCLUDE @THEME_INCLUDE@
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@"
#cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@"
#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@"
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
@@ -21,7 +21,6 @@
#cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@"
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
#cmakedefine APPLICATION_SERVER_URL "@APPLICATION_SERVER_URL@"
#cmakedefine LINUX_APPLICATION_ID "@LINUX_APPLICATION_ID@"
#cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
#cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
#cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"

View File

@@ -13,19 +13,11 @@ desktop client.
These instructions are updated to work with version |version| of the Nextcloud Client.
You have two possibilities to clone the repo.
Getting Source Code
-------------------
First option is As [remote URL](https://help.github.com/en/articles/which-remote-url-should-i-use) you can choose between cloning with HTTPS URL's, which is recommended or cloning with SSH URL's.
[https://github.com/nextcloud/desktop.git](https://github.com/nextcloud/desktop.git)
When you don't have SSH key added to your GitHub account, than use HTTPS.
When you no part of the nextcloud organisation, clone with HTTPS:
```
$ git clone git@github.com:nextcloud/desktop.git
```
The :ref:`generic-build-instructions` pull the latest code directly from
GitHub, and work on Linux, macOS, and Windows.
macOS
-----
@@ -55,7 +47,7 @@ To set up your build environment for development using HomeBrew_:
5. Install a Qt5 version with qtwebkit support::
brew install qt5
brew install qt5 --with-qtwebkit
6. Install any missing dependencies::
@@ -75,23 +67,10 @@ To set up your build environment for development using HomeBrew_:
10. Install the Packages_ package creation tool.
11. Enable git submodules:
```
$ cd desktop
$ git submodule init
$ git submodule update
```
12. Generate the build files:
```
$ cd build
$ cmake .. -DCMAKE_INSTALL_PREFIX=~/nextcloud-desktop-client -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1
```
13. Compile and install:
```
$ make install
```
11. In the build directory, run ``admin/osx/create_mac.sh <build_dir> <install_dir>``.
If you have a developer signing certificate, you can specify
its Common Name as a third parameter (use quotes) to have the package
signed automatically.
.. note:: Contrary to earlier versions, Nextcloud 1.7 and later are packaged
as a ``pkg`` installer. Do not call "make package" at any time when

View File

@@ -27,7 +27,7 @@ download page.
System Requirements
----------------------------------
- Windows 8.1+
- Windows 7+
- macOS 10.7+ (**64-bit only**)
- CentOS 6 & 7 (64-bit only)
- Debian 8.0 & 9.0

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewbox="0 0 16 16"><path fill="#00d400" d="M9.02 13.98h-2v-5h-5v-2h5v-5h2v5l5-.028V8.98h-5z"/></svg>

Before

Width:  |  Height:  |  Size: 179 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path d="m8 2c-2.142 0-4.125 1.145-5.196 3l1.948 1.125c0.671-1.162 1.906-1.875 3.2476-1.875 1.1906 0 2.297 0.56157 3 1.5l-1.5 1.5h4.5v-4.5l-1.406 1.406c-1.129-1.348-2.802-2.1563-4.594-2.1563z"/><path d="m2 8.75v4.5l1.408-1.41c1.116 1.334 2.817 2.145 4.592 2.16 2.16 0.01827 4.116-1.132 5.196-3.002l-1.948-1.125c-0.677 1.171-1.9005 1.886-3.248 1.875-1.18-0.01-2.3047-0.572-3-1.5l1.5-1.5z"/></svg>

Before

Width:  |  Height:  |  Size: 493 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16"><path d="m3.0503 4.4645 3.5355 3.5355-3.5355 3.536 1.4142 1.414 3.5355-3.5358 3.536 3.5358 1.414-1.414-3.5358-3.536 3.5358-3.5355-1.414-1.4142-3.536 3.5355-3.5355-3.5355-1.4142 1.4142z" fill="#d40000"/></svg>

Before

Width:  |  Height:  |  Size: 306 B

BIN
resources/dialog-cancel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
resources/dialog-close.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
resources/dialog-ok.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
resources/folder-grey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

BIN
resources/folder-sync.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
resources/task-ongoing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
resources/view-refresh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
resources/warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

BIN
resources/warning@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -499,7 +499,7 @@ restart_sync:
}
Cmd cmd;
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user);
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(options.source_dir, credentialFreeUrl, folder, user);
SyncJournalDb db(dbPath);
if (!selectiveSyncList.empty()) {

View File

@@ -23,7 +23,6 @@
#include <QElapsedTimer>
#include <QUrl>
#include <QDir>
#include <QStandardPaths>
#include <sqlite3.h>
#include "common/syncjournaldb.h"
@@ -103,15 +102,11 @@ SyncJournalDb::SyncJournalDb(const QString &dbFilePath, QObject *parent)
}
}
QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
QString SyncJournalDb::makeDbName(const QString &localPath,
const QUrl &remoteUrl,
const QString &remotePath,
const QString &user)
{
const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
if (!QDir(dbPath).exists()) {
QDir().mkdir(dbPath);
}
QString journalPath = QLatin1String("._sync_");
QString key = QString::fromUtf8("%1@%2:%3").arg(user, remoteUrl.toString(), remotePath);
@@ -120,16 +115,17 @@ QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
journalPath.append(ba.left(6).toHex());
journalPath.append(".db");
journalPath = dbPath + QLatin1Char('/') + journalPath;
// If the journal doesn't exist and we can't create a file
// at that location, try again with a journal name that doesn't
// have the ._ prefix.
//
// The disadvantage of that filename is that it will only be ignored
// by client versions >2.3.2.
//
// See #5633: "._*" is often forbidden on samba shared folders.
// If it exists already, the path is clearly usable
QFile file(QDir(dbPath).filePath(journalPath));
QFile file(QDir(localPath).filePath(journalPath));
if (file.exists()) {
return journalPath;
}
@@ -144,7 +140,7 @@ QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
// Can we create it if we drop the underscore?
QString alternateJournalPath = journalPath.mid(2).prepend(".");
QFile file2(QDir(dbPath).filePath(alternateJournalPath));
QFile file2(QDir(localPath).filePath(alternateJournalPath));
if (file2.open(QIODevice::ReadWrite)) {
// The alternative worked, use it
qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath;

View File

@@ -46,7 +46,8 @@ public:
virtual ~SyncJournalDb();
/// Create a journal path for a specific configuration
static QString makeDbName(const QUrl &remoteUrl,
static QString makeDbName(const QString &localPath,
const QUrl &remoteUrl,
const QString &remotePath,
const QString &user);

View File

@@ -255,7 +255,7 @@ void Utility::usleep(int usec)
}
// This can be overriden from the tests
OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool {
OCSYNC_EXPORT bool fsCasePreserving_override = []()-> bool {
QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
if (!env.isEmpty())
return env.toInt();
@@ -362,12 +362,12 @@ QString Utility::fileNameForGuiUse(const QString &fName)
QByteArray Utility::normalizeEtag(QByteArray etag)
{
/* strip "XXXX-gzip" */
if (etag.startsWith('"') && etag.endsWith("-gzip\"")) {
if(etag.startsWith('"') && etag.endsWith("-gzip\"")) {
etag.chop(6);
etag.remove(0, 1);
}
/* strip trailing -gzip */
if (etag.endsWith("-gzip")) {
if(etag.endsWith("-gzip")) {
etag.chop(5);
}
/* strip normal quotes */
@@ -400,7 +400,7 @@ void Utility::crash()
// without compiler warnings about possible truncation
uint Utility::convertSizeToUint(size_t &convertVar)
{
if (convertVar > UINT_MAX) {
if( convertVar > UINT_MAX ) {
//throw std::bad_cast();
convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
}
@@ -409,7 +409,7 @@ uint Utility::convertSizeToUint(size_t &convertVar)
uint Utility::convertSizeToInt(size_t &convertVar)
{
if (convertVar > INT_MAX) {
if( convertVar > INT_MAX ) {
//throw std::bad_cast();
convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
}
@@ -465,7 +465,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
if (floor(secs / 3600.0) > 0) {
int hours = floor(secs / 3600.0);
if (hours == 1) {
if(hours == 1){
return (QObject::tr("%n hour ago", "", hours));
} else {
return (QObject::tr("%n hours ago", "", hours));
@@ -480,7 +480,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
return QObject::tr("Less than a minute ago");
}
} else if (minutes == 1) {
} else if(minutes == 1){
return (QObject::tr("%n minute ago", "", minutes));
} else {
return (QObject::tr("%n minutes ago", "", minutes));

View File

@@ -20,7 +20,6 @@
#ifndef UTILITY_H
#define UTILITY_H
#include "ocsynclib.h"
#include <QString>
#include <QByteArray>
@@ -30,7 +29,6 @@
#include <QMap>
#include <QUrl>
#include <QUrlQuery>
#include <QtQuick/QQuickImageProvider>
#include <functional>
#include <memory>

View File

@@ -47,18 +47,14 @@ QString getUserAutostartDir_private()
bool hasLaunchOnStartup_private(const QString &appName)
{
QString desktopFileLocation = getUserAutostartDir_private()
+ QLatin1String(LINUX_APPLICATION_ID)
+ QLatin1String(".desktop");
QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop");
return QFile::exists(desktopFileLocation);
}
void setLaunchOnStartup_private(const QString &appName, const QString &guiName, bool enable)
{
QString userAutoStartPath = getUserAutostartDir_private();
QString desktopFileLocation = userAutoStartPath
+ QLatin1String(LINUX_APPLICATION_ID)
+ QLatin1String(".desktop");
QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
if (enable) {
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;

View File

@@ -1,5 +1,5 @@
project(gui)
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg)
find_package(Qt5 REQUIRED COMPONENTS Widgets)
set(CMAKE_AUTOMOC TRUE)
set(CMAKE_AUTOUIC TRUE)
set(CMAKE_AUTORCC TRUE)
@@ -24,6 +24,7 @@ set(client_UI_SRCS
ignorelisteditor.ui
ignorelisttablewidget.ui
networksettings.ui
activitywidget.ui
synclogdialog.ui
settingsdialog.ui
sharedialog.ui
@@ -34,8 +35,6 @@ set(client_UI_SRCS
addcertificatedialog.ui
proxyauthdialog.ui
mnemonicdialog.ui
tray/Window.qml
tray/UserLine.qml
wizard/flow2authwidget.ui
wizard/owncloudadvancedsetuppage.ui
wizard/owncloudconnectionmethoddialog.ui
@@ -74,6 +73,10 @@ set(client_SRCS
openfilemanager.cpp
owncloudgui.cpp
owncloudsetupwizard.cpp
activitydata.cpp
activitylistmodel.cpp
activitywidget.cpp
activityitemdelegate.cpp
selectivesyncdialog.cpp
settingsdialog.cpp
sharedialog.cpp
@@ -96,15 +99,12 @@ set(client_SRCS
synclogdialog.cpp
tooltipupdater.cpp
notificationconfirmjob.cpp
servernotificationhandler.cpp
guiutility.cpp
elidedlabel.cpp
headerbanner.cpp
iconjob.cpp
remotewipe.cpp
tray/ActivityData.cpp
tray/ActivityListModel.cpp
tray/UserModel.cpp
tray/NotificationHandler.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/oauth.cpp
@@ -298,7 +298,7 @@ else()
endif()
add_library(updater STATIC ${updater_SRCS})
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
@@ -308,7 +308,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" )
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml)
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Network Qt5::Xml)
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
target_link_libraries( ${APPLICATION_EXECUTABLE} updater )
target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} )
@@ -388,7 +388,7 @@ endif()
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in
${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop DESTINATION ${DATADIR}/applications )
${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop DESTINATION ${DATADIR}/applications )
endif()

View File

@@ -89,9 +89,6 @@ private:
// Adds an account to the tracked list, emitting accountAdded()
void addAccountState(AccountState *accountState);
AccountManager() {}
QList<AccountStatePtr> _accounts;
public slots:
/// Saves account data, not including the credentials
void saveAccount(Account *a);
@@ -107,5 +104,9 @@ Q_SIGNALS:
void accountAdded(AccountState *account);
void accountRemoved(AccountState *account);
void removeAccountFolders(AccountState *account);
private:
AccountManager() {}
QList<AccountStatePtr> _accounts;
};
}

View File

@@ -143,6 +143,9 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
_ui->_folderList->setAttribute(Qt::WA_Hover, true);
_ui->_folderList->installEventFilter(mouseCursorChanger);
createAccountToolbox();
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &AccountSettings::slotAccountAdded);
connect(this, &AccountSettings::removeAccountFolders,
AccountManager::instance(), &AccountManager::removeAccountFolders);
connect(_ui->_folderList, &QWidget::customContextMenuRequested,
@@ -204,13 +207,36 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
_ui->encryptionMessage->hide();
}
connect(UserModel::instance(), &UserModel::addAccount,
this, &AccountSettings::slotOpenAccountWizard);
customizeStyle();
}
void AccountSettings::createAccountToolbox()
{
QMenu *menu = new QMenu();
connect(menu, &QMenu::aboutToShow, this, &AccountSettings::slotMenuBeforeShow);
_addAccountAction = new QAction(tr("Add new"), this);
menu->addAction(_addAccountAction);
connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard);
_toggleSignInOutAction = new QAction(tr("Log out"), this);
connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState);
menu->addAction(_toggleSignInOutAction);
QAction *action = new QAction(tr("Remove"), this);
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);
slotAccountAdded(_accountState);
}
void AccountSettings::slotNewMnemonicGenerated()
{
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
@@ -223,6 +249,24 @@ void AccountSettings::slotNewMnemonicGenerated()
_ui->encryptionMessage->show();
}
void AccountSettings::slotMenuBeforeShow() {
if (_menuShown) {
return;
}
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()) {
QAction *mnemonic = new QAction(tr("Show E2E mnemonic"), this);
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
menu->addAction(mnemonic);
}
_menuShown = true;
}
QString AccountSettings::selectedFolderAlias() const
{
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
@@ -1016,12 +1060,21 @@ void AccountSettings::slotAccountStateChanged()
// sync user interface buttons.
refreshSelectiveSyncStatus();
/* set the correct label for the Account toolbox button */
if (_accountState) {
if (_accountState->isSignedOut()) {
_toggleSignInOutAction->setText(tr("Log in"));
} else {
_toggleSignInOutAction->setText(tr("Log out"));
}
}
if (state == AccountState::State::Connected) {
/* TODO: We should probably do something better here.
* Verify if the user has a private key already uploaded to the server,
* if it has, do not offer to create one.
*/
qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName()
qCInfo(lcAccountSettings) << "Accout" << accountsState()->account()->displayName()
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
}
}
@@ -1137,6 +1190,18 @@ void AccountSettings::refreshSelectiveSyncStatus()
}
}
void AccountSettings::slotAccountAdded(AccountState *)
{
// if the theme is limited to single account, the button must hide if
// there is already one account.
int s = AccountManager::instance()->accounts().size();
if (s > 0 && !Theme::instance()->multiAccount()) {
_addAccountAction->setVisible(false);
} else {
_addAccountAction->setVisible(true);
}
}
void AccountSettings::slotDeleteAccount()
{
// Deleting the account potentially deletes 'this', so

View File

@@ -90,6 +90,7 @@ protected slots:
void slotDeleteAccount();
void slotToggleSignInState();
void slotOpenAccountWizard();
void slotAccountAdded(AccountState *);
void refreshSelectiveSyncStatus();
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
@@ -99,6 +100,8 @@ protected slots:
void doExpand();
void slotLinkActivated(const QString &link);
void slotMenuBeforeShow();
// Encryption Related Stuff.
void slotShowMnemonic(const QString &mnemonic);
void slotNewMnemonicGenerated();

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>581</width>
<width>582</width>
<height>557</height>
</rect>
</property>
@@ -184,6 +184,13 @@
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="_accountToolbox">
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -20,7 +20,6 @@
#include "creds/httpcredentials.h"
#include "logger.h"
#include "configfile.h"
#include "ocsnavigationappsjob.h"
#include <QSettings>
#include <QTimer>
@@ -28,7 +27,6 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QNetworkRequest>
#include <QBuffer>
@@ -42,9 +40,9 @@ AccountState::AccountState(AccountPtr account)
, _state(AccountState::Disconnected)
, _connectionStatus(ConnectionValidator::Undefined)
, _waitingForNewCredentials(false)
, _notificationsEtagResponseHeader("*")
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
, _remoteWipe(new RemoteWipe(_account))
, _hasTalk(false)
{
qRegisterMetaType<AccountState *>("AccountState*");
@@ -76,11 +74,6 @@ AccountPtr AccountState::account() const
return _account;
}
bool AccountState::hasTalk() const
{
return _hasTalk;
}
AccountState::ConnectionStatus AccountState::connectionStatus() const
{
return _connectionStatus;
@@ -244,9 +237,6 @@ void AccountState::checkConnectivity()
// Use a small authed propfind as a minimal ping when we're
// already connected.
conValidator->checkAuthentication();
// Get the Apps available on the server.
fetchNavigationApps();
} else {
// Check the server and then the auth.
@@ -277,7 +267,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
// Come online gradually from 503 or maintenance mode
if (status == ConnectionValidator::Connected
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
if (!_timeSinceMaintenanceOver.isValid()) {
qCInfo(lcAccountState) << "AccountState reconnection: delaying for"
<< _maintenanceToConnectedDelay << "ms";
@@ -303,9 +293,6 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
case ConnectionValidator::Connected:
if (_state != Connected) {
setState(Connected);
// Get the Apps available on the server.
fetchNavigationApps();
}
break;
case ConnectionValidator::Undefined:
@@ -418,110 +405,4 @@ std::unique_ptr<QSettings> AccountState::settings()
return s;
}
void AccountState::fetchNavigationApps(){
OcsNavigationAppsJob *job = new OcsNavigationAppsJob(_account);
job->addRawHeader("If-None-Match", navigationAppsEtagResponseHeader());
connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &AccountState::slotNavigationAppsFetched);
connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &AccountState::slotEtagResponseHeaderReceived);
connect(job, &OcsNavigationAppsJob::ocsError, this, &AccountState::slotOcsError);
job->getNavigationApps();
}
void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
if(statusCode == 200){
qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
setNavigationAppsEtagResponseHeader(value);
}
}
void AccountState::slotOcsError(int statusCode, const QString &message)
{
qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message;
}
void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
{
if(_account){
if (statusCode == 304) {
qCWarning(lcAccountState) << "Status code " << statusCode << " Not Modified - No new navigation apps.";
} else {
_apps.clear();
_hasTalk = false;
if(!reply.isEmpty()){
auto element = reply.object().value("ocs").toObject().value("data");
auto navLinks = element.toArray();
if(navLinks.size() > 0){
foreach (const QJsonValue &value, navLinks) {
auto navLink = value.toObject();
AccountApp *app = new AccountApp(navLink.value("name").toString(), QUrl(navLink.value("href").toString()),
navLink.value("id").toString(), QUrl(navLink.value("icon").toString()));
_apps << app;
if(app->id() == QLatin1String("spreed"))
_hasTalk = true;
}
}
}
emit hasFetchedNavigationApps();
}
}
}
AccountAppList AccountState::appList() const
{
return _apps;
}
AccountApp* AccountState::findApp(const QString &appId) const
{
if(!appId.isEmpty()) {
foreach(AccountApp *app, appList()) {
if(app->id() == appId)
return app;
}
}
return nullptr;
}
/*-------------------------------------------------------------------------------------*/
AccountApp::AccountApp(const QString &name, const QUrl &url,
const QString &id, const QUrl &iconUrl,
QObject *parent)
: QObject(parent)
, _name(name)
, _url(url)
, _id(id)
, _iconUrl(iconUrl)
{
}
QString AccountApp::name() const
{
return _name;
}
QUrl AccountApp::url() const
{
return _url;
}
QString AccountApp::id() const
{
return _id;
}
QUrl AccountApp::iconUrl() const
{
return _iconUrl;
}
/*-------------------------------------------------------------------------------------*/
} // namespace OCC

View File

@@ -29,11 +29,9 @@ namespace OCC {
class AccountState;
class Account;
class AccountApp;
class RemoteWipe;
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
typedef QList<AccountApp*> AccountAppList;
/**
* @brief Extra info about an ownCloud server account.
@@ -103,11 +101,6 @@ public:
bool isSignedOut() const;
bool hasTalk() const;
AccountAppList appList() const;
AccountApp* findApp(const QString &appId) const;
/** A user-triggered sign out which disconnects, stops syncs
* for the account and forgets the password. */
void signOutByUi();
@@ -168,12 +161,10 @@ public slots:
private:
void setState(State state);
void fetchNavigationApps();
signals:
void stateChanged(int state);
void isConnectedChanged();
void hasFetchedNavigationApps();
protected Q_SLOTS:
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
@@ -185,17 +176,12 @@ protected Q_SLOTS:
void slotCredentialsFetched(AbstractCredentials *creds);
void slotCredentialsAsked(AbstractCredentials *creds);
void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotOcsError(int statusCode, const QString &message);
private:
AccountPtr _account;
State _state;
ConnectionStatus _connectionStatus;
QStringList _connectionErrors;
bool _waitingForNewCredentials;
bool _hasTalk;
QElapsedTimer _timeSinceLastETagCheck;
QPointer<ConnectionValidator> _connectionValidator;
QByteArray _notificationsEtagResponseHeader;
@@ -219,34 +205,7 @@ private:
*/
RemoteWipe *_remoteWipe;
/**
* Holds the App names and URLs available on the server
*/
AccountAppList _apps;
};
class AccountApp : public QObject
{
Q_OBJECT
public:
AccountApp(const QString &name, const QUrl &url,
const QString &id, const QUrl &iconUrl,
QObject* parent = 0);
QString name() const;
QUrl url() const;
QString id() const;
QUrl iconUrl() const;
private:
QString _name;
QUrl _url;
QString _id;
QUrl _iconUrl;
};
}
Q_DECLARE_METATYPE(OCC::AccountState *)

View File

@@ -14,7 +14,7 @@
#include <QtCore>
#include "ActivityData.h"
#include "activitydata.h"
namespace OCC {

View File

@@ -56,7 +56,6 @@ public:
Type _type;
qlonglong _id;
QString _fileAction;
QString _objectType;
QString _subject;
QString _message;
@@ -65,8 +64,6 @@ public:
QUrl _link;
QDateTime _dateTime;
QString _accName;
QString _icon;
QString _iconData;
// Stores information about the error
int _status;

View File

@@ -0,0 +1,407 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Olivier Goffart <ogoffart@woboq.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 "activityitemdelegate.h"
#include "activitylistmodel.h"
#include "folderstatusmodel.h"
#include "folderman.h"
#include "accountstate.h"
#include "activitydata.h"
#include <theme.h>
#include <account.h>
#include <QFileIconProvider>
#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 {
int ActivityItemDelegate::_iconHeight = 0;
int ActivityItemDelegate::_margin = 0;
int ActivityItemDelegate::_primaryButtonWidth = 0;
int ActivityItemDelegate::_secondaryButtonWidth = 0;
int ActivityItemDelegate::_spaceBetweenButtons = 0;
int ActivityItemDelegate::_timeWidth = 0;
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) {
QStyleOptionViewItem option;
QFont font = option.font;
QFontMetrics fm(font);
_iconHeight = qRound(fm.height() / 5.0 * 8.0);
}
return _iconHeight;
}
int ActivityItemDelegate::rowHeight()
{
if (_margin == 0) {
QStyleOptionViewItem opt;
QFont f = opt.font;
QFontMetrics fm(f);
_margin = fm.height() / 2;
#if defined(Q_OS_WIN)
_margin += 5;
#endif
}
return iconHeight() + 5 * _margin;
}
QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex & /* index */) const
{
QFont font = option.font;
return QSize(0, rowHeight());
}
void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
QFont font = option.font;
QFontMetrics fm(font);
int margin = fm.height() / 2.5;
painter->save();
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;
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));
QList<QVariant> customList = index.data(ActionsLinksRole).toList();
QString timeText = qvariant_cast<QString>(index.data(PointInTimeRole));
bool accountOnline = qvariant_cast<bool>(index.data(AccountConnectedRole));
// activity/notification icons
QRect actionIconRect = option.rect;
actionIconRect.setLeft(option.rect.left() + iconOffset/3);
actionIconRect.setRight(option.rect.left() + iconOffset);
actionIconRect.setTop(option.rect.top() + qRound((option.rect.height() - 16)/3.0));
// subject text rect
QRect actionTextBox = actionIconRect;
#if (HASQT5_11)
int actionTextBoxWidth = fm.horizontalAdvance(actionText);
#else
int actionTextBoxWidth = fm.width(actionText);
#endif
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
QRect messageTextBox = actionTextBox;
#if (HASQT5_11)
int messageTextWidth = fm.horizontalAdvance(messageText);
#else
int messageTextWidth = fm.width(messageText);
#endif
int messageTextTop = option.rect.top() + fm.height() + margin;
if(actionText.isEmpty()) messageTextTop = option.rect.top() + margin + offset/2;
messageTextBox.setTop(messageTextTop);
messageTextBox.setHeight(fm.height());
messageTextBox.setBottom(messageTextBox.top() + fm.height());
messageTextBox.setRight(messageTextBox.left() + messageTextWidth + margin);
if(messageText.isEmpty()){
messageTextBox.setHeight(0);
messageTextBox.setBottom(messageTextBox.top());
}
// time box rect
QRect timeBox = messageTextBox;
#if (HASQT5_11)
int timeTextWidth = fm.horizontalAdvance(timeText);
#else
int timeTextWidth = fm.width(timeText);
#endif
int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2;
if(messageText.isEmpty() || actionText.isEmpty())
timeTop = option.rect.top() + fm.height() + margin;
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
int rightMargin = margin;
int leftMargin = margin * offset;
int top = option.rect.top() + margin;
int buttonSize = option.rect.height()/2;
int right = option.rect.right() - rightMargin;
int left = right - buttonSize;
QStyleOptionButton secondaryButton;
secondaryButton.rect = option.rect;
secondaryButton.features |= QStyleOptionButton::Flat;
secondaryButton.state |= QStyle::State_None;
secondaryButton.rect.setLeft(left);
secondaryButton.rect.setRight(right);
secondaryButton.rect.setTop(top + margin);
secondaryButton.rect.setHeight(iconSize);
QStyleOptionButton primaryButton;
primaryButton.rect = option.rect;
primaryButton.features |= QStyleOptionButton::DefaultButton;
primaryButton.state |= QStyle::State_Raised;
primaryButton.rect.setTop(top);
primaryButton.rect.setHeight(buttonSize);
right = secondaryButton.rect.left() - rightMargin;
left = secondaryButton.rect.left() - leftMargin;
primaryButton.rect.setRight(right);
if(activityType == Activity::Type::NotificationType){
// Secondary will be 'Dismiss' or '...' multiple options button
secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose);
if(customList.size() > 1)
secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore);
secondaryButton.iconSize = QSize(iconSize, iconSize);
// Primary button will be 'More Information' or 'Accept'
primaryButton.text = tr("More information");
if(objectType == _remote_share) primaryButton.text = tr("Accept");
if(objectType == _call) primaryButton.text = tr("Join");
#if (HASQT5_11)
primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text));
#else
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
#endif
// save info to be able to filter mouse clicks
_buttonHeight = buttonSize;
_primaryButtonWidth = primaryButton.rect.size().width();
_secondaryButtonWidth = secondaryButton.rect.size().width();
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
} else if(activityType == Activity::SyncResultType){
// Secondary will be 'open file manager' with the folder icon
secondaryButton.icon = _iconFolder;
secondaryButton.iconSize = QSize(iconSize, iconSize);
// Primary button will be 'open browser'
primaryButton.text = tr("Open Browser");
#if (HASQT5_11)
primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text));
#else
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
#endif
// save info to be able to filter mouse clicks
_buttonHeight = buttonSize;
_primaryButtonWidth = primaryButton.rect.size().width();
_secondaryButtonWidth = secondaryButton.rect.size().width();
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
} else if(activityType == Activity::SyncFileItemType){
// Secondary will be 'open file manager' with the folder icon
secondaryButton.icon = _iconFolder;
secondaryButton.iconSize = QSize(iconSize, iconSize);
// No primary button on this case
// Whatever error we have at this case it is local, there is no point on opening the browser
_primaryButtonWidth = 0;
_secondaryButtonWidth = secondaryButton.rect.size().width();
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
} else {
_spaceBetweenButtons = leftMargin;
_primaryButtonWidth = 0;
_secondaryButtonWidth = 0;
}
// draw the icon
QPixmap pm = actionIcon.pixmap(iconSize, iconSize, QIcon::Normal);
painter->drawPixmap(QPoint(actionIconRect.left(), actionIconRect.top()), pm);
// change pen color if use is not online
QPalette p = option.palette;
if(!accountOnline)
p.setCurrentColorGroup(QPalette::Disabled);
// change pen color if the line is selected
if (isSelected)
painter->setPen(p.color(QPalette::HighlightedText));
else
painter->setPen(p.color(QPalette::Text));
// calculate space for text - use the max possible before using the elipses
int spaceLeftForText = option.rect.width() - (actionIconRect.width() + margin + rightMargin + leftMargin) -
(_primaryButtonWidth + _secondaryButtonWidth + _spaceBetweenButtons);
// draw the subject
const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, spaceLeftForText);
painter->drawText(actionTextBox, elidedAction);
// draw the buttons
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)
QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter);
// draw the message
// change pen color for the message
if(!messageText.isEmpty()){
const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText);
painter->drawText(messageTextBox, elidedMessage);
}
// change pen color for the time
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(timeText, Qt::ElideRight, spaceLeftForText);
painter->drawText(timeBox, elidedTime);
painter->restore();
}
bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index)
{
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
if(activityType != Activity::Type::ActivityType){
if (event->type() == QEvent::MouseButtonRelease){
QMouseEvent *mouseEvent = (QMouseEvent*)event;
if(mouseEvent){
int mouseEventX = mouseEvent->x();
int mouseEventY = mouseEvent->y();
int buttonsWidth = _primaryButtonWidth + _spaceBetweenButtons + _secondaryButtonWidth;
int x = option.rect.left() + option.rect.width() - buttonsWidth - _timeWidth;
int y = option.rect.top();
// clickable area for ...
if (mouseEventX > x && mouseEventX < x + buttonsWidth){
if(mouseEventY > y && mouseEventY < y + _buttonHeight){
// ...primary button ('more information' or 'accept' on notifications or 'open browser' on errors)
if (mouseEventX > x && mouseEventX < x + _primaryButtonWidth){
emit primaryButtonClickedOnItemView(index);
// ...secondary button ('dismiss' on notifications or 'open file manager' on errors)
} else {
x += _primaryButtonWidth + _spaceBetweenButtons;
if (mouseEventX > x && mouseEventX < x + _secondaryButtonWidth)
emit secondaryButtonClickedOnItemView(index);
}
}
}
}
}
}
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

@@ -0,0 +1,94 @@
/*
* Copyright (C) by Klaas Freitag <freitag@kde.org>
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#include <QStyledItemDelegate>
#include <QMouseEvent>
class QMouseEvent;
namespace OCC {
/**
* @brief The ActivityItemDelegate class
* @ingroup gui
*/
class ActivityItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
enum datarole { ActionIconRole = Qt::UserRole + 1,
UserIconRole,
AccountRole,
ObjectTypeRole,
ActionsLinksRole,
ActionTextRole,
ActionRole,
MessageRole,
PathRole,
LinkRole,
PointInTimeRole,
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,
const QModelIndex &index) override;
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;
static int _secondaryButtonWidth;
static int _spaceBetweenButtons;
static int _timeWidth;
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

@@ -15,6 +15,7 @@
#include <QtCore>
#include <QAbstractListModel>
#include <QWidget>
#include <QIcon>
#include <QJsonObject>
#include <QJsonDocument>
@@ -22,44 +23,34 @@
#include "accountstate.h"
#include "accountmanager.h"
#include "folderman.h"
#include "iconjob.h"
#include "accessmanager.h"
#include "activityitemdelegate.h"
#include "ActivityData.h"
#include "ActivityListModel.h"
#include "activitydata.h"
#include "activitylistmodel.h"
#include "theme.h"
#include "servernotificationhandler.h"
namespace OCC {
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
ActivityListModel::ActivityListModel(AccountState *accountState, QObject *parent)
: QAbstractListModel()
ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent)
: QAbstractListModel(parent)
, _accountState(accountState)
{
}
QHash<int, QByteArray> ActivityListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[DisplayPathRole] = "displaypath";
roles[PathRole] = "path";
roles[LinkRole] = "link";
roles[MessageRole] = "message";
roles[ActionRole] = "type";
roles[ActionIconRole] = "icon";
roles[ActionTextRole] = "subject";
roles[ActionTextColorRole] = "activityTextTitleColor";
roles[ObjectTypeRole] = "objectType";
roles[PointInTimeRole] = "dateTime";
return roles;
}
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
{
Activity a;
// filter the get action here
// send only the text of the get action
// if there is more than one send the icon? the ...
if (!index.isValid())
return QVariant();
@@ -70,44 +61,25 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
QStringList list;
switch (role) {
case DisplayPathRole:
if (!a._file.isEmpty()) {
case ActivityItemDelegate::PathRole:
if(!a._file.isEmpty()){
auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
if (folder) {
relPath.prepend(folder->remotePath());
}
if(folder) relPath.prepend(folder->remotePath());
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (list.count() > 0) {
if (relPath.startsWith('/') || relPath.startsWith('\\')) {
return relPath.remove(0, 1);
} else {
return relPath;
}
}
}
return QString();
case PathRole:
if (!a._file.isEmpty()) {
auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
if (folder)
relPath.prepend(folder->remotePath());
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (list.count() > 0) {
QString path = "file:///" + QString(list.at(0));
return QUrl(path);
return QVariant(list.at(0));
}
// File does not exist anymore? Let's try to open its path
if (QFileInfo(relPath).exists()) {
if(QFileInfo(relPath).exists()) {
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
if (list.count() > 0) {
return QVariant(list.at(0));
}
}
}
return QString();
case ActionsLinksRole: {
return QVariant();
case ActivityItemDelegate::ActionsLinksRole:{
QList<QVariant> customList;
foreach (ActivityLink customItem, a._links) {
QVariant customVariant;
@@ -116,81 +88,59 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
}
return customList;
}
case ActionIconRole: {
if (a._type == Activity::NotificationType) {
return "qrc:///client/theme/black/bell.svg";
} else if (a._type == Activity::SyncResultType) {
return "qrc:///client/theme/black/state-error.svg";
} else if (a._type == Activity::SyncFileItemType) {
if (a._status == SyncFileItem::NormalError
|| a._status == SyncFileItem::FatalError
|| a._status == SyncFileItem::DetailError
|| a._status == SyncFileItem::BlacklistedError) {
return "qrc:///client/theme/black/state-error.svg";
} else if (a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration
|| a._status == SyncFileItem::FileLocked) {
return "qrc:///client/theme/black/state-warning.svg";
} else if (a._status == SyncFileItem::FileIgnored) {
return "qrc:///client/theme/black/state-info.svg";
case ActivityItemDelegate::ActionIconRole:{
ActionIcon actionIcon;
if(a._type == Activity::NotificationType){
QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id);
if(!cachedIcon.isNull()) {
actionIcon.iconType = ActivityIconType::iconUseCached;
actionIcon.cachedIcon = cachedIcon;
} else {
// File sync successful
if (a._fileAction == "file_created") {
return "qrc:///client/resources/add-color.svg";
} else if (a._fileAction == "file_deleted") {
return "qrc:///client/resources/delete-color.svg";
} else {
return "qrc:///client/resources/change.svg";
}
actionIcon.iconType = ActivityIconType::iconBell;
}
} else if(a._type == Activity::SyncResultType){
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) {
actionIcon.iconType = ActivityIconType::iconStateError;
} else if(a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration
|| a._status == SyncFileItem::FileLocked){
actionIcon.iconType = ActivityIconType::iconStateWarning;
} else if(a._status == SyncFileItem::FileIgnored){
actionIcon.iconType = ActivityIconType::iconStateInfo;
} else {
actionIcon.iconType = ActivityIconType::iconStateSync;
}
} else {
// We have an activity
if (!a._iconData.isEmpty()) {
QString svgData = "data:image/svg+xml;utf8," + a._iconData;
return svgData;
}
return "qrc:///client/theme/black/activity.svg";
actionIcon.iconType = ActivityIconType::iconActivity;
}
QVariant icn;
icn.setValue(actionIcon);
return icn;
}
case ObjectTypeRole:
case ActivityItemDelegate::ObjectTypeRole:
return a._objectType;
case ActionRole: {
switch (a._type) {
case Activity::ActivityType:
return "Activity";
case Activity::NotificationType:
return "Notification";
case Activity::SyncFileItemType:
return "File";
case Activity::SyncResultType:
return "Sync";
default:
return QVariant();
}
case ActivityItemDelegate::ActionRole:{
QVariant type;
type.setValue(a._type);
return type;
}
case ActionTextRole:
case ActivityItemDelegate::ActionTextRole:
return a._subject;
case ActionTextColorRole:
return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry
case MessageRole:
if (a._message.isEmpty()) {
return QString("No description available.");
}
case ActivityItemDelegate::MessageRole:
return a._message;
case LinkRole: {
if (a._link.isEmpty()) {
return "";
} else {
return a._link;
}
}
case AccountRole:
case ActivityItemDelegate::LinkRole:
return a._link;
case ActivityItemDelegate::AccountRole:
return a._accName;
case PointInTimeRole:
//return a._id == -1 ? "" : QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate));
return a._id == -1 ? "" : Utility::timeAgoInWords(a._dateTime.toLocalTime());
case AccountConnectedRole:
case ActivityItemDelegate::PointInTimeRole:
return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime()));
case ActivityItemDelegate::AccountConnectedRole:
return (ast && ast->isConnected());
default:
return QVariant();
@@ -221,13 +171,13 @@ void ActivityListModel::startFetchJob()
if (!_accountState->isConnected()) {
return;
}
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this);
QObject::connect(job, &JsonApiJob::jsonReceived,
this, &ActivityListModel::slotActivitiesReceived);
QUrlQuery params;
params.addQueryItem(QLatin1String("since"), QString::number(_currentItem));
params.addQueryItem(QLatin1String("limit"), QString::number(50));
params.addQueryItem(QLatin1String("start"), QString::number(_currentItem));
params.addQueryItem(QLatin1String("count"), QString::number(100));
job->addQueryParams(params);
_currentlyFetching = true;
@@ -250,43 +200,21 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
}
_currentlyFetching = false;
QDateTime oldestDate = QDateTime::currentDateTime();
oldestDate = oldestDate.addDays(_maxActivitiesDays * -1);
_currentItem += activities.size();
foreach (auto activ, activities) {
auto json = activ.toObject();
Activity a;
a._type = Activity::ActivityType;
a._objectType = json.value("object_type").toString();
a._accName = ast->account()->displayName();
a._id = json.value("activity_id").toInt();
a._fileAction = json.value("type").toString();
a._id = json.value("id").toInt();
a._subject = json.value("subject").toString();
a._message = json.value("message").toString();
a._file = json.value("object_name").toString();
a._file = json.value("file").toString();
a._link = QUrl(json.value("link").toString());
a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
a._icon = json.value("icon").toString();
if (!a._icon.isEmpty()) {
IconJob *iconJob = new IconJob(QUrl(a._icon));
iconJob->setProperty("activityId", a._id);
connect(iconJob, &IconJob::jobFinished, this, &ActivityListModel::slotIconDownloaded);
}
a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate);
list.append(a);
_currentItem = list.last()._id;
_totalActivitiesFetched++;
if(_totalActivitiesFetched == _maxActivities ||
a._dateTime < oldestDate) {
_showMoreActivitiesAvailableEntry = true;
_doneFetching = true;
break;
}
}
_activityLists.append(list);
@@ -296,95 +224,76 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
combineActivityLists();
}
void ActivityListModel::slotIconDownloaded(QByteArray iconData)
{
for (size_t i = 0; i < _activityLists.count(); i++) {
if (_activityLists[i]._id == sender()->property("activityId").toLongLong()) {
_activityLists[i]._iconData = iconData;
}
}
}
void ActivityListModel::addErrorToActivityList(Activity activity)
{
void ActivityListModel::addErrorToActivityList(Activity activity) {
qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
_notificationErrorsLists.prepend(activity);
combineActivityLists();
}
void ActivityListModel::addIgnoredFileToList(Activity newActivity)
{
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) {
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) {
foreach(Activity activity, _listOfIgnoredFiles){
if(activity._file == newActivity._file){
duplicate = true;
break;
}
}
if (!duplicate) {
if(!duplicate){
_notificationIgnoredFiles._message.append(", " + newActivity._file);
}
}
void ActivityListModel::addNotificationToActivityList(Activity activity)
{
void ActivityListModel::addNotificationToActivityList(Activity activity) {
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
_notificationLists.prepend(activity);
combineActivityLists();
}
void ActivityListModel::clearNotifications()
{
void ActivityListModel::clearNotifications() {
qCInfo(lcActivity) << "Clear the notifications";
_notificationLists.clear();
combineActivityLists();
}
void ActivityListModel::removeActivityFromActivityList(int row)
{
void ActivityListModel::removeActivityFromActivityList(int row) {
Activity activity = _finalList.at(row);
removeActivityFromActivityList(activity);
combineActivityLists();
}
void ActivityListModel::addSyncFileItemToActivityList(Activity activity)
{
void ActivityListModel::addSyncFileItemToActivityList(Activity activity) {
qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject;
_syncFileItemLists.prepend(activity);
combineActivityLists();
}
void ActivityListModel::removeActivityFromActivityList(Activity activity)
{
void ActivityListModel::removeActivityFromActivityList(Activity activity) {
qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";
int index = -1;
if (activity._type == Activity::ActivityType) {
if(activity._type == Activity::ActivityType){
index = _activityLists.indexOf(activity);
if (index != -1)
_activityLists.removeAt(index);
} else if (activity._type == Activity::NotificationType) {
if(index != -1) _activityLists.removeAt(index);
} else if(activity._type == Activity::NotificationType){
index = _notificationLists.indexOf(activity);
if (index != -1)
_notificationLists.removeAt(index);
if(index != -1) _notificationLists.removeAt(index);
} else {
index = _notificationErrorsLists.indexOf(activity);
if (index != -1)
_notificationErrorsLists.removeAt(index);
if(index != -1) _notificationErrorsLists.removeAt(index);
}
if (index != -1) {
if(index != -1){
qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
combineActivityLists();
@@ -395,57 +304,38 @@ void ActivityListModel::combineActivityLists()
{
ActivityList resultList;
if (_notificationErrorsLists.count() > 0) {
if(_notificationErrorsLists.count() > 0) {
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
resultList.append(_notificationErrorsLists);
}
if (_listOfIgnoredFiles.size() > 0)
if(_listOfIgnoredFiles.size() > 0)
resultList.append(_notificationIgnoredFiles);
if (_notificationLists.count() > 0) {
if(_notificationLists.count() > 0) {
std::sort(_notificationLists.begin(), _notificationLists.end());
resultList.append(_notificationLists);
}
if (_syncFileItemLists.count() > 0) {
if(_syncFileItemLists.count() > 0) {
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
resultList.append(_syncFileItemLists);
}
if (_activityLists.count() > 0) {
if(_activityLists.count() > 0) {
std::sort(_activityLists.begin(), _activityLists.end());
resultList.append(_activityLists);
if(_showMoreActivitiesAvailableEntry) {
Activity a;
a._type = Activity::ActivityType;
a._accName = _accountState->account()->displayName();
a._id = -1;
a._subject = tr("For more activities please open the Activity app.");
a._dateTime = QDateTime::currentDateTime();
AccountApp *app = _accountState->findApp(QLatin1String("activity"));
if(app) {
a._link = app->url();
}
resultList.append(a);
}
}
beginResetModel();
_finalList.clear();
endResetModel();
if (resultList.count() > 0) {
beginInsertRows(QModelIndex(), 0, resultList.count() - 1);
_finalList = resultList;
endInsertRows();
}
beginInsertRows(QModelIndex(), 0, resultList.count());
_finalList = resultList;
endInsertRows();
}
bool ActivityListModel::canFetchActivities() const
{
bool ActivityListModel::canFetchActivities() const {
return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities();
}
@@ -464,8 +354,6 @@ void ActivityListModel::slotRefreshActivity()
_activityLists.clear();
_doneFetching = false;
_currentItem = 0;
_totalActivitiesFetched = 0;
_showMoreActivitiesAvailableEntry = false;
if (canFetchActivities()) {
startFetchJob();
@@ -482,7 +370,5 @@ void ActivityListModel::slotRemoveAccount()
_currentlyFetching = false;
_doneFetching = false;
_currentItem = 0;
_totalActivitiesFetched = 0;
_showMoreActivitiesAvailableEntry = false;
}
}

View File

@@ -17,7 +17,7 @@
#include <QtCore>
#include "ActivityData.h"
#include "activitydata.h"
class QJsonDocument;
@@ -38,24 +38,21 @@ class ActivityListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum DataRole {
ActionIconRole = Qt::UserRole + 1,
UserIconRole,
AccountRole,
ObjectTypeRole,
ActionsLinksRole,
ActionTextRole,
ActionTextColorRole,
ActionRole,
MessageRole,
DisplayPathRole,
PathRole,
LinkRole,
PointInTimeRole,
AccountConnectedRole,
SyncFileStatusRole};
enum ActivityIconType {
iconUseCached = 0,
iconActivity,
iconBell,
iconStateError,
iconStateWarning,
iconStateInfo,
iconStateSync
};
struct ActionIcon {
ActivityIconType iconType;
QIcon cachedIcon;
};
explicit ActivityListModel(AccountState *accountState, QObject* parent = 0);
explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr);
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -79,14 +76,10 @@ public slots:
private slots:
void slotActivitiesReceived(const QJsonDocument &json, int statusCode);
void slotIconDownloaded(QByteArray iconData);
signals:
void activityJobStatusCode(int statusCode);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
void startFetchJob();
void combineActivityLists();
@@ -103,12 +96,9 @@ private:
bool _currentlyFetching = false;
bool _doneFetching = false;
int _currentItem = 0;
int _totalActivitiesFetched = 0;
int _maxActivities = 100;
int _maxActivitiesDays = 30;
bool _showMoreActivitiesAvailableEntry = false;
};
}
Q_DECLARE_METATYPE(OCC::ActivityListModel::ActionIcon)
#endif // ACTIVITYLISTMODEL_H

653
src/gui/activitywidget.cpp Normal file
View File

@@ -0,0 +1,653 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.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 <QtGui>
#include <QtWidgets>
#include "activitylistmodel.h"
#include "activitywidget.h"
#include "syncresult.h"
#include "logger.h"
#include "theme.h"
#include "folderman.h"
#include "syncfileitem.h"
#include "folder.h"
#include "openfilemanager.h"
#include "owncloudpropagator.h"
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
#include "activityitemdelegate.h"
#include "QProgressIndicator.h"
#include "notificationconfirmjob.h"
#include "servernotificationhandler.h"
#include "theme.h"
#include "ocsjob.h"
#include "configfile.h"
#include "guiutility.h"
#include "socketapi.h"
#include "ui_activitywidget.h"
#include "syncengine.h"
#include <climits>
// time span in milliseconds which has to be between two
// refreshes of the notifications
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
namespace OCC {
ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent)
: QWidget(parent)
, _ui(new Ui::ActivityWidget)
, _notificationRequestsRunning(0)
, _accountState(accountState)
, _accept(tr("Accept"))
, _remote_share("remote_share")
{
_ui->setupUi(this);
// Adjust copyToClipboard() when making changes here!
#if defined(Q_OS_MAC)
_ui->_activityList->setMinimumWidth(400);
#endif
_model = new ActivityListModel(accountState, this);
ActivityItemDelegate *delegate = new ActivityItemDelegate;
delegate->setParent(this);
_ui->_activityList->setItemDelegate(delegate);
_ui->_activityList->setAlternatingRowColors(true);
_ui->_activityList->setModel(_model);
showLabels();
connect(_model, &ActivityListModel::activityJobStatusCode,
this, &ActivityWidget::slotAccountActivityStatus);
connect(_model, &QAbstractItemModel::rowsInserted, this, &ActivityWidget::rowsInserted);
connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView);
connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView);
connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
this, &ActivityWidget::slotProgressInfo);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
this, &ActivityWidget::slotItemCompleted);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
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()
{
delete _ui;
}
void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
{
if (progress.status() == ProgressInfo::Reconcile) {
// Wipe all non-persistent entries - as well as the persistent ones
// in cases where a local discovery was done.
auto f = FolderMan::instance()->folder(folder);
if (!f)
return;
const auto &engine = f->syncEngine();
const auto style = engine.lastLocalDiscoveryStyle();
foreach (Activity activity, _model->errorsList()) {
if (activity._folder != folder){
continue;
}
if (style == LocalDiscoveryStyle::FilesystemOnly){
_model->removeActivityFromActivityList(activity);
continue;
}
if(activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity);
continue;
}
if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity);
continue;
}
if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) {
_model->removeActivityFromActivityList(activity);
continue;
}
if(!QFileInfo(f->path() + activity._file).exists()){
_model->removeActivityFromActivityList(activity);
continue;
}
auto path = QFileInfo(activity._file).dir().path().toUtf8();
if (path == ".")
path.clear();
if(engine.shouldDiscoverLocally(path))
_model->removeActivityFromActivityList(activity);
}
}
if (progress.status() == ProgressInfo::Done) {
// We keep track very well of pending conflicts.
// Inform other components about them.
QStringList conflicts;
foreach (Activity activity, _model->errorsList()) {
if (activity._folder == folder
&& activity._status == SyncFileItem::Conflict) {
conflicts.append(activity._file);
}
}
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
}
}
void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){
auto folderInstance = FolderMan::instance()->folder(folder);
if (!folderInstance)
return;
// check if we are adding it to the right account and if it is useful information (protocol errors)
if(folderInstance->accountState() == _accountState){
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString;
Activity activity;
activity._type = Activity::SyncFileItemType; //client activity
activity._status = item->_status;
activity._dateTime = QDateTime::currentDateTime();
activity._message = item->_originalFile;
activity._link = folderInstance->accountState()->account()->url();
activity._accName = folderInstance->accountState()->account()->displayName();
activity._file = item->_file;
activity._folder = folder;
if(item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success){
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
activity._message.prepend(" ");
activity._message.prepend(tr("Synced"));
_model->addSyncFileItemToActivityList(activity);
} else {
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
activity._subject = item->_errorString;
if(item->_status == SyncFileItem::Status::FileIgnored) {
_model->addIgnoredFileToList(activity);
} else {
// add 'protocol error' to activity list
_model->addErrorToActivityList(activity);
}
}
}
}
void ActivityWidget::addError(const QString &folderAlias, const QString &message,
ErrorCategory category)
{
auto folderInstance = FolderMan::instance()->folder(folderAlias);
if (!folderInstance)
return;
if(folderInstance->accountState() == _accountState){
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message;
Activity activity;
activity._type = Activity::SyncResultType;
activity._status = SyncResult::Error;
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
activity._subject = message;
activity._message = folderInstance->shortGuiLocalPath();
activity._link = folderInstance->shortGuiLocalPath();
activity._accName = folderInstance->accountState()->account()->displayName();
activity._folder = folderAlias;
if (category == ErrorCategory::InsufficientRemoteStorage) {
ActivityLink link;
link._label = tr("Retry all uploads");
link._link = folderInstance->path();
link._verb = "";
link._isPrimary = true;
activity._links.append(link);
}
// add 'other errors' to activity list
_model->addErrorToActivityList(activity);
}
}
void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){
QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole));
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
if(!link.isEmpty()){
qCWarning(lcActivity) << "Opening" << link.toString() << "in browser for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
Utility::openBrowser(link, this);
} else if(objectType == _remote_share){
QVariant customItem = index.data(ActivityItemDelegate::ActionsLinksRole).toList().first();
ActivityLink actionLink = qvariant_cast<ActivityLink>(customItem);
if(actionLink._label == _accept){
qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
} else {
qCWarning(lcActivity) << "Failed: " << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
}
}
}
void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){
QList<QVariant> customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList();
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
QList<ActivityLink> actionLinks;
foreach(QVariant customItem, customList){
actionLinks << qvariant_cast<ActivityLink>(customItem);
}
if(objectType == _remote_share && actionLinks.first()._label == _accept)
actionLinks.removeFirst();
if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
if(actionLinks.size() == 1){
if(actionLinks.at(0)._verb == "DELETE"){
qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row());
}
} else if(actionLinks.size() > 1){
QMenu menu;
qCWarning(lcActivity) << "Displaying menu for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
foreach (ActivityLink actionLink, actionLinks) {
QAction *menuAction = new QAction(actionLink._label, &menu);
connect(menuAction, &QAction::triggered, this, [this, index, accountName, actionLink] {
this->slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
});
menu.addAction(menuAction);
}
menu.exec(QCursor::pos());
}
}
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole));
if(activityType == Activity::Type::SyncFileItemType || activityType == Activity::Type::SyncResultType)
slotOpenFile(index);
}
void ActivityWidget::slotNotificationRequestFinished(int statusCode)
{
int row = sender()->property("activityRow").toInt();
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible.";
} else {
// to do use the model to rebuild the list or remove the item
qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list.";
_model->removeActivityFromActivityList(row);
}
}
void ActivityWidget::slotRefreshActivities()
{
_model->slotRefreshActivity();
}
void ActivityWidget::slotRefreshNotifications()
{
// start a server notification handler if no notification requests
// are running
if (_notificationRequestsRunning == 0) {
ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState);
connect(snh, &ServerNotificationHandler::newNotificationList,
this, &ActivityWidget::slotBuildNotificationDisplay);
snh->slotFetchNotifications();
} else {
qCWarning(lcActivity) << "Notification request counter not zero.";
}
}
void ActivityWidget::slotRemoveAccount()
{
_model->slotRemoveAccount();
}
void ActivityWidget::showLabels()
{
_ui->_bottomLabel->hide(); // hide whatever was there before
QString t("");
QSetIterator<QString> i(_accountsWithoutActivities);
while (i.hasNext()) {
t.append(tr("<br/>Account %1 does not have activities enabled.").arg(i.next()));
}
if(!t.isEmpty()){
_ui->_bottomLabel->setTextFormat(Qt::RichText);
_ui->_bottomLabel->setText(t);
_ui->_bottomLabel->show();
}
}
void ActivityWidget::slotAccountActivityStatus(int statusCode)
{
if (!(_accountState && _accountState->account())) {
return;
}
if (statusCode == 999) {
_accountsWithoutActivities.insert(_accountState->account()->displayName());
} else {
_accountsWithoutActivities.remove(_accountState->account()->displayName());
}
checkActivityWidgetVisibility();
showLabels();
}
// FIXME: Reused from protocol widget. Move over to utilities.
QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const
{
const QLocale loc = QLocale::system();
QString dtFormat = loc.dateTimeFormat(format);
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
dtFormat.replace(re, "\\1:mm:ss");
return loc.toString(dt, dtFormat);
}
void ActivityWidget::storeActivityList(QTextStream &ts)
{
ActivityList activities = _model->activityList();
foreach (Activity activity, activities) {
ts << right
// account name
<< qSetFieldWidth(activity._accName.length())
<< activity._accName
// separator
<< qSetFieldWidth(2) << " - "
// date and time
<< qSetFieldWidth(activity._dateTime.toString().length())
<< activity._dateTime.toString()
// separator
<< qSetFieldWidth(2) << " - "
// fileq
<< qSetFieldWidth(activity._file.length())
<< activity._file
// separator
<< qSetFieldWidth(2) << " - "
// subject
<< qSetFieldWidth(activity._subject.length())
<< activity._subject
// separator
<< qSetFieldWidth(2) << " - "
// message
<< qSetFieldWidth(activity._message.length())
<< activity._message
<< endl;
}
}
void ActivityWidget::checkActivityWidgetVisibility()
{
int accountCount = AccountManager::instance()->accounts().count();
bool hasAccountsWithActivity =
_accountsWithoutActivities.count() != accountCount;
_ui->_activityList->setVisible(hasAccountsWithActivity);
emit hideActivityTab(!hasAccountsWithActivity);
}
void ActivityWidget::slotOpenFile(QModelIndex indx)
{
qCDebug(lcActivity) << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
if (indx.isValid()) {
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
if(!fullPath.isEmpty()){
if (QFile::exists(fullPath)) {
showInFileManager(fullPath);
}
}
}
}
// GUI: Display the notifications.
// All notifications in list are coming from the same account
// but in the _widgetForNotifId hash widgets for all accounts are
// collected.
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
{
// Whether a new notification was added to the list
bool newNotificationShown = false;
_model->clearNotifications();
foreach (auto activity, list) {
if (_blacklistedNotifications.contains(activity)) {
qCInfo(lcActivity) << "Activity in blacklist, skip";
continue;
}
// handle gui logs. In order to NOT annoy the user with every fetching of the
// notifications the notification id is stored in a Set. Only if an id
// is not in the set, it qualifies for guiLog.
// Important: The _guiLoggedNotifications set must be wiped regularly which
// will repeat the gui log.
// after one hour, clear the gui log notification store
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
_guiLoggedNotifications.clear();
}
if (!_guiLoggedNotifications.contains(activity._id)) {
newNotificationShown = true;
_guiLoggedNotifications.insert(activity._id);
// Assemble a tray notification for the NEW notification
ConfigFile cfg;
if(cfg.optionalServerNotifications()){
if(AccountManager::instance()->accounts().count() == 1){
emit guiLog(activity._subject, "");
} else {
emit guiLog(activity._subject, activity._accName);
}
}
}
_model->addNotificationToActivityList(activity);
}
// restart the gui log timer now that we show a new notification
if(newNotificationShown) {
_guiLogTimer.start();
}
}
void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
{
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
const QStringList validVerbs = QStringList() << "GET"
<< "PUT"
<< "POST"
<< "DELETE";
if (validVerbs.contains(verb)) {
AccountStatePtr acc = AccountManager::instance()->account(accountName);
if (acc) {
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
QUrl l(link);
job->setLinkAndVerb(l, verb);
job->setProperty("activityRow", QVariant::fromValue(row));
connect(job, &AbstractNetworkJob::networkError,
this, &ActivityWidget::slotNotifyNetworkError);
connect(job, &NotificationConfirmJob::jobFinished,
this, &ActivityWidget::slotNotifyServerFinished);
job->start();
// count the number of running notification requests. If this member var
// is larger than zero, no new fetching of notifications is started
_notificationRequestsRunning++;
}
} else {
qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb;
}
}
void ActivityWidget::endNotificationRequest(int replyCode)
{
_notificationRequestsRunning--;
slotNotificationRequestFinished(replyCode);
}
void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply)
{
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
if (!job) {
return;
}
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
endNotificationRequest(resultCode);
qCWarning(lcActivity) << "Server notify job failed with code " << resultCode;
}
void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode)
{
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
if (!job) {
return;
}
endNotificationRequest(replyCode);
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)
: QWidget(parent)
, _accountState(accountState)
{
_vbox = new QVBoxLayout(this);
setLayout(_vbox);
_activityWidget = new ActivityWidget(_accountState, this);
_vbox->insertWidget(1, _activityWidget);
connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog);
connect(&_notificationCheckTimer, &QTimer::timeout,
this, &ActivitySettings::slotRegularNotificationCheck);
// Add a progress indicator to spin if the acitivity list is updated.
_progressIndicator = new QProgressIndicator(this);
// 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(){
_vbox->removeWidget(_progressIndicator);
}
void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval)
{
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
_notificationCheckTimer.start(interval.count());
}
void ActivitySettings::slotRemoveAccount()
{
_activityWidget->slotRemoveAccount();
}
void ActivitySettings::slotRefresh()
{
// QElapsedTimer isn't actually constructed as invalid.
if (!_timeSinceLastCheck.contains(_accountState)) {
_timeSinceLastCheck[_accountState].invalidate();
}
QElapsedTimer &timer = _timeSinceLastCheck[_accountState];
// Fetch Activities only if visible and if last check is longer than 15 secs ago
if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) {
qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000;
return;
}
if (_accountState && _accountState->isConnected()) {
if (isVisible() || !timer.isValid()) {
_vbox->insertWidget(0, _progressIndicator);
_vbox->setAlignment(_progressIndicator, Qt::AlignHCenter);
_progressIndicator->startAnimation();
_activityWidget->slotRefreshActivities();
}
_activityWidget->slotRefreshNotifications();
timer.start();
}
}
void ActivitySettings::slotRegularNotificationCheck()
{
slotRefresh();
}
bool ActivitySettings::event(QEvent *e)
{
if (e->type() == QEvent::Show) {
slotRefresh();
}
return QWidget::event(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();
}
}

165
src/gui/activitywidget.h Normal file
View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef ACTIVITYWIDGET_H
#define ACTIVITYWIDGET_H
#include <QDialog>
#include <QDateTime>
#include <QLocale>
#include <QAbstractListModel>
#include <chrono>
#include "progressdispatcher.h"
#include "owncloudgui.h"
#include "account.h"
#include "activitydata.h"
#include "accountmanager.h"
#include "ui_activitywidget.h"
class QPushButton;
class QProgressIndicator;
namespace OCC {
class Account;
class AccountStatusPtr;
class JsonApiJob;
class ActivityListModel;
namespace Ui {
class ActivityWidget;
}
class Application;
/**
* @brief The ActivityWidget class
* @ingroup gui
*
* The list widget to display the activities, contained in the
* subsequent ActivitySettings widget.
*/
class ActivityWidget : public QWidget
{
Q_OBJECT
public:
explicit ActivityWidget(AccountState *accountState, QWidget *parent = nullptr);
~ActivityWidget();
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
void storeActivityList(QTextStream &ts);
/**
* Adjusts the activity tab's and some widgets' visibility
*
* Based on whether activities are enabled and whether notifications are
* available.
*/
void checkActivityWidgetVisibility();
public slots:
void slotOpenFile(QModelIndex indx);
void slotRefreshActivities();
void slotRefreshNotifications();
void slotRemoveAccount();
void slotAccountActivityStatus(int statusCode);
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);
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
void slotNotifyNetworkError(QNetworkReply *);
void slotNotifyServerFinished(const QString &reply, int replyCode);
void endNotificationRequest(int replyCode);
void slotNotificationRequestFinished(int statusCode);
void slotPrimaryButtonClickedOnListView(const QModelIndex &index);
void slotSecondaryButtonClickedOnListView(const QModelIndex &index);
private:
void customizeStyle();
void showLabels();
QString timeString(QDateTime dt, QLocale::FormatType format) const;
Ui::ActivityWidget *_ui;
QSet<QString> _accountsWithoutActivities;
QElapsedTimer _guiLogTimer;
QSet<int> _guiLoggedNotifications;
ActivityList _blacklistedNotifications;
QTimer _removeTimer;
// number of currently running notification requests. If non zero,
// no query for notifications is started.
int _notificationRequestsRunning;
ActivityListModel *_model;
AccountState *_accountState;
const QString _accept;
const QString _remote_share;
};
/**
* @brief The ActivitySettings class
* @ingroup gui
*
* Implements a tab for the settings dialog, displaying the three activity
* lists.
*/
class ActivitySettings : public QWidget
{
Q_OBJECT
public:
explicit ActivitySettings(AccountState *accountState, QWidget *parent = nullptr);
~ActivitySettings();
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
public slots:
void slotRefresh();
void slotRemoveAccount();
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
void slotStyleChanged();
private slots:
void slotRegularNotificationCheck();
void slotDisplayActivities();
signals:
void guiLog(const QString &, const QString &);
void styleChanged();
private:
bool event(QEvent *e) override;
ActivityWidget *_activityWidget;
QProgressIndicator *_progressIndicator;
QVBoxLayout *_vbox;
QTimer _notificationCheckTimer;
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
AccountState *_accountState;
};
}
#endif // ActivityWIDGET_H

98
src/gui/activitywidget.ui Normal file
View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OCC::ActivityWidget</class>
<widget class="QWidget" name="OCC::ActivityWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>652</width>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListView" name="_activityList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="defaultDropAction">
<enum>Qt::IgnoreAction</enum>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="viewMode">
<enum>QListView::ListMode</enum>
</property>
<property name="modelColumn">
<number>0</number>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="_bottomLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">TextLabel</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>_activityList</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -52,7 +52,6 @@
#include <QMenu>
#include <QMessageBox>
#include <QDesktopServices>
#include <QGuiApplication>
class QSocket;
@@ -109,6 +108,7 @@ Application::Application(int &argc, char **argv)
, _userTriggeredConnect(false)
, _debugMode(false)
, _backgroundMode(false)
, _isQuitting(false)
{
_startedAt.start();
@@ -123,15 +123,6 @@ Application::Application(int &argc, char **argv)
// TODO: Can't set this without breaking current config paths
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
// setDesktopFilename to provide wayland compatibility (in general: conformance with naming standards)
// but only on Qt >= 5.7, where setDesktopFilename was introduced
#if (QT_VERSION >= 0x050700)
QString desktopFileName = QString(QLatin1String(LINUX_APPLICATION_ID)
+ QLatin1String(".desktop"));
setDesktopFileName(desktopFileName);
#endif
setApplicationName(_theme->appName());
setWindowIcon(_theme->applicationIcon());
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
@@ -266,8 +257,6 @@ Application::Application(int &argc, char **argv)
// Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example)
connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings);
_gui->createTray();
}
Application::~Application()
@@ -300,7 +289,7 @@ void Application::slotAccountStateRemoved(AccountState *accountState)
}
// if there is no more account, show the wizard.
if (AccountManager::instance()->accounts().isEmpty()) {
if (!_isQuitting && AccountManager::instance()->accounts().isEmpty()) {
// allow to add a new account if there is non any more. Always think
// about single account theming!
OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int)));
@@ -323,6 +312,8 @@ void Application::slotAccountStateAdded(AccountState *accountState)
void Application::slotCleanup()
{
_isQuitting = true;
AccountManager::instance()->save();
FolderMan::instance()->unloadAndDeleteAllFolders();
@@ -392,7 +383,7 @@ void Application::slotownCloudWizardDone(int res)
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
}
Systray::instance()->showWindow();
_gui->slotShowSettings();
}
}

View File

@@ -116,6 +116,7 @@ private:
bool _userTriggeredConnect;
bool _debugMode;
bool _backgroundMode;
bool _isQuitting;
ClientProxy _proxy;

View File

@@ -249,11 +249,6 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
return;
}
// Check for the directEditing capability
QUrl directEditingURL = QUrl(caps["files"].toObject()["directEditing"].toObject()["url"].toString());
QString directEditingETag = caps["files"].toObject()["directEditing"].toObject()["etag"].toString();
_account->fetchDirectEditors(directEditingURL, directEditingETag);
fetchUser();
}

View File

@@ -914,8 +914,6 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item)
_folderWatcher->removePath(path() + item->_file);
_folderWatcher->addPath(path() + item->destination());
break;
default:
break;
}
}
@@ -1206,7 +1204,7 @@ QString FolderDefinition::absoluteJournalPath() const
QString FolderDefinition::defaultJournalPath(AccountPtr account)
{
return SyncJournalDb::makeDbName(account->url(), targetPath, account->credentials()->user());
return SyncJournalDb::makeDbName(localPath, account->url(), targetPath, account->credentials()->user());
}
} // namespace OCC

View File

@@ -57,7 +57,7 @@ public:
QString alias;
/// path on local machine
QString localPath;
/// path to the journal, usually in QStandardPaths::AppDataLocation
/// path to the journal, usually relative to localPath
QString journalPath;
/// path on remote
QString targetPath;

View File

@@ -210,27 +210,6 @@ void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account,
folderDefinition.journalPath = defaultJournalPath;
}
// Migration #2: journalPath now in DataAppDir, not root of local tree (cross-platform persistent user roaming files)
if (folderDefinition.journalPath.at(0) == QChar('.')) {
QFile oldJournal(folderDefinition.localPath + "/" + folderDefinition.journalPath);
QFile oldJournalShm(folderDefinition.localPath + "/" + folderDefinition.journalPath.append("-shm"));
QFile oldJournalWal(folderDefinition.localPath + "/" + folderDefinition.journalPath.append("-wal"));
folderDefinition.journalPath = defaultJournalPath;
socketApi()->slotUnregisterPath(folderAlias);
auto settings = account->settings();
Folder *f = addFolderInternal(folderDefinition, account.data());
f->saveToSettings();
oldJournal.remove();
oldJournalShm.remove();
oldJournalWal.remove();
return;
}
// Migration: ._ files sometimes don't work
// So if the configured journalPath is the default one ("._sync_*.db")
// but the current default doesn't have the underscore, switch to the

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<width>785</width>
<height>523</height>
</rect>
</property>

View File

@@ -23,9 +23,6 @@ IconJob::IconJob(const QUrl &url, QObject *parent) :
this, &IconJob::finished);
QNetworkRequest request(url);
#if (QT_VERSION >= 0x050600)
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
#endif
_accessManager.get(request);
}

View File

@@ -24,7 +24,7 @@ LegalNotice::LegalNotice(QDialog *parent)
{
_ui->setupUi(this);
QString notice = tr("<p>Copyright 2017-2020 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

@@ -32,11 +32,6 @@ NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
_updateCloudStorageRegistryTimer.setSingleShot(true);
connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
// Ensure that the folder integration stays persistent in Explorer,
// the uninstaller removes the folder upon updating the client.
_showInExplorerNavigationPane = !_showInExplorerNavigationPane;
setShowInExplorerNavigationPane(!_showInExplorerNavigationPane);
}
void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show)
@@ -144,9 +139,7 @@ void NavigationPaneHelper::updateCloudStorageRegistry()
#else
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
// Don't crash, by any means!
// Q_ASSERT(false);
Q_ASSERT(false);
#endif
}
}

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<width>563</width>
<height>444</height>
</rect>
</property>

View File

@@ -73,17 +73,6 @@ void OcsShareJob::setPassword(const QString &shareId, const QString &password)
start();
}
void OcsShareJob::setNote(const QString &shareId, const QString &note)
{
appendPath(shareId);
setVerb("PUT");
addParam(QString::fromLatin1("note"), note);
_value = note;
start();
}
void OcsShareJob::setPublicUpload(const QString &shareId, bool publicUpload)
{
appendPath(shareId);

View File

@@ -61,14 +61,6 @@ public:
*/
void setExpireDate(const QString &shareId, const QDate &date);
/**
* Set note a share
*
* @param note The note to a share, if the note is empty the
* share will be removed
*/
void setNote(const QString &shareId, const QString &note);
/**
* Set the password of a share
*

View File

@@ -14,6 +14,7 @@
#include "application.h"
#include "owncloudgui.h"
#include "ocsnavigationappsjob.h"
#include "theme.h"
#include "folderman.h"
#include "progressdispatcher.h"
@@ -45,11 +46,9 @@
#include <QX11Info>
#endif
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQmlApplicationEngine>
#include <QQuickItem>
#include <QQmlContext>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
namespace OCC {
@@ -63,32 +62,22 @@ ownCloudGui::ownCloudGui(Application *parent)
#ifdef WITH_LIBCLOUDPROVIDERS
, _bus(QDBusConnection::sessionBus())
#endif
, _recentActionsMenu(nullptr)
, _app(parent)
{
_tray = Systray::instance();
_tray = new Systray();
_tray->setParent(this);
// for the beginning, set the offline icon until the account was verified
_tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true));
_tray->show();
// for the beginning, set the offline icon until the account was verified
_tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false));
connect(_tray.data(), &QSystemTrayIcon::activated,
this, &ownCloudGui::slotTrayClicked);
connect(_tray.data(), &Systray::pauseSync,
this, &ownCloudGui::slotPauseAllFolders);
setupActions();
setupContextMenu();
connect(_tray.data(), &Systray::pauseSync,
this, &ownCloudGui::slotUnpauseAllFolders);
connect(_tray.data(), &Systray::openHelp,
this, &ownCloudGui::slotHelp);
connect(_tray.data(), &Systray::openSettings,
this, &ownCloudGui::slotShowSettings);
connect(_tray.data(), &Systray::shutdown,
this, &ownCloudGui::slotShutdown);
_tray->show();
ProgressDispatcher *pd = ProgressDispatcher::instance();
connect(pd, &ProgressDispatcher::progressInfo, this,
@@ -98,18 +87,17 @@ ownCloudGui::ownCloudGui(Application *parent)
connect(folderMan, &FolderMan::folderSyncStateChange,
this, &ownCloudGui::slotSyncStateChange);
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &ownCloudGui::updateContextMenuNeeded);
connect(AccountManager::instance(), &AccountManager::accountRemoved,
this, &ownCloudGui::updateContextMenuNeeded);
connect(Logger::instance(), &Logger::guiLog,
this, &ownCloudGui::slotShowTrayMessage);
connect(Logger::instance(), &Logger::optionalGuiLog,
this, &ownCloudGui::slotShowOptionalTrayMessage);
connect(Logger::instance(), &Logger::guiMessage,
this, &ownCloudGui::slotShowGuiMessage);
}
void ownCloudGui::createTray()
{
_tray->create();
}
#ifdef WITH_LIBCLOUDPROVIDERS
@@ -152,6 +140,13 @@ void ownCloudGui::slotOpenSettingsDialog()
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
{
if (_workaroundFakeDoubleClick) {
static QElapsedTimer last_click;
if (last_click.isValid() && last_click.elapsed() < 200) {
return;
}
last_click.start();
}
// Left click
if (reason == QSystemTrayIcon::Trigger) {
@@ -163,15 +158,17 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
Q_ASSERT(shareDialog.data());
raiseDialog(shareDialog);
}
} else if (_tray->isOpen()) {
_tray->hideWindow();
} else {
if (AccountManager::instance()->accounts().isEmpty()) {
this->slotOpenSettingsDialog();
} else {
_tray->showWindow();
#ifdef Q_OS_MAC
// on macOS, a left click always opens menu.
// However if the settings dialog is already visible but hidden
// by other applications, this will bring it to the front.
if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
raiseDialog(_settingsDialog.data());
}
#else
slotOpenSettingsDialog();
#endif
}
}
// FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613
@@ -181,6 +178,7 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
void ownCloudGui::slotSyncStateChange(Folder *folder)
{
slotComputeOverallSyncStatus();
updateContextMenuNeeded();
if (!folder) {
return; // Valid, just a general GUI redraw was needed.
@@ -196,11 +194,16 @@ void ownCloudGui::slotSyncStateChange(Folder *folder)
|| result.status() == SyncResult::Error) {
Logger::instance()->enterNextLogFile();
}
if (result.status() == SyncResult::NotYetStarted) {
_settingsDialog->slotRefreshActivity(folder->accountState());
}
}
void ownCloudGui::slotFoldersChanged()
{
slotComputeOverallSyncStatus();
updateContextMenuNeeded();
}
void ownCloudGui::slotOpenPath(const QString &path)
@@ -210,6 +213,7 @@ void ownCloudGui::slotOpenPath(const QString &path)
void ownCloudGui::slotAccountStateChanged()
{
updateContextMenuNeeded();
slotComputeOverallSyncStatus();
}
@@ -235,7 +239,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
// Don't overwrite the status if we're currently syncing
if (FolderMan::instance()->currentSyncFolder())
return;
//_actionStatus->setText(text);
_actionStatus->setText(text);
};
foreach (auto a, AccountManager::instance()->accounts()) {
@@ -255,7 +259,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
}
if (!problemAccounts.empty()) {
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
if (allDisconnected) {
setStatusText(tr("Disconnected"));
} else {
@@ -285,12 +289,12 @@ void ownCloudGui::slotComputeOverallSyncStatus()
}
if (allSignedOut) {
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
_tray->setToolTip(tr("Please sign in"));
setStatusText(tr("Signed out"));
return;
} else if (allPaused) {
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true));
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible()));
_tray->setToolTip(tr("Account synchronization is disabled"));
setStatusText(tr("Synchronization is paused"));
return;
@@ -317,7 +321,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
iconStatus = SyncResult::Problem;
}
QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true);
QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true, contextMenuVisible());
_tray->setIcon(statusIcon);
// create the tray blob message, check if we have an defined state
@@ -355,6 +359,381 @@ void ownCloudGui::slotComputeOverallSyncStatus()
}
}
void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu)
{
// Only show the name in the action if it's not part of an
// account sub menu.
QString browserOpen = tr("Open in browser");
if (!separateMenu) {
browserOpen = tr("Open %1 in browser").arg(Theme::instance()->appNameGUI());
}
auto actionOpenoC = menu->addAction(browserOpen);
actionOpenoC->setProperty(propertyAccountC, QVariant::fromValue(accountState->account()));
QObject::connect(actionOpenoC, &QAction::triggered, this, &ownCloudGui::slotOpenOwnCloud);
FolderMan *folderMan = FolderMan::instance();
bool firstFolder = true;
bool singleSyncFolder = folderMan->map().size() == 1 && Theme::instance()->singleSyncFolder();
bool onePaused = false;
bool allPaused = true;
foreach (Folder *folder, folderMan->map()) {
if (folder->accountState() != accountState.data()) {
continue;
}
if (folder->syncPaused()) {
onePaused = true;
} else {
allPaused = false;
}
if (firstFolder && !singleSyncFolder) {
firstFolder = false;
menu->addSeparator();
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
}
QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()));
auto alias = folder->alias();
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
}
menu->addSeparator();
if (separateMenu) {
if (onePaused) {
QAction *enable = menu->addAction(tr("Resume all folders"));
enable->setProperty(propertyAccountC, QVariant::fromValue(accountState));
connect(enable, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders);
}
if (!allPaused) {
QAction *enable = menu->addAction(tr("Pause all folders"));
enable->setProperty(propertyAccountC, QVariant::fromValue(accountState));
connect(enable, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders);
}
if (accountState->isSignedOut()) {
QAction *signin = menu->addAction(tr("Log in …"));
signin->setProperty(propertyAccountC, QVariant::fromValue(accountState));
connect(signin, &QAction::triggered, this, &ownCloudGui::slotLogin);
} else {
QAction *signout = menu->addAction(tr("Log out"));
signout->setProperty(propertyAccountC, QVariant::fromValue(accountState));
connect(signout, &QAction::triggered, this, &ownCloudGui::slotLogout);
}
}
}
void ownCloudGui::slotContextMenuAboutToShow()
{
_contextMenuVisibleManual = true;
// Update icon in sys tray, as it might change depending on the context menu state
slotComputeOverallSyncStatus();
if (!_workaroundNoAboutToShowUpdate) {
updateContextMenu();
}
}
void ownCloudGui::slotContextMenuAboutToHide()
{
_contextMenuVisibleManual = false;
// Update icon in sys tray, as it might change depending on the context menu state
slotComputeOverallSyncStatus();
}
bool ownCloudGui::contextMenuVisible() const
{
// On some platforms isVisible doesn't work and always returns false,
// elsewhere aboutToHide is unreliable.
if (_workaroundManualVisibility)
return _contextMenuVisibleManual;
return _contextMenu->isVisible();
}
static bool minimalTrayMenu()
{
static QByteArray var = qgetenv("OWNCLOUD_MINIMAL_TRAY_MENU");
return !var.isEmpty();
}
static bool updateWhileVisible()
{
static QByteArray var = qgetenv("OWNCLOUD_TRAY_UPDATE_WHILE_VISIBLE");
if (var == "1") {
return true;
} else if (var == "0") {
return false;
} else {
// triggers bug on OS X: https://bugreports.qt.io/browse/QTBUG-54845
// or flickering on Xubuntu
return false;
}
}
static QByteArray envForceQDBusTrayWorkaround()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
return var;
}
static QByteArray envForceWorkaroundShowAndHideTray()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE");
return var;
}
static QByteArray envForceWorkaroundNoAboutToShowUpdate()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW");
return var;
}
static QByteArray envForceWorkaroundFakeDoubleClick()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK");
return var;
}
static QByteArray envForceWorkaroundManualVisibility()
{
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY");
return var;
}
void ownCloudGui::setupContextMenu()
{
if (_contextMenu) {
return;
}
_contextMenu.reset(new QMenu());
_contextMenu->setTitle(Theme::instance()->appNameGUI());
_recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data());
// this must be called only once after creating the context menu, or
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
_tray->setContextMenu(_contextMenu.data());
// The tray menu is surprisingly problematic. Being able to switch to
// a minimal version of it is a useful workaround and testing tool.
if (minimalTrayMenu()) {
_contextMenu->addAction(_actionQuit);
return;
}
auto applyEnvVariable = [](bool *sw, const QByteArray &value) {
if (value == "1")
*sw = true;
if (value == "0")
*sw = false;
};
// This is an old compound flag that people might still depend on
bool qdbusmenuWorkarounds = false;
applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround());
if (qdbusmenuWorkarounds) {
_workaroundFakeDoubleClick = true;
_workaroundNoAboutToShowUpdate = true;
_workaroundShowAndHideTray = true;
}
#ifdef Q_OS_MAC
// https://bugreports.qt.io/browse/QTBUG-54633
_workaroundNoAboutToShowUpdate = true;
_workaroundManualVisibility = true;
#endif
#ifdef Q_OS_LINUX
// For KDE sessions if the platform plugin is missing,
// neither aboutToShow() updates nor the isVisible() call
// work. At least aboutToHide is reliable.
// https://github.com/owncloud/client/issues/6545
static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
static QByteArray desktopSession = qgetenv("DESKTOP_SESSION");
bool isKde =
xdgCurrentDesktop.contains("KDE")
|| desktopSession.contains("plasma")
|| desktopSession.contains("kde");
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
_workaroundManualVisibility = true;
_workaroundNoAboutToShowUpdate = true;
}
#endif
applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate());
applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick());
applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray());
applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility());
qCInfo(lcApplication) << "Tray menu workarounds:"
<< "noabouttoshow:" << _workaroundNoAboutToShowUpdate
<< "fakedoubleclick:" << _workaroundFakeDoubleClick
<< "showhide:" << _workaroundShowAndHideTray
<< "manualvisibility:" << _workaroundManualVisibility;
connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
_delayedTrayUpdateTimer.setInterval(2 * 1000);
_delayedTrayUpdateTimer.setSingleShot(true);
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
// unfortunately aboutToHide is unreliable, it seems to work on OSX though
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
// Populate the context menu now.
updateContextMenu();
}
void ownCloudGui::updateContextMenu()
{
if (minimalTrayMenu()) {
return;
}
// If it's visible, we can't update live, and it won't be updated lazily: reschedule
if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) {
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
if (_workaroundShowAndHideTray) {
// To make tray menu updates work with these bugs (see setupContextMenu)
// we need to hide and show the tray icon. We don't want to do that
// while it's visible!
if (contextMenuVisible()) {
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
_tray->hide();
}
_contextMenu->clear();
slotRebuildRecentMenus();
// We must call deleteLater because we might be called from the press in one of the actions.
foreach (auto menu, _accountMenus) {
menu->deleteLater();
}
_accountMenus.clear();
auto accountList = AccountManager::instance()->accounts();
bool isConfigured = (!accountList.isEmpty());
bool atLeastOneConnected = false;
bool atLeastOnePaused = false;
bool atLeastOneNotPaused = false;
foreach (auto a, accountList) {
if (a->isConnected()) {
atLeastOneConnected = true;
}
}
foreach (auto f, FolderMan::instance()->map()) {
if (f->syncPaused()) {
atLeastOnePaused = true;
} else {
atLeastOneNotPaused = true;
}
}
if (accountList.count() > 1) {
foreach (AccountStatePtr account, accountList) {
QMenu *accountMenu = new QMenu(account->account()->displayName(), _contextMenu.data());
_accountMenus.append(accountMenu);
_contextMenu->addMenu(accountMenu);
addAccountContextMenu(account, accountMenu, true);
fetchNavigationApps(account);
}
} else if (accountList.count() == 1) {
addAccountContextMenu(accountList.first(), _contextMenu.data(), false);
fetchNavigationApps(accountList.first());
}
_contextMenu->addSeparator();
_contextMenu->addAction(_actionStatus);
if (isConfigured && atLeastOneConnected) {
_contextMenu->addMenu(_recentActionsMenu);
}
_contextMenu->addSeparator();
if (_navLinksMenu) {
_contextMenu->addMenu(_navLinksMenu);
}
_contextMenu->addSeparator();
if (accountList.isEmpty()) {
_contextMenu->addAction(_actionNewAccountWizard);
}
_contextMenu->addAction(_actionSettings);
if (!Theme::instance()->helpUrl().isEmpty()) {
_contextMenu->addAction(_actionHelp);
}
if (_actionCrash) {
_contextMenu->addAction(_actionCrash);
}
_contextMenu->addSeparator();
if (atLeastOnePaused) {
QString text;
if (accountList.count() > 1) {
text = tr("Resume all synchronization");
} else {
text = tr("Resume synchronization");
}
QAction *action = _contextMenu->addAction(text);
connect(action, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders);
}
if (atLeastOneNotPaused) {
QString text;
if (accountList.count() > 1) {
text = tr("Pause all synchronization");
} else {
text = tr("Pause synchronization");
}
QAction *action = _contextMenu->addAction(text);
connect(action, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders);
}
_contextMenu->addAction(_actionQuit);
if (_workaroundShowAndHideTray) {
_tray->show();
}
}
void ownCloudGui::updateContextMenuNeeded()
{
// if it's visible and we can update live: update now
if (contextMenuVisible() && updateWhileVisible()) {
// Note: don't update while visible on OSX
// https://bugreports.qt.io/browse/QTBUG-54845
updateContextMenu();
return;
}
// if we can't lazily update: update later
if (_workaroundNoAboutToShowUpdate) {
// Note: don't update immediately even in the invisible case
// as that can lead to extremely frequent menu updates
if (!_delayedTrayUpdateTimer.isActive()) {
_delayedTrayUpdateTimer.start();
}
return;
}
}
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
{
if (_tray)
@@ -368,6 +747,7 @@ void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QStrin
slotShowTrayMessage(title, msg);
}
/*
* open the folder with the given Alias
*/
@@ -391,17 +771,156 @@ void ownCloudGui::slotFolderOpenAction(const QString &alias)
}
}
void ownCloudGui::setupActions()
{
_actionStatus = new QAction(tr("Unknown status"), this);
_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);
_actionRecent->setEnabled(true);
QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol);
QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings);
QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::slotNewAccountWizard);
_actionHelp = new QAction(tr("Help"), this);
QObject::connect(_actionHelp, &QAction::triggered, this, &ownCloudGui::slotHelp);
_actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this);
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit()));
if (_app->debugMode()) {
_actionCrash = new QAction(tr("Crash now", "Only shows in debug mode to allow testing the crash handler"), this);
connect(_actionCrash, &QAction::triggered, _app, &Application::slotCrash);
} else {
_actionCrash = nullptr;
}
}
void ownCloudGui::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
if(statusCode == 200){
qCDebug(lcApplication) << "New navigation apps ETag Response Header received " << value;
auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC));
account->setNavigationAppsEtagResponseHeader(value);
}
}
void ownCloudGui::fetchNavigationApps(AccountStatePtr account){
OcsNavigationAppsJob *job = new OcsNavigationAppsJob(account->account());
job->setProperty(propertyAccountC, QVariant::fromValue(account));
job->addRawHeader("If-None-Match", account->navigationAppsEtagResponseHeader());
connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &ownCloudGui::slotNavigationAppsFetched);
connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &ownCloudGui::slotEtagResponseHeaderReceived);
connect(job, &OcsNavigationAppsJob::ocsError, this, &ownCloudGui::slotOcsError);
job->getNavigationApps();
}
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;
// when there is more than one account add the nav links above pause/unpause folder or logout action
if(AccountManager::instance()->accounts().size() > 1){
foreach(QAction *action, accountMenu->actions()){
// pause/unpause folder and logout actions have propertyAccountC
if(auto actionAccount = qvariant_cast<AccountStatePtr>(action->property(propertyAccountC))){
if(actionAccount == account){
actionBefore = action;
break;
}
}
}
}
// Create submenu with links
foreach (const QJsonValue &value, navLinks) {
auto navLink = value.toObject();
QAction *action = new QAction(navLink.value("name").toString(), this);
QUrl href(navLink.value("href").toString());
connect(action, &QAction::triggered, this, [href] { QDesktopServices::openUrl(href); });
_navLinksMenu->addAction(action);
}
}
}
void ownCloudGui::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
{
if(auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC))){
if (statusCode == 304) {
qCWarning(lcApplication) << "Status code " << statusCode << " Not Modified - No new navigation apps.";
} else {
if(!reply.isEmpty()){
auto element = reply.object().value("ocs").toObject().value("data");
auto navLinks = element.toArray();
_navApps.insert(account, navLinks);
}
}
// TODO see pull #523
auto accountList = AccountManager::instance()->accounts();
if(accountList.size() > 1){
// the list of apps will be displayed under the account that it belongs to
foreach (QMenu *accountMenu, _accountMenus) {
if(accountMenu->title() == account->account()->displayName()){
buildNavigationAppsMenu(account, accountMenu);
break;
}
}
} else if(accountList.size() == 1){
buildNavigationAppsMenu(account, _contextMenu.data());
}
}
}
void ownCloudGui::slotOcsError(int statusCode, const QString &message)
{
emit serverError(statusCode, message);
}
void ownCloudGui::slotRebuildRecentMenus()
{
_recentActionsMenu->clear();
if (!_recentItemsActions.isEmpty()) {
foreach (QAction *a, _recentItemsActions) {
_recentActionsMenu->addAction(a);
}
_recentActionsMenu->addSeparator();
} else {
_recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false);
}
// add a more... entry.
_recentActionsMenu->addAction(_actionRecent);
}
/// Returns true if the completion of a given item should show up in the
/// 'Recent Activity' menu
static bool shouldShowInRecentsMenu(const SyncFileItem &item)
{
return !Progress::isIgnoredKind(item._status)
&& item._instruction != CSYNC_INSTRUCTION_EVAL
&& item._instruction != CSYNC_INSTRUCTION_NONE;
}
void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress)
{
Q_UNUSED(folder);
if (progress.status() == ProgressInfo::Discovery) {
if (!progress._currentDiscoveredRemoteFolder.isEmpty()) {
//_actionStatus->setText(tr("Checking for changes in remote '%1'")
//.arg(progress._currentDiscoveredRemoteFolder));
_actionStatus->setText(tr("Checking for changes in remote '%1'")
.arg(progress._currentDiscoveredRemoteFolder));
} else if (!progress._currentDiscoveredLocalFolder.isEmpty()) {
//_actionStatus->setText(tr("Checking for changes in local '%1'")
//.arg(progress._currentDiscoveredLocalFolder));
_actionStatus->setText(tr("Checking for changes in local '%1'")
.arg(progress._currentDiscoveredLocalFolder));
}
} else if (progress.status() == ProgressInfo::Done) {
QTimer::singleShot(2000, this, &ownCloudGui::slotComputeOverallSyncStatus);
@@ -424,7 +943,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
.arg(currentFile)
.arg(totalFileCount);
}
//_actionStatus->setText(msg);
_actionStatus->setText(msg);
} else {
QString totalSizeStr = Utility::octetsToString(progress.totalSize());
QString msg;
@@ -435,10 +954,18 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
msg = tr("Syncing %1")
.arg(totalSizeStr);
}
//_actionStatus->setText(msg);
_actionStatus->setText(msg);
}
if (!progress._lastCompletedItem.isEmpty()) {
_actionRecent->setIcon(QIcon()); // Fixme: Set a "in-progress"-item eventually.
if (!progress._lastCompletedItem.isEmpty()
&& shouldShowInRecentsMenu(progress._lastCompletedItem)) {
if (Progress::isWarningKind(progress._lastCompletedItem._status)) {
// display a warn icon if warnings happened.
QIcon warnIcon(":/client/resources/warning");
_actionRecent->setIcon(warnIcon);
}
QString kindStr = Progress::asResultString(progress._lastCompletedItem);
QString timeStr = QTime::currentTime().toString("hh:mm");
@@ -457,6 +984,12 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
_recentItemsActions.takeFirst()->deleteLater();
}
_recentItemsActions.append(action);
// Update the "Recent" menu if the context menu is being shown,
// otherwise it'll be updated later, when the context menu is opened.
if (updateWhileVisible() && contextMenuVisible()) {
slotRebuildRecentMenus();
}
}
}
@@ -564,7 +1097,6 @@ void ownCloudGui::slotShutdown()
_settingsDialog->close();
if (!_logBrowser.isNull())
_logBrowser->deleteLater();
_app->quit();
}
void ownCloudGui::slotToggleLogBrowser()

View File

@@ -63,7 +63,9 @@ public:
void setupCloudProviders();
bool cloudProviderApiAvailable();
#endif
void createTray();
/// Whether the tray menu is visible
bool contextMenuVisible() const;
signals:
void setupProxy();
@@ -71,10 +73,16 @@ signals:
void isShowingSettingsDialog();
public slots:
void setupContextMenu();
void updateContextMenu();
void updateContextMenuNeeded();
void slotContextMenuAboutToShow();
void slotContextMenuAboutToHide();
void slotComputeOverallSyncStatus();
void slotShowTrayMessage(const QString &title, const QString &msg);
void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
void slotFolderOpenAction(const QString &alias);
void slotRebuildRecentMenus();
void slotUpdateProgress(const QString &folder, const ProgressInfo &progress);
void slotShowGuiMessage(const QString &title, const QString &message);
void slotFoldersChanged();
@@ -91,6 +99,8 @@ public slots:
void slotOpenPath(const QString &path);
void slotAccountStateChanged();
void slotTrayMessageIfServerUnsupported(Account *account);
void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
/**
@@ -104,6 +114,9 @@ public slots:
void slotRemoveDestroyedShareDialogs();
protected slots:
void slotOcsError(int statusCode, const QString &message);
private slots:
void slotLogin();
void slotLogout();
@@ -113,21 +126,47 @@ private slots:
private:
void setPauseOnAllFoldersHelper(bool pause);
void setupActions();
void addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu);
void fetchNavigationApps(AccountStatePtr account);
void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu);
QPointer<Systray> _tray;
QPointer<SettingsDialog> _settingsDialog;
QPointer<LogBrowser> _logBrowser;
// tray's menu
QScopedPointer<QMenu> _contextMenu;
// Manually tracking whether the context menu is visible via aboutToShow
// and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere
// so this only gets used with _workaroundManualVisibility (when the tray's
// isVisible() is unreliable)
bool _contextMenuVisibleManual = false;
#ifdef WITH_LIBCLOUDPROVIDERS
QDBusConnection _bus;
#endif
QMenu *_recentActionsMenu;
QVector<QMenu *> _accountMenus;
bool _workaroundShowAndHideTray = false;
bool _workaroundNoAboutToShowUpdate = false;
bool _workaroundFakeDoubleClick = false;
bool _workaroundManualVisibility = false;
QTimer _delayedTrayUpdateTimer;
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
QAction *_actionNewAccountWizard;
QAction *_actionSettings;
QAction *_actionStatus;
QAction *_actionEstimate;
QAction *_actionRecent;
QAction *_actionHelp;
QAction *_actionQuit;
QAction *_actionCrash;
QMenu *_navLinksMenu;
QMap<AccountStatePtr, QJsonArray> _navApps;
QList<QAction *> _recentItemsActions;
Application *_app;

View File

@@ -1,5 +1,18 @@
#include "NotificationHandler.h"
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.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 "servernotificationhandler.h"
#include "accountstate.h"
#include "capabilities.h"
#include "networkjobs.h"
@@ -17,7 +30,7 @@ const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/a
const char propertyAccountStateC[] = "oc_account_state";
const int successStatusCode = 200;
const int notModifiedStatusCode = 304;
QMap<int, QByteArray> ServerNotificationHandler::iconCache;
QMap<int, QIcon> ServerNotificationHandler::iconCache;
ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent)
: QObject(parent)
@@ -28,7 +41,9 @@ ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState,
void ServerNotificationHandler::slotFetchNotifications()
{
// check connectivity and credentials
if (!(_accountState && _accountState->isConnected() && _accountState->account() && _accountState->account()->credentials() && _accountState->account()->credentials()->ready())) {
if (!(_accountState && _accountState->isConnected() &&
_accountState->account() && _accountState->account()->credentials() &&
_accountState->account()->credentials()->ready())) {
deleteLater();
return;
}
@@ -53,18 +68,18 @@ void ServerNotificationHandler::slotFetchNotifications()
_notificationJob->start();
}
void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode)
{
if (statusCode == successStatusCode) {
void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
if(statusCode == successStatusCode){
qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value;
AccountState *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
account->setNotificationsEtagResponseHeader(value);
}
}
void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData)
{
iconCache.insert(sender()->property("activityId").toInt(),iconData);
void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData){
QPixmap pixmap;
pixmap.loadFromData(iconData);
iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap));
}
void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode)
@@ -92,7 +107,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
auto json = element.toObject();
a._type = Activity::NotificationType;
a._accName = ai->account()->displayName();
a._id = json.value("activity_id").toInt();
a._id = json.value("notification_id").toInt();
//need to know, specially for remote_share
a._objectType = json.value("object_type").toString();
@@ -100,17 +115,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._subject = json.value("subject").toString();
a._message = json.value("message").toString();
a._icon = json.value("icon").toString();
if (!a._icon.isEmpty()) {
IconJob *iconJob = new IconJob(QUrl(a._icon));
if(!json.value("icon").toString().isEmpty()){
IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString()));
iconJob->setProperty("activityId", a._id);
connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded);
}
QUrl link(json.value("link").toString());
if (!link.isEmpty()) {
if (link.host().isEmpty()) {
if(link.host().isEmpty()){
link.setScheme(ai->account()->url().scheme());
link.setHost(ai->account()->url().host());
}
@@ -137,8 +151,8 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
ActivityLink al;
al._label = tr("Dismiss");
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
al._verb = "DELETE";
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
al._verb = "DELETE";
al._isPrimary = false;
a._links.append(al);
@@ -148,4 +162,4 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
deleteLater();
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef SERVERNOTIFICATIONHANDLER_H
#define SERVERNOTIFICATIONHANDLER_H
#include <QtCore>
#include "activitywidget.h"
class QJsonDocument;
namespace OCC {
class ServerNotificationHandler : public QObject
{
Q_OBJECT
public:
explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr);
static QMap<int, QIcon> iconCache;
signals:
void newNotificationList(ActivityList);
public slots:
void slotFetchNotifications();
private slots:
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotIconDownloaded(QByteArray iconData);
private:
QPointer<JsonApiJob> _notificationJob;
AccountState *_accountState;
};
}
#endif // SERVERNOTIFICATIONHANDLER_H

View File

@@ -23,6 +23,7 @@
#include "configfile.h"
#include "progressdispatcher.h"
#include "owncloudgui.h"
#include "activitywidget.h"
#include "accountmanager.h"
#include <QLabel>
@@ -188,13 +189,39 @@ void SettingsDialog::showFirstPage()
}
}
void SettingsDialog::showActivityPage()
{
if (auto account = qvariant_cast<AccountState*>(sender()->property("account"))) {
_activitySettings[account]->show();
_ui->stack->setCurrentWidget(_activitySettings[account]);
}
}
void SettingsDialog::showIssuesList(AccountState *account) {
/*for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
if (it.value() == _activitySettings[account]) {
it.key()->activate(QAction::ActionEvent::Trigger);
break;
}
}*/
}
}
void SettingsDialog::activityAdded(AccountState *s){
_ui->stack->addWidget(_activitySettings[s]);
connect(_activitySettings[s], &ActivitySettings::guiLog, _gui,
&ownCloudGui::slotShowOptionalTrayMessage);
ConfigFile cfg;
_activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
// Note: all the actions have a '\n' because the account name is in two lines and
// all buttons must have the same size in order to keep a good layout
QAction *action = createColorAwareAction(QLatin1String(":/client/resources/activity.png"), tr("Activity"));
action->setProperty("account", QVariant::fromValue(s));
_toolBar->insertAction(_actionBefore, action);
_actionGroup->addAction(action);
_actionGroupWidgets.insert(action, _activitySettings[s]);
connect(action, &QAction::triggered, this, &SettingsDialog::showActivityPage);
}
void SettingsDialog::accountAdded(AccountState *s)
@@ -202,6 +229,14 @@ void SettingsDialog::accountAdded(AccountState *s)
auto height = _toolBar->sizeHint().height();
bool brandingSingleAccount = !Theme::instance()->multiAccount();
_activitySettings[s] = new ActivitySettings(s, this);
// if this is not the first account, then before we continue to add more accounts we add a separator
if(AccountManager::instance()->accounts().first().data() != s &&
AccountManager::instance()->accounts().size() >= 1){
_actionGroupWidgets.insert(_toolBar->insertSeparator(_actionBefore), _activitySettings[s]);
}
QAction *accountAction;
QImage avatar = s->account()->avatar();
const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName();
@@ -229,11 +264,19 @@ void SettingsDialog::accountAdded(AccountState *s)
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
connect(accountSettings, &AccountSettings::openFolderAlias,
_gui, &ownCloudGui::slotFolderOpenAction);
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList);
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged);
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged);
// 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);
}
void SettingsDialog::slotAccountAvatarChanged()
@@ -289,6 +332,19 @@ void SettingsDialog::accountRemoved(AccountState *s)
_actionForAccount.remove(s->account().data());
}
if(_activitySettings.contains(s)){
_activitySettings[s]->slotRemoveAccount();
_activitySettings[s]->hide();
// get the settings widget and the separator
foreach(QAction *action, _actionGroupWidgets.keys(_activitySettings[s])){
_actionGroupWidgets.remove(action);
_toolBar->removeAction(action);
}
_toolBar->widgetForAction(_actionBefore)->hide();
_activitySettings.remove(s);
}
// Hide when the last account is deleted. We want to enter the same
// state we'd be in the client was started up without an account
// configured.
@@ -358,4 +414,15 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q
return createActionWithIcon(coloredIcon, text, iconPath);
}
void SettingsDialog::slotRefreshActivityAccountStateSender()
{
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
}
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
{
if (accountState->isConnected())
_activitySettings[accountState]->slotRefresh();
}
} // namespace OCC

View File

@@ -37,6 +37,7 @@ class AccountSettings;
class Application;
class FolderMan;
class ownCloudGui;
class ActivitySettings;
/**
* @brief The SettingsDialog class
@@ -54,8 +55,11 @@ public:
public slots:
void showFirstPage();
void showActivityPage();
void showIssuesList(AccountState *account);
void slotSwitchPage(QAction *action);
void slotRefreshActivity(AccountState *accountState);
void slotRefreshActivityAccountStateSender();
void slotAccountAvatarChanged();
void slotAccountDisplayNameChanged();
@@ -74,6 +78,7 @@ private slots:
private:
void customizeStyle();
void activityAdded(AccountState *);
QAction *createColorAwareAction(const QString &iconName, const QString &fileName);
QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString());
@@ -90,6 +95,7 @@ private:
QHash<Account *, QAction *> _actionForAccount;
QToolBar *_toolBar;
QMap<AccountState *, ActivitySettings *> _activitySettings;
ownCloudGui *_gui;
};

View File

@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<width>693</width>
<height>457</height>
</rect>
</property>

View File

@@ -28,7 +28,6 @@
#include <QFileInfo>
#include <QFileIconProvider>
#include <QInputDialog>
#include <QPointer>
#include <QPushButton>
#include <QFrame>
@@ -138,7 +137,6 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
_manager = new ShareManager(accountState->account(), this);
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
}
}
@@ -170,7 +168,7 @@ void ShareDialog::initLinkShareWidget(){
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
_linkWidgetList.append(_emptyShareLinkWidget);
connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
// connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
// connect(this, &ShareDialog::toggleAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleAnimation);
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
@@ -211,6 +209,7 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
emit toggleAnimation(false);
}
// TODO
void ShareDialog::slotAdjustScrollWidgetSize()
{
int count = this->findChildren<ShareLinkWidget *>().count();
@@ -304,24 +303,6 @@ void ShareDialog::slotCreateLinkShare()
_manager->createLinkShare(_sharePath, QString(), QString());
}
void ShareDialog::slotLinkShareRequiresPassword()
{
bool ok;
QString password = QInputDialog::getText(this,
tr("Password for share required"),
tr("Please enter a password for your link share:"),
QLineEdit::Normal,
QString(),
&ok);
if (!ok) {
// The dialog was canceled so no need to do anything
return;
}
// Try to create the link share again with the newly entered password
_manager->createLinkShare(_sharePath, QString(), password);
}
void ShareDialog::slotDeleteShare()
{

View File

@@ -64,7 +64,6 @@ private slots:
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
void slotDeleteShare();
void slotCreateLinkShare();
void slotLinkShareRequiresPassword();
void slotAdjustScrollWidgetSize();
signals:

View File

@@ -6,151 +6,159 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<width>372</width>
<height>150</height>
</rect>
</property>
<layout class="QVBoxLayout" name="shareDialogVerticalLayout">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
<property name="leftMargin">
<number>0</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="spacing">
<number>10</number>
</property>
<item row="0" column="1">
<widget class="QLabel" name="label_name">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>315</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>share label</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_sharePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>315</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Nextcloud Path:</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="label_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Icon</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="spacing">
<number>10</number>
</property>
<item row="1" column="1">
<widget class="QLabel" name="label_sharePath">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
<property name="minimumSize">
<size>
<width>315</width>
<height>0</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustIgnored</enum>
<property name="text">
<string>ownCloud Path:</string>
</property>
<property name="widgetResizable">
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>69</width>
<height>69</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
</widget>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_name">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>315</width>
<height>0</height>
</size>
</property>
<property name="text">
<string notr="true">share label</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="label_icon">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>40</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string notr="true">Icon</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>367</width>
<height>85</height>
</rect>
</property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@@ -28,7 +28,6 @@
#include <QDesktopServices>
#include <QMessageBox>
#include <QMenu>
#include <QTextEdit>
#include <QToolButton>
#include <QPropertyAnimation>
@@ -48,7 +47,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
, _localPath(localPath)
, _linkShare(nullptr)
, _passwordRequired(false)
, _noteRequired(false)
, _expiryRequired(false)
, _namesSupported(true)
, _linkContextMenu(nullptr)
@@ -57,13 +55,14 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
, _allowUploadEditingLinkAction(nullptr)
, _allowUploadLinkAction(nullptr)
, _passwordProtectLinkAction(nullptr)
, _noteLinkAction(nullptr)
, _expirationDateLinkAction(nullptr)
, _unshareLinkAction(nullptr)
{
_ui->setupUi(this);
QSizePolicy sp = _ui->shareLinkToolButton->sizePolicy();
sp.setRetainSizeWhenHidden(true);
_ui->shareLinkToolButton->setSizePolicy(sp);
_ui->shareLinkToolButton->hide();
//Is this a file or folder?
@@ -73,8 +72,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
connect(_ui->textEdit_note, &QTextEdit::textChanged, this, &ShareLinkWidget::slotCreateNote);
connect(_ui->confirmNote, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreateNote);
connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
@@ -100,7 +97,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
togglePasswordOptions(false);
toggleExpireDateOptions(false);
toggleNoteOptions(false);
_ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
// check if the file is already inside of a synced folder
@@ -115,8 +111,7 @@ ShareLinkWidget::~ShareLinkWidget()
delete _ui;
}
void ShareLinkWidget::slotToggleAnimation(bool start)
{
void ShareLinkWidget::slotToggleAnimation(bool start){
if (start) {
if (!_ui->progressIndicator->isAnimated())
_ui->progressIndicator->startAnimation();
@@ -125,25 +120,21 @@ void ShareLinkWidget::slotToggleAnimation(bool start)
}
}
void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare)
{
void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare){
_linkShare = linkShare;
}
QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare()
{
QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare(){
return _linkShare;
}
void ShareLinkWidget::setupUiOptions()
{
void ShareLinkWidget::setupUiOptions(){
connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
connect(_linkShare.data(), &LinkShare::noteSet, this, &ShareLinkWidget::slotNoteSet);
connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
// Prepare permissions check and create group action
const QDate expireDate = _linkShare.data()->getExpireDate().isValid() ? _linkShare.data()->getExpireDate() : QDate();
const QDate expireDate = _linkShare.data()->getExpireDate().isValid()? _linkShare.data()->getExpireDate() : QDate();
const SharePermissions perm = _linkShare.data()->getPermissions();
bool checked = false;
QActionGroup *permissionsGroup = new QActionGroup(this);
@@ -154,7 +145,7 @@ void ShareLinkWidget::setupUiOptions()
// radio button style
permissionsGroup->setExclusive(true);
if (_isFile) {
if(_isFile){
checked = perm & (SharePermissionRead & SharePermissionUpdate);
_allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow Editing"));
_allowEditingLinkAction->setCheckable(true);
@@ -166,7 +157,10 @@ void ShareLinkWidget::setupUiOptions()
_readOnlyLinkAction->setCheckable(true);
_readOnlyLinkAction->setChecked(checked);
checked = perm & (SharePermissionRead & SharePermissionCreate & SharePermissionUpdate & SharePermissionDelete);
checked = perm & (SharePermissionRead &
SharePermissionCreate &
SharePermissionUpdate &
SharePermissionDelete);
_allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow Upload && Editing"));
_allowUploadEditingLinkAction->setCheckable(true);
_allowUploadEditingLinkAction->setChecked(checked);
@@ -178,7 +172,7 @@ void ShareLinkWidget::setupUiOptions()
}
// Adds permissions actions (radio button style)
if (_isFile) {
if(_isFile){
_linkContextMenu->addAction(_allowEditingLinkAction);
} else {
_linkContextMenu->addAction(_readOnlyLinkAction);
@@ -186,21 +180,11 @@ void ShareLinkWidget::setupUiOptions()
_linkContextMenu->addAction(_allowUploadLinkAction);
}
// Adds action to display note widget (check box)
_noteLinkAction = _linkContextMenu->addAction(tr("Add note to recipient"));
_noteLinkAction->setCheckable(true);
if (_linkShare->getNote().isSimpleText()) {
_ui->textEdit_note->setText(_linkShare->getNote());
_noteLinkAction->setChecked(true);
showNoteOptions(true);
}
// Adds action to display password widget (check box)
_passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password Protect"));
_passwordProtectLinkAction->setCheckable(true);
if (_linkShare.data()->isPasswordSet()) {
if(_linkShare.data()->isPasswordSet()){
_passwordProtectLinkAction->setChecked(true);
_ui->lineEdit_password->setPlaceholderText("********");
showPasswordOptions(true);
@@ -216,7 +200,7 @@ void ShareLinkWidget::setupUiOptions()
// Adds action to display expiration date widget (check box)
_expirationDateLinkAction = _linkContextMenu->addAction(tr("Expiration Date"));
_expirationDateLinkAction->setCheckable(true);
if (!expireDate.isNull()) {
if(!expireDate.isNull()){
_ui->calendar->setDate(expireDate);
_expirationDateLinkAction->setChecked(true);
showExpireDateOptions(true);
@@ -233,12 +217,12 @@ void ShareLinkWidget::setupUiOptions()
// Adds action to unshare widget (check box)
_unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/delete.png"),
tr("Unshare"));
tr("Unshare"));
_linkContextMenu->addSeparator();
_addAnotherLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/add.png"),
tr("Add another link"));
tr("Add another link"));
_ui->enableShareLink->setIcon(QIcon(":/client/resources/copy.svg"));
disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
@@ -261,27 +245,7 @@ void ShareLinkWidget::setupUiOptions()
customizeStyle();
}
void ShareLinkWidget::setNote(const QString &note)
{
if (_linkShare) {
slotToggleAnimation(true);
_ui->errorLabel->hide();
_linkShare->setNote(note);
}
}
void ShareLinkWidget::slotCreateNote()
{
setNote(_ui->textEdit_note->toPlainText());
}
void ShareLinkWidget::slotNoteSet()
{
slotToggleAnimation(false);
}
void ShareLinkWidget::slotCopyLinkShare(bool clicked)
{
void ShareLinkWidget::slotCopyLinkShare(bool clicked){
Q_UNUSED(clicked);
QApplication::clipboard()->setText(_linkShare->getLink().toString());
@@ -294,7 +258,7 @@ void ShareLinkWidget::slotExpireDateSet()
void ShareLinkWidget::slotSetExpireDate()
{
if (!_linkShare) {
if(!_linkShare){
return;
}
@@ -334,8 +298,8 @@ void ShareLinkWidget::slotPasswordSet()
slotToggleAnimation(false);
}
void ShareLinkWidget::startAnimation(const int start, const int end)
{
void ShareLinkWidget::startAnimation(const int start, const int end){
QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this);
animation->setDuration(500);
@@ -343,7 +307,7 @@ void ShareLinkWidget::startAnimation(const int start, const int end)
animation->setEndValue(end);
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
if (end < start) // that is to remove the widget, not to show it
if(end < start) // that is to remove the widget, not to show it
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished);
connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested);
@@ -359,32 +323,10 @@ void ShareLinkWidget::slotDeleteShareFetched()
_linkShare.clear();
togglePasswordOptions(false);
toggleNoteOptions(false);
toggleExpireDateOptions(false);
emit deleteLinkShare();
}
void ShareLinkWidget::showNoteOptions(bool show)
{
_ui->noteLabel->setVisible(show);
_ui->textEdit_note->setVisible(show);
_ui->confirmNote->setVisible(show);
}
void ShareLinkWidget::toggleNoteOptions(bool enable)
{
showNoteOptions(enable);
if (enable) {
_ui->textEdit_note->setFocus();
} else {
// 'deletes' note
if (_linkShare)
_linkShare->setNote(QString());
}
}
void ShareLinkWidget::slotAnimationFinished()
{
emit resizeRequested();
@@ -425,11 +367,11 @@ void ShareLinkWidget::togglePasswordOptions(bool enable)
{
showPasswordOptions(enable);
if (enable) {
if(enable) {
_ui->lineEdit_password->setFocus();
} else {
// 'deletes' password
if (_linkShare)
if(_linkShare)
_linkShare->setPassword(QString());
}
}
@@ -452,7 +394,7 @@ void ShareLinkWidget::toggleExpireDateOptions(bool enable)
_ui->calendar->setFocus();
} else {
// 'deletes' expire date
if (_linkShare)
if(_linkShare)
_linkShare->setExpireDate(QDate());
}
}
@@ -473,11 +415,11 @@ void ShareLinkWidget::confirmAndDeleteShare()
connect(messageBox, &QMessageBox::finished, this,
[messageBox, yesButton, this]() {
if (messageBox->clickedButton() == yesButton) {
this->slotToggleAnimation(true);
this->_linkShare->deleteShare();
}
});
if (messageBox->clickedButton() == yesButton) {
this->slotToggleAnimation(true);
this->_linkShare->deleteShare();
}
});
messageBox->open();
}
@@ -498,10 +440,11 @@ void ShareLinkWidget::slotContextMenuButtonClicked()
void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
{
bool state = action->isChecked();
SharePermissions perm = SharePermissionRead;
if (action == _addAnotherLinkAction) {
if(action == _addAnotherLinkAction){
emit createLinkShare();
} else if (action == _readOnlyLinkAction && state) {
@@ -525,9 +468,6 @@ void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
} else if (action == _expirationDateLinkAction) {
toggleExpireDateOptions(state);
} else if (action == _noteLinkAction) {
toggleNoteOptions(state);
} else if (action == _unshareLinkAction) {
confirmAndDeleteShare();
}
@@ -565,12 +505,15 @@ void ShareLinkWidget::customizeStyle()
_addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/add.png"));
_ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/resources/copy.svg"));
_ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/resources/public.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"));
_ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.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"));

View File

@@ -74,9 +74,6 @@ private slots:
void slotPasswordSet();
void slotPasswordSetError(int code, const QString &message);
void slotCreateNote();
void slotNoteSet();
void slotSetExpireDate();
void slotExpireDateSet();
@@ -98,10 +95,6 @@ private:
void showPasswordOptions(bool show);
void togglePasswordOptions(bool enable);
void showNoteOptions(bool show);
void toggleNoteOptions(bool enable);
void setNote(const QString &note);
void showExpireDateOptions(bool show);
void toggleExpireDateOptions(bool enable);
@@ -129,7 +122,6 @@ private:
bool _passwordRequired;
bool _expiryRequired;
bool _namesSupported;
bool _noteRequired;
QMenu *_linkContextMenu;
QAction *_readOnlyLinkAction;
@@ -140,7 +132,6 @@ private:
QAction *_expirationDateLinkAction;
QAction *_unshareLinkAction;
QAction *_addAnotherLinkAction;
QAction *_noteLinkAction;
};
}

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>365</width>
<height>192</height>
<width>350</width>
<height>160</height>
</rect>
</property>
<property name="sizePolicy">
@@ -16,266 +16,60 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="shareLinkIconLabel">
<property name="text">
<string notr="true"/>
</property>
<property name="pixmap">
<pixmap resource="../../client.qrc">:/client/resources/public.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="shareLinkLabel">
<property name="text">
<string>Share link</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QProgressIndicator" name="progressIndicator" native="true"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="enableShareLink">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/add.png</normaloff>:/client/resources/add.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="shareLinkToolButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="2" column="1">
<widget class="QDateEdit" name="calendar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<widget class="QLabel" name="noteLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>78</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Note:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="textEdit_note">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>60</height>
</size>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="confirmNote">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="passwordLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>78</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Password:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="confirmPassword">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
<item row="1" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Password:</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="expirationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>78</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Expires:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QDateEdit" name="calendar">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="confirmExpirationDate">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
<item row="0" column="3">
<widget class="QToolButton" name="shareLinkToolButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<item row="3" column="0" colspan="4">
<widget class="QLabel" name="errorLabel">
<property name="palette">
<palette>
@@ -325,6 +119,130 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_password">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="confirmPassword">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QToolButton" name="confirmExpirationDate">
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="createShareButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">text-align: left</string>
</property>
<property name="text">
<string>&amp;Share link</string>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/public.svg</normaloff>:/client/resources/public.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QProgressIndicator" name="progressIndicator" native="true"/>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>25</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="enableShareLink">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/add.png</normaloff>:/client/resources/add.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="expirationLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Expiration date:</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>

View File

@@ -200,11 +200,6 @@ QString LinkShare::getName() const
return _name;
}
QString LinkShare::getNote() const
{
return _note;
}
void LinkShare::setName(const QString &name)
{
OcsShareJob *job = new OcsShareJob(_account);
@@ -213,20 +208,6 @@ void LinkShare::setName(const QString &name)
job->setName(getId(), name);
}
void LinkShare::setNote(const QString &note)
{
OcsShareJob *job = new OcsShareJob(_account);
connect(job, &OcsShareJob::shareJobFinished, this, &LinkShare::slotNoteSet);
connect(job, &OcsJob::ocsError, this, &LinkShare::slotOcsError);
job->setNote(getId(), note);
}
void LinkShare::slotNoteSet(const QJsonDocument &, const QVariant &note)
{
_note = note.toString();
emit noteSet();
}
QString LinkShare::getToken() const
{
return _token;

View File

@@ -183,12 +183,6 @@ public:
*/
QString getName() const;
/*
* Returns the note of the link share.
*/
QString getNote() const;
/*
* Set the name of the link share.
*
@@ -196,12 +190,6 @@ public:
*/
void setName(const QString &name);
/*
* Set the note of the link share.
*/
void setNote(const QString &note);
/*
* Returns the token of the link share.
*/
@@ -236,13 +224,11 @@ public:
signals:
void expireDateSet();
void passwordSet();
void noteSet();
void passwordSetError(int statusCode, const QString &message);
void nameSet();
private slots:
void slotPasswordSet(const QJsonDocument &, const QVariant &value);
void slotNoteSet(const QJsonDocument &, const QVariant &value);
void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
void slotSetPasswordError(int statusCode, const QString &message);
void slotNameSet(const QJsonDocument &, const QVariant &value);
@@ -251,7 +237,6 @@ private:
QString _name;
QString _token;
bool _passwordSet;
QString _note;
QDate _expireDate;
QUrl _url;
};

View File

@@ -46,8 +46,6 @@
#include <QPainter>
#include <QListWidget>
#include <string.h>
namespace OCC {
ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
@@ -208,8 +206,7 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
}
// the owner of the file that shared it first
// leave out if it's the current user
if(x == 0 && !share->getUidOwner().isEmpty() && !(share->getUidOwner() == _account->credentials()->user())) {
if(x == 0 && !share->getUidOwner().isEmpty()){
_ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
}

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>350</width>
<height>70</height>
<height>55</height>
</rect>
</property>
<property name="sizePolicy">
@@ -17,24 +17,30 @@
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="mainOwnerLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="shareeHorizontalLayout" stretch="0,0">
<property name="spacing">
<number>6</number>
</property>
<layout class="QHBoxLayout" name="shareeHorizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -49,12 +55,6 @@
</property>
<item>
<widget class="QLineEdit" name="shareeLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Share with users or groups ...</string>
</property>

View File

@@ -6,26 +6,35 @@
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>58</height>
<width>350</width>
<height>45</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>360</width>
<height>0</height>
</size>
</property>
<property name="autoFillBackground">
<bool>false</bool>
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,2,2,2">
<property name="spacing">
<number>5</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="avatar">
<property name="sizePolicy">
@@ -48,7 +57,7 @@
<item>
<widget class="OCC::ElidedLabel" name="sharedWith">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<sizepolicy hsizetype="Ignored" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@@ -61,22 +70,6 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="permissionsEdit">
<property name="sizePolicy">
@@ -96,12 +89,9 @@
<iconset resource="../../client.qrc">
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</layout>
</widget>
<customwidgets>
<customwidget>

View File

@@ -49,7 +49,6 @@
#include <QLocalSocket>
#include <QStringBuilder>
#include <QMessageBox>
#include <QInputDialog>
#include <QClipboard>
@@ -455,42 +454,7 @@ void SocketApi::command_VERSION(const QString &, SocketListener *listener)
void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
{
//listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + Theme::instance()->appNameGUI());
}
void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
{
auto fileData = FileData::get(localFile);
if (!fileData.folder) {
qCWarning(lcSocketApi) << "Unknown path" << localFile;
return;
}
auto record = fileData.journalRecord();
if (!record.isValid())
return;
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
if (!editor)
return;
JsonApiJob *job = new JsonApiJob(fileData.folder->accountState()->account(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing/open"), this);
QUrlQuery params;
params.addQueryItem("path", fileData.accountRelativePath);
params.addQueryItem("editorId", editor->id());
job->addQueryParams(params);
job->usePOST();
QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
auto data = json.object().value("ocs").toObject().value("data").toObject();
auto url = QUrl(data.value("url").toString());
if(!url.isEmpty())
Utility::openBrowser(url, nullptr);
});
job->start();
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
}
// don't pull the share manager into socketapi unittests
@@ -513,8 +477,6 @@ public:
this, &GetOrCreatePublicLinkShare::linkShareCreated);
connect(&_shareManager, &ShareManager::serverError,
this, &GetOrCreatePublicLinkShare::serverError);
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
this, &GetOrCreatePublicLinkShare::passwordRequired);
}
void run()
@@ -550,24 +512,6 @@ private slots:
success(share->getLink().toString());
}
void passwordRequired() {
bool ok;
QString password = QInputDialog::getText(nullptr,
tr("Password for share required"),
tr("Please enter a password for your link share:"),
QLineEdit::Normal,
QString(),
&ok);
if (!ok) {
// The dialog was canceled so no need to do anything
return;
}
// Try to create the link share again with the newly entered password
_shareManager.createLinkShare(_localFile, QString(), password);
}
void serverError(int code, const QString &message)
{
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
@@ -679,7 +623,7 @@ 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", 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 …") },
} };
@@ -773,41 +717,13 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument);
bool isOnTheServer = fileData.journalRecord().isValid();
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
auto capabilities = fileData.folder->accountState()->account()->capabilities();
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
if (editor) {
//listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name());
listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit"));
} else {
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
}
sendSharingContextMenuOptions(fileData, listener);
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
}
listener->sendMessage(QString("GET_MENU_ITEMS:END"));
}
DirectEditor* SocketApi::getDirectEditorForLocalFile(const QString &localFile)
{
FileData fileData = FileData::get(localFile);
auto capabilities = fileData.folder->accountState()->account()->capabilities();
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
QMimeDatabase db;
QMimeType type = db.mimeTypeForFile(localFile);
DirectEditor* editor = capabilities.getDirectEditorForMimetype(type);
if (!editor) {
editor = capabilities.getDirectEditorForOptionalMimetype(type);
}
return editor;
}
return nullptr;
}
QString SocketApi::buildRegisterPathMessage(const QString &path)
{
QFileInfo fi(path);

View File

@@ -37,7 +37,6 @@ namespace OCC {
class SyncFileStatus;
class Folder;
class SocketListener;
class DirectEditor;
/**
* @brief The SocketApi class
@@ -124,10 +123,6 @@ private:
*/
Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener);
/// Direct Editing
Q_INVOKABLE void command_EDIT(const QString &localFile, SocketListener *listener);
DirectEditor* getDirectEditorForLocalFile(const QString &localFile);
QString buildRegisterPathMessage(const QString &path);
QSet<QString> _registeredAliases;

View File

@@ -12,17 +12,9 @@
* for more details.
*/
#include "accountmanager.h"
#include "systray.h"
#include "theme.h"
#include "config.h"
#include "tray/UserModel.h"
#include <QDesktopServices>
#include <QGuiApplication>
#include <QQmlComponent>
#include <QQmlEngine>
#include <QScreen>
#ifdef USE_FDO_NOTIFICATIONS
#include <QDBusConnection>
@@ -36,78 +28,10 @@
namespace OCC {
Systray *Systray::_instance = nullptr;
Systray *Systray::instance()
{
if (_instance == nullptr) {
_instance = new Systray();
}
return _instance;
}
Systray::Systray()
: _isOpen(false)
, _syncIsPaused(false)
, _trayComponent(nullptr)
, _trayContext(nullptr)
{
// Create QML tray engine, build component, set C++ backend context used in window.qml
// Use pointer instead of engine() helper function until Qt 5.12 is minimum standard
_trayEngine = new QQmlEngine;
_trayEngine->addImageProvider("avatars", new ImageProvider);
_trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance());
_trayEngine->rootContext()->setContextProperty("appsMenuModelBackend", UserAppsModel::instance());
_trayEngine->rootContext()->setContextProperty("systrayBackend", this);
_trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml")));
connect(UserModel::instance(), &UserModel::newUserSelected,
this, &Systray::slotNewUserSelected);
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &Systray::showWindow);
}
void Systray::create()
{
if (_trayContext == nullptr) {
if (!AccountManager::instance()->accounts().isEmpty()) {
_trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
}
_trayContext = _trayEngine->contextForObject(_trayComponent->create());
hideWindow();
}
}
void Systray::slotNewUserSelected()
{
// Change ActivityModel
_trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
// Rebuild App list
UserAppsModel::instance()->buildAppList();
}
bool Systray::isOpen()
{
return _isOpen;
}
Q_INVOKABLE void Systray::setOpened()
{
_isOpen = true;
}
Q_INVOKABLE void Systray::setClosed()
{
_isOpen = false;
}
void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint)
{
#ifdef USE_FDO_NOTIFICATIONS
if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) {
if(QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) {
QList<QVariant> args = QList<QVariant>() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME
<< title << message << QStringList() << QVariantMap() << qint32(-1);
QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify");
@@ -130,95 +54,4 @@ void Systray::setToolTip(const QString &tip)
QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
}
int Systray::calcTrayWindowX()
{
#ifdef Q_OS_OSX
// macOS handles DPI awareness differently
// and menu bar is always at the top, icons starting from the right
QPoint topLeft = this->geometry().topLeft();
QPoint topRight = this->geometry().topRight();
int trayIconTopCenterX = (topRight - ((topRight - topLeft) * 0.5)).x();
return trayIconTopCenterX - (400 * 0.5);
#else
QScreen *trayScreen = QGuiApplication::primaryScreen();
int screenWidth = trayScreen->geometry().width();
int screenHeight = trayScreen->geometry().height();
int availableWidth = trayScreen->availableGeometry().width();
int availableHeight = trayScreen->availableGeometry().height();
QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
// get coordinates from top center point of tray icon
int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y();
if (availableHeight < screenHeight) {
// taskbar is on top or bottom
if (trayIconTopCenterX + (400 * 0.5) > availableWidth) {
return availableWidth - 400 - 12;
} else {
return trayIconTopCenterX - (400 * 0.5);
}
} else {
if (trayScreen->availableGeometry().x() > trayScreen->geometry().x()) {
// on the left
return (screenWidth - availableWidth) + 6;
} else {
// on the right
return screenWidth - 400 - (screenWidth - availableWidth) - 6;
}
}
#endif
}
int Systray::calcTrayWindowY()
{
#ifdef Q_OS_OSX
// macOS menu bar is always 22 (effective) pixels
// don't use availableGeometry() here, because this also excludes the dock
return 22+6;
#else
QScreen *trayScreen = QGuiApplication::primaryScreen();
int screenWidth = trayScreen->geometry().width();
int screenHeight = trayScreen->geometry().height();
int availableHeight = trayScreen->availableGeometry().height();
QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
// get coordinates from top center point of tray icon
int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y();
if (availableHeight < screenHeight) {
// taskbar is on top or bottom
if (trayScreen->availableGeometry().y() > trayScreen->geometry().y()) {
// on top
return (screenHeight - availableHeight) + 6;
} else {
// on bottom
return screenHeight - 510 - (screenHeight - availableHeight) - 6;
}
} else {
// on the left or right
return (trayIconTopCenterY - 510 + 12);
}
#endif
}
bool Systray::syncIsPaused()
{
return _syncIsPaused;
}
void Systray::pauseResumeSync()
{
if (_syncIsPaused) {
_syncIsPaused = false;
emit resumeSync();
} else {
_syncIsPaused = true;
emit pauseSync();
}
}
} // namespace OCC

View File

@@ -16,10 +16,6 @@
#define SYSTRAY_H
#include <QSystemTrayIcon>
#include <QQmlContext>
#include "accountmanager.h"
#include "tray/UserModel.h"
class QIcon;
@@ -30,56 +26,16 @@ bool canOsXSendUserNotification();
void sendOsXUserNotification(const QString &title, const QString &message);
#endif
namespace Ui {
class Systray;
}
/**
* @brief The Systray class
* @ingroup gui
*/
class Systray
: public QSystemTrayIcon
class Systray : public QSystemTrayIcon
{
Q_OBJECT
public:
static Systray *instance();
virtual ~Systray() {};
void create();
void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000);
void setToolTip(const QString &tip);
bool isOpen();
Q_INVOKABLE void pauseResumeSync();
Q_INVOKABLE int calcTrayWindowX();
Q_INVOKABLE int calcTrayWindowY();
Q_INVOKABLE bool syncIsPaused();
Q_INVOKABLE void setOpened();
Q_INVOKABLE void setClosed();
signals:
void currentUserChanged();
void openSettings();
void openHelp();
void shutdown();
void pauseSync();
void resumeSync();
Q_INVOKABLE void hideWindow();
Q_INVOKABLE void showWindow();
public slots:
void slotNewUserSelected();
private:
static Systray *_instance;
Systray();
bool _isOpen;
bool _syncIsPaused;
QQmlEngine *_trayEngine;
QQmlComponent *_trayComponent;
QQmlContext *_trayContext;
};
} // namespace OCC

View File

@@ -1,36 +0,0 @@
#ifndef NOTIFICATIONHANDLER_H
#define NOTIFICATIONHANDLER_H
#include <QtCore>
#include "UserModel.h"
class QJsonDocument;
namespace OCC {
class ServerNotificationHandler : public QObject
{
Q_OBJECT
public:
explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr);
static QMap<int, QByteArray> iconCache;
signals:
void newNotificationList(ActivityList);
public slots:
void slotFetchNotifications();
private slots:
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotIconDownloaded(QByteArray iconData);
private:
QPointer<JsonApiJob> _notificationJob;
AccountState *_accountState;
};
}
#endif // NOTIFICATIONHANDLER_H

View File

@@ -1,156 +0,0 @@
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
MenuItem {
id: userLine
height: 60
RowLayout {
id: userLineLayout
spacing: 0
width: 220
height: 60
Button {
id: accountButton
Layout.preferredWidth: (userLineLayout.width * (5/6))
Layout.preferredHeight: (userLineLayout.height)
display: AbstractButton.IconOnly
flat: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
}
onClicked: {
if (!isCurrentUser) {
userModelBackend.switchCurrentUser(id)
} else {
accountMenu.close()
}
}
}
background: Rectangle {
color: "transparent"
}
RowLayout {
id: accountControlRowLayout
height: accountButton.height
width: accountButton.width
spacing: 0
Image {
id: accountAvatar
Layout.leftMargin: 4
verticalAlignment: Qt.AlignCenter
cache: false
source: ("image://avatars/" + id)
Layout.preferredHeight: (userLineLayout.height -16)
Layout.preferredWidth: (userLineLayout.height -16)
Rectangle {
id: accountStateIndicatorBackground
width: accountStateIndicator.sourceSize.width + 2
height: width
anchors.bottom: accountAvatar.bottom
anchors.right: accountAvatar.right
color: "white"
radius: width*0.5
}
Image {
id: accountStateIndicator
source: isConnected ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
cache: false
x: accountStateIndicatorBackground.x + 1
y: accountStateIndicatorBackground.y + 1
sourceSize.width: 16
sourceSize.height: 16
}
}
Column {
id: accountLabels
spacing: 4
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 6
Label {
id: accountUser
width: 128
text: name
elide: Text.ElideRight
color: "black"
font.pixelSize: 12
font.bold: true
}
Label {
id: accountServer
width: 128
text: server
elide: Text.ElideRight
color: "black"
font.pixelSize: 10
}
}
}
} // accountButton
Button {
id: userMoreButton
Layout.preferredWidth: (userLineLayout.width * (1/6))
Layout.preferredHeight: userLineLayout.height
flat: true
icon.source: "qrc:///client/resources/more.svg"
icon.color: "transparent"
MouseArea {
id: userMoreButtonMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked:
{
userMoreButtonMenu.popup()
}
}
background:
Rectangle {
color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent"
opacity: 0.2
height: userMoreButton.height - 2
y: userMoreButton.y + 1
}
Menu {
id: userMoreButtonMenu
width: 120
background: Rectangle {
border.color: "#0082c9"
radius: 2
}
MenuItem {
text: isConnected ? qsTr("Log out") : qsTr("Log in")
font.pixelSize: 12
onClicked: {
isConnected ? userModelBackend.logout(index) : userModelBackend.login(index)
accountMenu.close()
}
}
MenuItem {
text: qsTr("Remove Account")
font.pixelSize: 12
onClicked: {
userModelBackend.removeAccount(index)
accountMenu.close()
}
}
}
}
}
} // MenuItem userLine

View File

@@ -1,874 +0,0 @@
#include "NotificationHandler.h"
#include "UserModel.h"
#include "accountmanager.h"
#include "owncloudgui.h"
#include "syncengine.h"
#include "ocsjob.h"
#include "configfile.h"
#include "notificationconfirmjob.h"
#include <QDesktopServices>
#include <QIcon>
#include <QMessageBox>
#include <QSvgRenderer>
#include <QPainter>
#include <QPushButton>
// time span in milliseconds which has to be between two
// refreshes of the notifications
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
namespace OCC {
User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
: QObject(parent)
, _account(account)
, _isCurrentUser(isCurrent)
, _activityModel(new ActivityListModel(_account.data()))
, _notificationRequestsRunning(0)
{
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
this, &User::slotProgressInfo);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
this, &User::slotItemCompleted);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
this, &User::slotAddError);
connect(&_notificationCheckTimer, &QTimer::timeout,
this, &User::slotRefresh);
connect(_account.data(), &AccountState::stateChanged,
[=]() { if (isConnected()) {slotRefresh();} });
connect(_account.data(), &AccountState::hasFetchedNavigationApps,
this, &User::slotRebuildNavigationAppList);
}
void User::slotBuildNotificationDisplay(const ActivityList &list)
{
// Whether a new notification was added to the list
bool newNotificationShown = false;
_activityModel->clearNotifications();
foreach (auto activity, list) {
if (_blacklistedNotifications.contains(activity)) {
qCInfo(lcActivity) << "Activity in blacklist, skip";
continue;
}
// handle gui logs. In order to NOT annoy the user with every fetching of the
// notifications the notification id is stored in a Set. Only if an id
// is not in the set, it qualifies for guiLog.
// Important: The _guiLoggedNotifications set must be wiped regularly which
// will repeat the gui log.
// after one hour, clear the gui log notification store
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
_guiLoggedNotifications.clear();
}
if (!_guiLoggedNotifications.contains(activity._id)) {
newNotificationShown = true;
_guiLoggedNotifications.insert(activity._id);
// Assemble a tray notification for the NEW notification
ConfigFile cfg;
if (cfg.optionalServerNotifications()) {
if (AccountManager::instance()->accounts().count() == 1) {
emit guiLog(activity._subject, "");
} else {
emit guiLog(activity._subject, activity._accName);
}
}
}
_activityModel->addNotificationToActivityList(activity);
}
// restart the gui log timer now that we show a new notification
if (newNotificationShown) {
_guiLogTimer.start();
}
}
void User::setNotificationRefreshInterval(std::chrono::milliseconds interval)
{
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
_notificationCheckTimer.start(interval.count());
}
void User::slotRefreshImmediately() {
if (_account.data() && _account.data()->isConnected()) {
this->slotRefreshActivities();
}
this->slotRefreshNotifications();
}
void User::slotRefresh()
{
// QElapsedTimer isn't actually constructed as invalid.
if (!_timeSinceLastCheck.contains(_account.data())) {
_timeSinceLastCheck[_account.data()].invalidate();
}
QElapsedTimer &timer = _timeSinceLastCheck[_account.data()];
// Fetch Activities only if visible and if last check is longer than 15 secs ago
if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) {
qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000;
return;
}
if (_account.data() && _account.data()->isConnected()) {
if (!timer.isValid()) {
this->slotRefreshActivities();
}
this->slotRefreshNotifications();
timer.start();
}
}
void User::slotRefreshActivities()
{
_activityModel->slotRefreshActivity();
}
void User::slotRefreshNotifications()
{
// start a server notification handler if no notification requests
// are running
if (_notificationRequestsRunning == 0) {
ServerNotificationHandler *snh = new ServerNotificationHandler(_account.data());
connect(snh, &ServerNotificationHandler::newNotificationList,
this, &User::slotBuildNotificationDisplay);
snh->slotFetchNotifications();
} else {
qCWarning(lcActivity) << "Notification request counter not zero.";
}
}
void User::slotRebuildNavigationAppList()
{
// Rebuild App list
UserAppsModel::instance()->buildAppList();
}
void User::slotNotificationRequestFinished(int statusCode)
{
int row = sender()->property("activityRow").toInt();
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible.";
} else {
// to do use the model to rebuild the list or remove the item
qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list.";
_activityModel->removeActivityFromActivityList(row);
}
}
void User::slotEndNotificationRequest(int replyCode)
{
_notificationRequestsRunning--;
slotNotificationRequestFinished(replyCode);
}
void User::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
{
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
const QStringList validVerbs = QStringList() << "GET"
<< "PUT"
<< "POST"
<< "DELETE";
if (validVerbs.contains(verb)) {
AccountStatePtr acc = AccountManager::instance()->account(accountName);
if (acc) {
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
QUrl l(link);
job->setLinkAndVerb(l, verb);
job->setProperty("activityRow", QVariant::fromValue(row));
connect(job, &AbstractNetworkJob::networkError,
this, &User::slotNotifyNetworkError);
connect(job, &NotificationConfirmJob::jobFinished,
this, &User::slotNotifyServerFinished);
job->start();
// count the number of running notification requests. If this member var
// is larger than zero, no new fetching of notifications is started
_notificationRequestsRunning++;
}
} else {
qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb;
}
}
void User::slotNotifyNetworkError(QNetworkReply *reply)
{
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
if (!job) {
return;
}
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
slotEndNotificationRequest(resultCode);
qCWarning(lcActivity) << "Server notify job failed with code " << resultCode;
}
void User::slotNotifyServerFinished(const QString &reply, int replyCode)
{
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
if (!job) {
return;
}
slotEndNotificationRequest(replyCode);
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
}
void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
{
if (progress.status() == ProgressInfo::Reconcile) {
// Wipe all non-persistent entries - as well as the persistent ones
// in cases where a local discovery was done.
auto f = FolderMan::instance()->folder(folder);
if (!f)
return;
const auto &engine = f->syncEngine();
const auto style = engine.lastLocalDiscoveryStyle();
foreach (Activity activity, _activityModel->errorsList()) {
if (activity._folder != folder) {
continue;
}
if (style == LocalDiscoveryStyle::FilesystemOnly) {
_activityModel->removeActivityFromActivityList(activity);
continue;
}
if (activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()) {
_activityModel->removeActivityFromActivityList(activity);
continue;
}
if (activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()) {
_activityModel->removeActivityFromActivityList(activity);
continue;
}
if (activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) {
_activityModel->removeActivityFromActivityList(activity);
continue;
}
if (!QFileInfo(f->path() + activity._file).exists()) {
_activityModel->removeActivityFromActivityList(activity);
continue;
}
auto path = QFileInfo(activity._file).dir().path().toUtf8();
if (path == ".")
path.clear();
if (engine.shouldDiscoverLocally(path))
_activityModel->removeActivityFromActivityList(activity);
}
}
if (progress.status() == ProgressInfo::Done) {
// We keep track very well of pending conflicts.
// Inform other components about them.
QStringList conflicts;
foreach (Activity activity, _activityModel->errorsList()) {
if (activity._folder == folder
&& activity._status == SyncFileItem::Conflict) {
conflicts.append(activity._file);
}
}
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
}
}
void User::slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category)
{
auto folderInstance = FolderMan::instance()->folder(folderAlias);
if (!folderInstance)
return;
if (folderInstance->accountState() == _account.data()) {
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message;
Activity activity;
activity._type = Activity::SyncResultType;
activity._status = SyncResult::Error;
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
activity._subject = message;
activity._message = folderInstance->shortGuiLocalPath();
activity._link = folderInstance->shortGuiLocalPath();
activity._accName = folderInstance->accountState()->account()->displayName();
activity._folder = folderAlias;
if (category == ErrorCategory::InsufficientRemoteStorage) {
ActivityLink link;
link._label = tr("Retry all uploads");
link._link = folderInstance->path();
link._verb = "";
link._isPrimary = true;
activity._links.append(link);
}
// add 'other errors' to activity list
_activityModel->addErrorToActivityList(activity);
}
}
void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
{
auto folderInstance = FolderMan::instance()->folder(folder);
if (!folderInstance)
return;
// check if we are adding it to the right account and if it is useful information (protocol errors)
if (folderInstance->accountState() == _account.data()) {
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString;
Activity activity;
activity._type = Activity::SyncFileItemType; //client activity
activity._status = item->_status;
activity._dateTime = QDateTime::currentDateTime();
activity._message = item->_originalFile;
activity._link = folderInstance->accountState()->account()->url();
activity._accName = folderInstance->accountState()->account()->displayName();
activity._file = item->_file;
activity._folder = folder;
activity._fileAction = "";
if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
activity._fileAction = "file_deleted";
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
activity._fileAction = "file_created";
} else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) {
activity._fileAction = "file_renamed";
} else {
activity._fileAction = "file_changed";
}
if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) {
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
if (activity._fileAction == "file_renamed") {
activity._message.prepend(tr("You renamed") + " ");
} else if (activity._fileAction == "file_deleted") {
activity._message.prepend(tr("You deleted") + " ");
} else if (activity._fileAction == "file_created") {
activity._message.prepend(tr("You created") + " ");
} else {
activity._message.prepend(tr("You changed") + " ");
}
_activityModel->addSyncFileItemToActivityList(activity);
} else {
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
activity._subject = item->_errorString;
if (item->_status == SyncFileItem::Status::FileIgnored) {
_activityModel->addIgnoredFileToList(activity);
} else {
// add 'protocol error' to activity list
_activityModel->addErrorToActivityList(activity);
}
}
}
}
AccountPtr User::account() const
{
return _account->account();
}
void User::setCurrentUser(const bool &isCurrent)
{
_isCurrentUser = isCurrent;
}
Folder *User::getFolder()
{
foreach (Folder *folder, FolderMan::instance()->map()) {
if (folder->accountState() == _account.data()) {
return folder;
}
}
}
ActivityListModel *User::getActivityModel()
{
return _activityModel;
}
void User::openLocalFolder()
{
#ifdef Q_OS_WIN
QString path = "file:///" + this->getFolder()->path();
#else
QString path = "file://" + this->getFolder()->path();
#endif
QDesktopServices::openUrl(path);
}
void User::login() const
{
_account->account()->resetRejectedCertificates();
_account->signIn();
}
void User::logout() const
{
_account->signOutByUi();
}
QString User::name() const
{
// If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username
QString name = _account->account()->davDisplayName();
if (name == "") {
name = _account->account()->credentials()->user();
}
return name;
}
QString User::server(bool shortened) const
{
QString serverUrl = _account->account()->url().toString();
if (shortened) {
serverUrl.replace(QLatin1String("https://"), QLatin1String(""));
serverUrl.replace(QLatin1String("http://"), QLatin1String(""));
}
return serverUrl;
}
QImage User::avatar(bool whiteBg) const
{
QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar());
if (img.isNull()) {
QImage image(128, 128, QImage::Format_ARGB32);
image.fill(Qt::GlobalColor::transparent);
QPainter painter(&image);
QSvgRenderer renderer(QString(whiteBg ? ":/client/theme/black/user.svg" : ":/client/theme/white/user.svg"));
renderer.render(&painter);
return image;
} else {
return img;
}
}
bool User::serverHasTalk() const
{
return _account->hasTalk();
}
bool User::hasActivities() const
{
return _account->account()->capabilities().hasActivities();
}
AccountAppList User::appList() const
{
return _account->appList();
}
bool User::isCurrentUser() const
{
return _isCurrentUser;
}
bool User::isConnected() const
{
return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected);
}
void User::removeAccount() const
{
AccountManager::instance()->deleteAccount(_account.data());
AccountManager::instance()->save();
}
/*-------------------------------------------------------------------------------------*/
UserModel *UserModel::_instance = nullptr;
UserModel *UserModel::instance()
{
if (_instance == nullptr) {
_instance = new UserModel();
}
return _instance;
}
UserModel::UserModel(QObject *parent)
: QAbstractListModel(parent)
, _currentUserId()
{
// TODO: Remember selected user from last quit via settings file
if (AccountManager::instance()->accounts().size() > 0) {
buildUserList();
}
connect(AccountManager::instance(), &AccountManager::accountAdded,
this, &UserModel::buildUserList);
}
void UserModel::buildUserList()
{
for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) {
auto user = AccountManager::instance()->accounts().at(i);
addUser(user);
}
if (_init) {
_users.first()->setCurrentUser(true);
_init = false;
}
}
Q_INVOKABLE int UserModel::numUsers()
{
return _users.size();
}
Q_INVOKABLE int UserModel::currentUserId()
{
return _currentUserId;
}
Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
{
if (!_users.isEmpty()) {
return _users[id]->isConnected();
} else {
return false;
}
}
Q_INVOKABLE QImage UserModel::currentUserAvatar()
{
if (_users.count() >= 1) {
return _users[_currentUserId]->avatar();
} else {
QImage image(128, 128, QImage::Format_ARGB32);
image.fill(Qt::GlobalColor::transparent);
QPainter painter(&image);
QSvgRenderer renderer(QString(":/client/theme/white/user.svg"));
renderer.render(&painter);
return image;
}
}
QImage UserModel::avatarById(const int &id)
{
return _users[id]->avatar(true);
}
Q_INVOKABLE QString UserModel::currentUserName()
{
if (_users.count() >= 1) {
return _users[_currentUserId]->name();
} else {
return QString("No users");
}
}
Q_INVOKABLE QString UserModel::currentUserServer()
{
if (_users.count() >= 1) {
return _users[_currentUserId]->server();
} else {
return QString("");
}
}
Q_INVOKABLE bool UserModel::currentServerHasTalk()
{
if (_users.count() >= 1) {
return _users[_currentUserId]->serverHasTalk();
} else {
return false;
}
}
void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
{
bool containsUser = false;
for (int i = 0; i < _users.size(); i++) {
if (_users[i]->account() == user->account()) {
containsUser = true;
continue;
}
}
if (!containsUser) {
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_users << new User(user, isCurrent);
if (isCurrent) {
_currentUserId = _users.indexOf(_users.last());
}
endInsertRows();
ConfigFile cfg;
_users.last()->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
emit newUserSelected();
}
}
int UserModel::currentUserIndex()
{
return _currentUserId;
}
Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder()
{
_users[_currentUserId]->openLocalFolder();
}
Q_INVOKABLE void UserModel::openCurrentAccountTalk()
{
QString url = _users[_currentUserId]->server(false) + "/apps/spreed";
if (!(url.contains("http://") || url.contains("https://"))) {
url = "https://" + _users[_currentUserId]->server(false) + "/apps/spreed";
}
QDesktopServices::openUrl(QUrl(url));
}
Q_INVOKABLE void UserModel::openCurrentAccountServer()
{
// Don't open this URL when the QML appMenu pops up on click (see Window.qml)
if(appList().count() > 0)
return;
QString url = _users[_currentUserId]->server(false);
if (!(url.contains("http://") || url.contains("https://"))) {
url = "https://" + _users[_currentUserId]->server(false);
}
QDesktopServices::openUrl(QUrl(url));
}
Q_INVOKABLE void UserModel::switchCurrentUser(const int &id)
{
_users[_currentUserId]->setCurrentUser(false);
_users[id]->setCurrentUser(true);
_currentUserId = id;
emit refreshCurrentUserGui();
emit newUserSelected();
}
Q_INVOKABLE void UserModel::login(const int &id)
{
_users[id]->login();
emit refreshCurrentUserGui();
}
Q_INVOKABLE void UserModel::logout(const int &id)
{
_users[id]->logout();
emit refreshCurrentUserGui();
}
Q_INVOKABLE void UserModel::removeAccount(const int &id)
{
QMessageBox messageBox(QMessageBox::Question,
tr("Confirm Account Removal"),
tr("<p>Do you really want to remove the connection to the account <i>%1</i>?</p>"
"<p><b>Note:</b> This will <b>not</b> delete any files.</p>")
.arg(_users[id]->name()),
QMessageBox::NoButton);
QPushButton *yesButton =
messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole);
messageBox.addButton(tr("Cancel"), QMessageBox::NoRole);
messageBox.exec();
if (messageBox.clickedButton() != yesButton) {
return;
}
if (_users[id]->isCurrentUser() && _users.count() > 1) {
id == 0 ? switchCurrentUser(1) : switchCurrentUser(0);
}
_users[id]->logout();
_users[id]->removeAccount();
beginRemoveRows(QModelIndex(), id, id);
_users.removeAt(id);
endRemoveRows();
emit refreshCurrentUserGui();
}
int UserModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return _users.count();
}
QVariant UserModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= _users.count()) {
return QVariant();
}
if (role == NameRole) {
return _users[index.row()]->name();
} else if (role == ServerRole) {
return _users[index.row()]->server();
} else if (role == AvatarRole) {
return _users[index.row()]->avatar();
} else if (role == IsCurrentUserRole) {
return _users[index.row()]->isCurrentUser();
} else if (role == IsConnectedRole) {
return _users[index.row()]->isConnected();
} else if (role == IdRole) {
return index.row();
}
return QVariant();
}
QHash<int, QByteArray> UserModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ServerRole] = "server";
roles[AvatarRole] = "avatar";
roles[IsCurrentUserRole] = "isCurrentUser";
roles[IsConnectedRole] = "isConnected";
roles[IdRole] = "id";
return roles;
}
ActivityListModel *UserModel::currentActivityModel()
{
return _users[currentUserIndex()]->getActivityModel();
}
bool UserModel::currentUserHasActivities()
{
return _users[currentUserIndex()]->hasActivities();
}
void UserModel::fetchCurrentActivityModel()
{
_users[currentUserId()]->slotRefresh();
}
AccountAppList UserModel::appList() const
{
if (_users.count() > 0) {
return _users[_currentUserId]->appList();
} else {
return AccountAppList();
}
}
/*-------------------------------------------------------------------------------------*/
ImageProvider::ImageProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
Q_UNUSED(size)
Q_UNUSED(requestedSize)
if (id == "currentUser") {
return UserModel::instance()->currentUserAvatar();
} else {
int uid = id.toInt();
return UserModel::instance()->avatarById(uid);
}
}
/*-------------------------------------------------------------------------------------*/
UserAppsModel *UserAppsModel::_instance = nullptr;
UserAppsModel *UserAppsModel::instance()
{
if (_instance == nullptr) {
_instance = new UserAppsModel();
}
return _instance;
}
UserAppsModel::UserAppsModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void UserAppsModel::buildAppList()
{
if (rowCount() > 0) {
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
_apps.clear();
endRemoveRows();
}
if(UserModel::instance()->appList().count() > 0) {
foreach(AccountApp *app, UserModel::instance()->appList()) {
// Filter out Talk because we have a dedicated button for it
if(app->id() == QLatin1String("spreed"))
continue;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
_apps << app;
endInsertRows();
}
}
}
void UserAppsModel::openAppUrl(const QUrl &url)
{
QDesktopServices::openUrl(url);
}
int UserAppsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return _apps.count();
}
QVariant UserAppsModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= _apps.count()) {
return QVariant();
}
if (role == NameRole) {
return _apps[index.row()]->name();
} else if (role == UrlRole) {
return _apps[index.row()]->url();
} else if (role == IconUrlRole) {
return _apps[index.row()]->iconUrl().toString();
}
return QVariant();
}
QHash<int, QByteArray> UserAppsModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "appName";
roles[UrlRole] = "appUrl";
roles[IconUrlRole] = "appIconUrl";
return roles;
}
}

View File

@@ -1,183 +0,0 @@
#ifndef USERMODEL_H
#define USERMODEL_H
#include <QAbstractListModel>
#include <QImage>
#include <QDateTime>
#include <QStringList>
#include <QQuickImageProvider>
#include "ActivityListModel.h"
#include "accountmanager.h"
#include "folderman.h"
#include <chrono>
namespace OCC {
class User : public QObject
{
Q_OBJECT
public:
User(AccountStatePtr &account, const bool &isCurrent = false, QObject* parent = 0);
AccountPtr account() const;
bool isConnected() const;
bool isCurrentUser() const;
void setCurrentUser(const bool &isCurrent);
Folder *getFolder();
ActivityListModel *getActivityModel();
void openLocalFolder();
QString name() const;
QString server(bool shortened = true) const;
bool serverHasTalk() const;
bool hasActivities() const;
AccountAppList appList() const;
QImage avatar(bool whiteBg = false) const;
QString id() const;
void login() const;
void logout() const;
void removeAccount() const;
signals:
void guiLog(const QString &, const QString &);
public slots:
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category);
void slotNotificationRequestFinished(int statusCode);
void slotNotifyNetworkError(QNetworkReply *reply);
void slotEndNotificationRequest(int replyCode);
void slotNotifyServerFinished(const QString &reply, int replyCode);
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
void slotBuildNotificationDisplay(const ActivityList &list);
void slotRefreshNotifications();
void slotRefreshActivities();
void slotRefresh();
void slotRefreshImmediately();
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
void slotRebuildNavigationAppList();
private:
AccountStatePtr _account;
bool _isCurrentUser;
ActivityListModel *_activityModel;
ActivityList _blacklistedNotifications;
QTimer _notificationCheckTimer;
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
QElapsedTimer _guiLogTimer;
QSet<int> _guiLoggedNotifications;
// number of currently running notification requests. If non zero,
// no query for notifications is started.
int _notificationRequestsRunning;
};
class UserModel : public QAbstractListModel
{
Q_OBJECT
public:
static UserModel *instance();
virtual ~UserModel() {};
void addUser(AccountStatePtr &user, const bool &isCurrent = false);
int currentUserIndex();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QImage avatarById(const int &id);
Q_INVOKABLE void fetchCurrentActivityModel();
Q_INVOKABLE void openCurrentAccountLocalFolder();
Q_INVOKABLE void openCurrentAccountTalk();
Q_INVOKABLE void openCurrentAccountServer();
Q_INVOKABLE QImage currentUserAvatar();
Q_INVOKABLE int numUsers();
Q_INVOKABLE QString currentUserName();
Q_INVOKABLE QString currentUserServer();
Q_INVOKABLE bool currentUserHasActivities();
Q_INVOKABLE bool currentServerHasTalk();
Q_INVOKABLE int currentUserId();
Q_INVOKABLE bool isUserConnected(const int &id);
Q_INVOKABLE void switchCurrentUser(const int &id);
Q_INVOKABLE void login(const int &id);
Q_INVOKABLE void logout(const int &id);
Q_INVOKABLE void removeAccount(const int &id);
ActivityListModel *currentActivityModel();
enum UserRoles {
NameRole = Qt::UserRole + 1,
ServerRole,
AvatarRole,
IsCurrentUserRole,
IsConnectedRole,
IdRole
};
AccountAppList appList() const;
signals:
Q_INVOKABLE void addAccount();
Q_INVOKABLE void refreshCurrentUserGui();
Q_INVOKABLE void newUserSelected();
protected:
QHash<int, QByteArray> roleNames() const override;
private:
static UserModel *_instance;
UserModel(QObject *parent = 0);
QList<User*> _users;
int _currentUserId;
bool _init = true;
void buildUserList();
};
class ImageProvider : public QQuickImageProvider
{
public:
ImageProvider();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
};
class UserAppsModel : public QAbstractListModel
{
Q_OBJECT
public:
static UserAppsModel *instance();
virtual ~UserAppsModel() {};
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
enum UserAppsRoles {
NameRole = Qt::UserRole + 1,
UrlRole,
IconUrlRole
};
void buildAppList();
public slots:
void openAppUrl(const QUrl &url);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
static UserAppsModel *_instance;
UserAppsModel(QObject *parent = 0);
AccountAppList _apps;
};
}
#endif // USERMODEL_H

View File

@@ -1,612 +0,0 @@
import QtQml 2.1
import QtQml.Models 2.1
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0
Window {
id: trayWindow
visible: true
width: 400
height: 510
color: "transparent"
flags: Qt.FramelessWindowHint
onActiveChanged: {
if(!active) {
trayWindow.hide();
systrayBackend.setClosed();
}
}
onVisibleChanged: {
currentAccountAvatar.source = ""
currentAccountAvatar.source = "image://avatars/currentUser"
currentAccountUser.text = userModelBackend.currentUserName();
currentAccountServer.text = userModelBackend.currentUserServer();
trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false;
currentAccountStateIndicator.source = ""
currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
userLineInstantiator.active = false;
userLineInstantiator.active = true;
}
Connections {
target: userModelBackend
onRefreshCurrentUserGui: {
currentAccountAvatar.source = ""
currentAccountAvatar.source = "image://avatars/currentUser"
currentAccountUser.text = userModelBackend.currentUserName();
currentAccountServer.text = userModelBackend.currentUserServer();
currentAccountStateIndicator.source = ""
currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
}
onNewUserSelected: {
accountMenu.close();
trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false;
}
}
Connections {
target: systrayBackend
onShowWindow: {
accountMenu.close();
trayWindow.show();
trayWindow.raise();
trayWindow.requestActivate();
trayWindow.setX( systrayBackend.calcTrayWindowX());
trayWindow.setY( systrayBackend.calcTrayWindowY());
systrayBackend.setOpened();
userModelBackend.fetchCurrentActivityModel();
}
onHideWindow: {
trayWindow.hide();
systrayBackend.setClosed();
}
}
Rectangle {
id: trayWindowBackground
anchors.fill: parent
radius: 10
border.color: "#0082c9"
Rectangle {
id: trayWindowHeaderBackground
anchors.left: trayWindowBackground.left
anchors.top: trayWindowBackground.top
height: 60
width: parent.width
radius: 9
color: "#0082c9"
Rectangle {
anchors.left: trayWindowHeaderBackground.left
anchors.bottom: trayWindowHeaderBackground.bottom
height: 30
width: parent.width
color: "#0082c9"
}
RowLayout {
id: trayWindowHeaderLayout
spacing: 0
anchors.fill: parent
Button {
id: currentAccountButton
Layout.preferredWidth: 220
Layout.preferredHeight: (trayWindowHeaderBackground.height)
display: AbstractButton.IconOnly
flat: true
MouseArea {
id: accountBtnMouseArea
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
currentAccountStateIndicatorBackground.color = (containsMouse ? "#009dd9" : "#0082c9")
}
onClicked:
{
syncPauseButton.text = systrayBackend.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
accountMenu.open()
}
Menu {
id: accountMenu
x: (currentAccountButton.x + 2)
y: (currentAccountButton.y + currentAccountButton.height + 2)
width: (currentAccountButton.width - 2)
closePolicy: "CloseOnPressOutside"
background: Rectangle {
border.color: "#0082c9"
radius: 2
}
onClosed: {
userLineInstantiator.active = false;
userLineInstantiator.active = true;
}
Instantiator {
id: userLineInstantiator
model: userModelBackend
delegate: UserLine {}
onObjectAdded: accountMenu.insertItem(index, object)
onObjectRemoved: accountMenu.removeItem(object)
}
MenuItem {
id: addAccountButton
height: 50
RowLayout {
width: addAccountButton.width
height: addAccountButton.height
spacing: 0
Image {
Layout.leftMargin: 14
verticalAlignment: Qt.AlignCenter
source: "qrc:///client/theme/black/add.svg"
sourceSize.width: openLocalFolderButton.icon.width
sourceSize.height: openLocalFolderButton.icon.height
}
Label {
Layout.leftMargin: 14
text: qsTr("Add account")
color: "black"
font.pixelSize: 12
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
onClicked: userModelBackend.addAccount()
}
MenuSeparator { id: accountMenuSeparator }
MenuItem {
id: syncPauseButton
font.pixelSize: 12
onClicked: systrayBackend.pauseResumeSync()
}
MenuItem {
text: qsTr("Open settings")
font.pixelSize: 12
onClicked: systrayBackend.openSettings()
}
MenuItem {
text: qsTr("Help")
font.pixelSize: 12
onClicked: systrayBackend.openHelp()
}
MenuItem {
text: qsTr("Quit Nextcloud")
font.pixelSize: 12
onClicked: systrayBackend.shutdown()
}
}
}
background:
Item {
id: leftHoverContainer
height: currentAccountButton.height
width: currentAccountButton.width
Rectangle {
width: currentAccountButton.width / 2
height: currentAccountButton.height / 2
color: "transparent"
clip: true
Rectangle {
width: currentAccountButton.width
height: currentAccountButton.height
radius: 10
color: "white"
opacity: 0.2
visible: accountBtnMouseArea.containsMouse
}
}
Rectangle {
width: currentAccountButton.width / 2
height: currentAccountButton.height / 2
anchors.bottom: leftHoverContainer.bottom
color: "white"
opacity: 0.2
visible: accountBtnMouseArea.containsMouse
}
Rectangle {
width: currentAccountButton.width / 2
height: currentAccountButton.height / 2
anchors.right: leftHoverContainer.right
color: "white"
opacity: 0.2
visible: accountBtnMouseArea.containsMouse
}
Rectangle {
width: currentAccountButton.width / 2
height: currentAccountButton.height / 2
anchors.right: leftHoverContainer.right
anchors.bottom: leftHoverContainer.bottom
color: "white"
opacity: 0.2
visible: accountBtnMouseArea.containsMouse
}
}
RowLayout {
id: accountControlRowLayout
height: currentAccountButton.height
width: currentAccountButton.width
spacing: 0
Image {
id: currentAccountAvatar
Layout.leftMargin: 8
verticalAlignment: Qt.AlignCenter
cache: false
source: "image://avatars/currentUser"
Layout.preferredHeight: (trayWindowHeaderBackground.height -16)
Layout.preferredWidth: (trayWindowHeaderBackground.height -16)
Rectangle {
id: currentAccountStateIndicatorBackground
width: currentAccountStateIndicator.sourceSize.width + 2
height: width
anchors.bottom: currentAccountAvatar.bottom
anchors.right: currentAccountAvatar.right
color: "#0082c9"
radius: width*0.5
}
Image {
id: currentAccountStateIndicator
source: userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
cache: false
x: currentAccountStateIndicatorBackground.x + 1
y: currentAccountStateIndicatorBackground.y + 1
sourceSize.width: 16
sourceSize.height: 16
}
}
Column {
id: accountLabels
spacing: 4
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 6
Label {
id: currentAccountUser
width: 128
text: userModelBackend.currentUserName()
elide: Text.ElideRight
color: "white"
font.pixelSize: 12
font.bold: true
}
Label {
id: currentAccountServer
width: 128
text: userModelBackend.currentUserServer()
elide: Text.ElideRight
color: "white"
font.pixelSize: 10
}
}
Image {
Layout.alignment: Qt.AlignRight
verticalAlignment: Qt.AlignCenter
Layout.margins: 8
source: "qrc:///client/theme/white/caret-down.svg"
sourceSize.width: 20
sourceSize.height: 20
}
}
}
Item {
id: trayWindowHeaderSpacer
Layout.fillWidth: true
}
Button {
id: openLocalFolderButton
Layout.alignment: Qt.AlignRight
display: AbstractButton.IconOnly
Layout.preferredWidth: (trayWindowHeaderBackground.height)
Layout.preferredHeight: (trayWindowHeaderBackground.height)
flat: true
icon.source: "qrc:///client/theme/white/folder.svg"
icon.color: "transparent"
MouseArea {
id: folderBtnMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked:
{
userModelBackend.openCurrentAccountLocalFolder();
}
}
background:
Rectangle {
color: folderBtnMouseArea.containsMouse ? "white" : "transparent"
opacity: 0.2
}
}
Button {
id: trayWindowTalkButton
Layout.alignment: Qt.AlignRight
display: AbstractButton.IconOnly
Layout.preferredWidth: (trayWindowHeaderBackground.height)
Layout.preferredHeight: (trayWindowHeaderBackground.height)
flat: true
visible: userModelBackend.currentServerHasTalk() ? true : false
icon.source: "qrc:///client/theme/white/talk-app.svg"
icon.color: "transparent"
MouseArea {
id: talkBtnMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked:
{
userModelBackend.openCurrentAccountTalk();
}
}
background:
Rectangle {
color: talkBtnMouseArea.containsMouse ? "white" : "transparent"
opacity: 0.2
}
}
Button {
id: trayWindowAppsButton
Layout.alignment: Qt.AlignRight
display: AbstractButton.IconOnly
Layout.preferredWidth: (trayWindowHeaderBackground.height)
Layout.preferredHeight: (trayWindowHeaderBackground.height)
flat: true
icon.source: "qrc:///client/theme/white/more-apps.svg"
icon.color: "transparent"
MouseArea {
id: appsBtnMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked:
{
/*
// The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10)
// so we handle this with userModelBackend.openCurrentAccountServer()
//
// See UserModel::openCurrentAccountServer() to disable this workaround
// in the future for Qt >= 5.10
if(appsMenu.count() > 0) {
appsMenu.popup();
} else {
userModelBackend.openCurrentAccountServer();
}
*/
appsMenu.open();
userModelBackend.openCurrentAccountServer();
}
Menu {
id: appsMenu
y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2)
width: (trayWindowAppsButton.width * 3)
closePolicy: "CloseOnPressOutside"
background: Rectangle {
border.color: "#0082c9"
radius: 2
}
Instantiator {
id: appsMenuInstantiator
model: appsMenuModelBackend
onObjectAdded: appsMenu.insertItem(index, object)
onObjectRemoved: appsMenu.removeItem(object)
delegate: MenuItem {
text: appName
font.pixelSize: 12
icon.source: appIconUrl
onTriggered: appsMenuModelBackend.openAppUrl(appUrl)
}
}
}
}
background:
Item {
id: rightHoverContainer
height: trayWindowAppsButton.height
width: trayWindowAppsButton.width
Rectangle {
width: trayWindowAppsButton.width / 2
height: trayWindowAppsButton.height / 2
color: "white"
opacity: 0.2
visible: appsBtnMouseArea.containsMouse
}
Rectangle {
width: trayWindowAppsButton.width / 2
height: trayWindowAppsButton.height / 2
anchors.bottom: rightHoverContainer.bottom
color: "white"
opacity: 0.2
visible: appsBtnMouseArea.containsMouse
}
Rectangle {
width: trayWindowAppsButton.width / 2
height: trayWindowAppsButton.height / 2
anchors.bottom: rightHoverContainer.bottom
anchors.right: rightHoverContainer.right
color: "white"
opacity: 0.2
visible: appsBtnMouseArea.containsMouse
}
Rectangle {
id: rightHoverContainerClipper
anchors.right: rightHoverContainer.right
width: trayWindowAppsButton.width / 2
height: trayWindowAppsButton.height / 2
color: "transparent"
clip: true
Rectangle {
width: trayWindowAppsButton.width
height: trayWindowAppsButton.height
anchors.right: rightHoverContainerClipper.right
radius: 10
color: "white"
opacity: 0.2
visible: appsBtnMouseArea.containsMouse
}
}
}
}
}
} // Rectangle trayWindowHeaderBackground
ListView {
id: activityListView
anchors.top: trayWindowHeaderBackground.bottom
width: trayWindowBackground.width
height: trayWindowBackground.height - trayWindowHeaderBackground.height
clip: true
ScrollBar.vertical: ScrollBar {
id: listViewScrollbar
}
model: activityModel
delegate: RowLayout {
id: activityItem
width: activityListView.width
height: trayWindowHeaderLayout.height
spacing: 0
Image {
id: activityIcon
Layout.leftMargin: 8
Layout.rightMargin: 8
Layout.preferredWidth: activityButton1.icon.width
Layout.preferredHeight: activityButton1.icon.height
verticalAlignment: Qt.AlignCenter
cache: true
source: icon
sourceSize.height: 64
sourceSize.width: 64
}
Column {
id: activityTextColumn
spacing: 4
Layout.alignment: Qt.AlignLeft
Text {
id: activityTextTitle
text: (type === "Activity" || type === "Notification") ? subject : message
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
elide: Text.ElideRight
font.pixelSize: 12
color: activityTextTitleColor
}
Text {
id: activityTextInfo
text: (type === "Activity" || type === "File" || type === "Sync") ? displaypath : message
height: (text === "") ? 0 : activityTextTitle.height
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
elide: Text.ElideRight
font.pixelSize: 10
}
Text {
id: activityTextDateTime
text: dateTime
height: (text === "") ? 0 : activityTextTitle.height
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
elide: Text.ElideRight
font.pixelSize: 10
color: "#808080"
}
}
Item {
id: activityItemFiller
Layout.fillWidth: true
}
Button {
id: activityButton1
Layout.preferredWidth: (path === "") ? 0 : activityItem.height
Layout.preferredHeight: activityItem.height
Layout.alignment: Qt.AlignRight
flat: true
hoverEnabled: false
visible: (path === "") ? false : true
display: AbstractButton.IconOnly
icon.source: "qrc:///client/resources/files.svg"
icon.color: "transparent"
onClicked: {
Qt.openUrlExternally(path)
}
}
Button {
id: activityButton2
Layout.preferredWidth: (link === "") ? 0 : activityItem.height
Layout.preferredHeight: activityItem.height
Layout.alignment: Qt.AlignRight
flat: true
hoverEnabled: false
visible: (link === "") ? false : true
display: AbstractButton.IconOnly
icon.source: "qrc:///client/resources/public.svg"
icon.color: "transparent"
onClicked: {
Qt.openUrlExternally(link)
}
}
}
/*add: Transition {
NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear }
}
remove: Transition {
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 }
}
removeDisplaced: Transition {
SequentialAnimation {
PauseAnimation { duration: 100}
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
}
}
displaced: Transition {
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
}*/
}
} // Rectangle trayWindowBackground
}

View File

@@ -84,7 +84,6 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-files.png"), tr("Secure collaboration & file exchange"));
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-groupware.png"), tr("Easy-to-use web mail, calendaring & contacts"));
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-talk.png"), tr("Screensharing, online meetings & web conferences"));
connect(_ui.slideShow, &SlideShow::clicked, _ui.slideShow, &SlideShow::stopShow);
connect(_ui.nextButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::nextSlide);
connect(_ui.prevButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::prevSlide);

View File

@@ -35,9 +35,6 @@
#include <QSslKey>
#include <QAuthenticator>
#include <QStandardPaths>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <keychain.h>
#include "creds/abstractcredentials.h"
@@ -603,49 +600,4 @@ void Account::deleteAppPassword(){
job->start();
}
void Account::fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag)
{
if(directEditingURL.isEmpty() || directEditingETag.isEmpty())
return;
// Check for the directEditing capability
if (!directEditingURL.isEmpty() &&
(directEditingETag.isEmpty() || directEditingETag != _lastDirectEditingETag)) {
// Fetch the available editors and their mime types
JsonApiJob *job = new JsonApiJob(sharedFromThis(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing"), this);
QObject::connect(job, &JsonApiJob::jsonReceived, this, &Account::slotDirectEditingRecieved);
job->start();
}
}
void Account::slotDirectEditingRecieved(const QJsonDocument &json)
{
auto data = json.object().value("ocs").toObject().value("data").toObject();
auto editors = data.value("editors").toObject();
foreach (auto editorKey, editors.keys()) {
auto editor = editors.value(editorKey).toObject();
const QString id = editor.value("id").toString();
const QString name = editor.value("name").toString();
if(!id.isEmpty() && !name.isEmpty()) {
auto mimeTypes = editor.value("mimetypes").toArray();
auto optionalMimeTypes = editor.value("optionalMimetypes").toArray();
DirectEditor *directEditor = new DirectEditor(id, name);
foreach(auto mimeType, mimeTypes) {
directEditor->addMimetype(mimeType.toString().toLatin1());
}
foreach(auto optionalMimeType, optionalMimeTypes) {
directEditor->addOptionalMimetype(optionalMimeType.toString().toLatin1());
}
_capabilities.addDirectEditor(directEditor);
}
}
}
} // namespace OCC

View File

@@ -246,10 +246,6 @@ public:
void writeAppPasswordOnce(QString appPassword);
void deleteAppPassword();
/// Direct Editing
// Check for the directEditing capability
void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);
public slots:
/// Used when forgetting credentials
void clearQNAMCache();
@@ -282,7 +278,6 @@ signals:
protected Q_SLOTS:
void slotCredentialsFetched();
void slotCredentialsAsked();
void slotDirectEditingRecieved(const QJsonDocument &json);
private:
Account(QObject *parent = nullptr);
@@ -329,9 +324,6 @@ private:
friend class AccountManager;
// Direct Editing
QString _lastDirectEditingETag;
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
*

View File

@@ -103,8 +103,7 @@ bool Capabilities::isValid() const
return !_capabilities.isEmpty();
}
bool Capabilities::hasActivities() const
{
bool Capabilities::hasActivities() const {
return _capabilities.contains("activity");
}
@@ -176,85 +175,4 @@ bool Capabilities::uploadConflictFiles() const
return _capabilities["uploadConflictFiles"].toBool();
}
/*-------------------------------------------------------------------------------------*/
// Direct Editing
void Capabilities::addDirectEditor(DirectEditor* directEditor)
{
if(directEditor)
_directEditors.append(directEditor);
}
DirectEditor* Capabilities::getDirectEditorForMimetype(const QMimeType &mimeType)
{
foreach(DirectEditor* editor, _directEditors) {
if(editor->hasMimetype(mimeType))
return editor;
}
return nullptr;
}
DirectEditor* Capabilities::getDirectEditorForOptionalMimetype(const QMimeType &mimeType)
{
foreach(DirectEditor* editor, _directEditors) {
if(editor->hasOptionalMimetype(mimeType))
return editor;
}
return nullptr;
}
/*-------------------------------------------------------------------------------------*/
DirectEditor::DirectEditor(const QString &id, const QString &name, QObject* parent)
: QObject(parent)
, _id(id)
, _name(name)
{
}
QString DirectEditor::id() const
{
return _id;
}
QString DirectEditor::name() const
{
return _name;
}
void DirectEditor::addMimetype(const QByteArray &mimeType)
{
_mimeTypes.append(mimeType);
}
void DirectEditor::addOptionalMimetype(const QByteArray &mimeType)
{
_optionalMimeTypes.append(mimeType);
}
QList<QByteArray> DirectEditor::mimeTypes() const
{
return _mimeTypes;
}
QList<QByteArray> DirectEditor::optionalMimeTypes() const
{
return _optionalMimeTypes;
}
bool DirectEditor::hasMimetype(const QMimeType &mimeType)
{
return _mimeTypes.contains(mimeType.name().toLatin1());
}
bool DirectEditor::hasOptionalMimetype(const QMimeType &mimeType)
{
return _optionalMimeTypes.contains(mimeType.name().toLatin1());
}
/*-------------------------------------------------------------------------------------*/
}

View File

@@ -20,12 +20,9 @@
#include <QVariantMap>
#include <QStringList>
#include <QMimeDatabase>
namespace OCC {
class DirectEditor;
/**
* @brief The Capabilities class represents the capabilities of an ownCloud
* server
@@ -130,47 +127,9 @@ public:
*/
bool uploadConflictFiles() const;
// Direct Editing
void addDirectEditor(DirectEditor* directEditor);
DirectEditor* getDirectEditorForMimetype(const QMimeType &mimeType);
DirectEditor* getDirectEditorForOptionalMimetype(const QMimeType &mimeType);
private:
QVariantMap _capabilities;
QList<DirectEditor*> _directEditors;
};
/*-------------------------------------------------------------------------------------*/
class OWNCLOUDSYNC_EXPORT DirectEditor : public QObject
{
Q_OBJECT
public:
DirectEditor(const QString &id, const QString &name, QObject* parent = 0);
void addMimetype(const QByteArray &mimeType);
void addOptionalMimetype(const QByteArray &mimeType);
bool hasMimetype(const QMimeType &mimeType);
bool hasOptionalMimetype(const QMimeType &mimeType);
QString id() const;
QString name() const;
QList<QByteArray> mimeTypes() const;
QList<QByteArray> optionalMimeTypes() const;
private:
QString _id;
QString _name;
QList<QByteArray> _mimeTypes;
QList<QByteArray> _optionalMimeTypes;
};
/*-------------------------------------------------------------------------------------*/
}
#endif //CAPABILITIES_H

View File

@@ -384,14 +384,7 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
propertyMapToFileStat(map, file_stat.get());
if (file_stat->type == ItemTypeDirectory)
file_stat->size = 0;
if (file_stat->remotePerm.hasPermission(RemotePermissions::IsShared) && file_stat->etag.isEmpty()) {
/* Handle broken shared file error gracefully instead of stopping sync in the desktop client.
DO not set _error */
qCWarning(lcDiscovery)
<< "Missing path to a share :" << file << file_stat->path << file_stat->type << file_stat->size
<< file_stat->modtime << file_stat->remotePerm.toString()
<< file_stat->etag << file_stat->file_id;
} else if (file_stat->type == ItemTypeSkip
if (file_stat->type == ItemTypeSkip
|| file_stat->size == -1
|| file_stat->remotePerm.isNull()
|| file_stat->etag.isEmpty()

View File

@@ -801,7 +801,7 @@ void JsonApiJob::start()
auto query = _additionalParams;
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
sendRequest(_usePOST ? "POST" : "GET", url, _request);
sendRequest("GET", url, _request);
AbstractNetworkJob::start();
}

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