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

Compare commits

...

75 Commits

Author SHA1 Message Date
Michael Schuster
b45f5fd1a9 Bump version to 2.6.4
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-03 20:32:31 +01:00
Nextcloud bot
a4317ca50d [tx-robot] updated from transifex 2020-03-03 19:24:11 +00:00
Michael Schuster
6c6eeab479 Merge pull request #1830 from nextcloud/backport/1829/stable-2.6
[stable-2.6] Fix Explorer pinning: Add fallbacks for Shell commands (fixes #1599)
2020-03-03 20:15:57 +01:00
Christian Kamm
7fdbc72991 Windows: Fix context menu handling only own verbs #7004
Previously it'd handle all verbs as if they were our own.

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-03 19:13:31 +00:00
Michael Schuster
2187c6f4fc Fix Explorer pinning: Add fallbacks for Shell commands (fixes #1599)
See: #1599

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-03 19:13:31 +00:00
Michael Schuster
f3b825f333 Merge pull request #1828 from nextcloud/backport/1827/stable-2.6
[stable-2.6] WebFlowCredentials: Make username comparison case-insensitive (fix #1741)
2020-03-03 06:27:18 +01:00
Michael Schuster
676d4e1308 WebFlowCredentials: Make username comparison case-insensitive (fix #1741)
Fixes issue #1741

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-03 05:26:20 +00:00
Nextcloud bot
e03309a05e [tx-robot] updated from transifex 2020-03-03 03:27:09 +00:00
Bruno Perel
de15f20006 Fix git merge hiccup
(cherry picked from commit 7378ae6a7f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-03 01:39:04 +01:00
Nextcloud bot
7ed1062314 [tx-robot] updated from transifex 2020-03-02 03:25:11 +00:00
Michael Schuster
80138b1cbf Merge pull request #1825 from nextcloud/backport/1823/stable-2.6
[stable-2.6] Disable HTTP/2 for now due to Qt bug, allow enabling it via env var
2020-03-02 03:29:03 +01:00
Michael Schuster
93547ced6d Fix build with older Qt: Disable http2 for now due to Qt bug, add env var (upstream)
Disable http2 for now due to Qt bug but allow enabling it via env var, see: https://github.com/owncloud/client/pull/7620
  and: https://github.com/nextcloud/desktop/pull/1806
Issue: https://github.com/nextcloud/desktop/issues/1503

Co-authored-by: XNG <Milokita@users.noreply.github.com>
Co-authored-by: Hannah von Reth <hannah.vonreth@owncloud.com>

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-02 02:27:55 +00:00
XNG
0aefa58644 Disable http2 for now due to Qt bug
So that user may continue to use http2 on their webpage

Signed-off-by: XNG <Milokita@users.noreply.github.com>
(cherry picked from commit dad95d4e4617211360bf2b4391e29c341e939844)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-02 02:27:55 +00:00
Michael Schuster
992bcf56f5 Merge pull request #1824 from nextcloud/activity-date-time-tooltip
ActivityListModel: Show full date and time as a Tooltip only
2020-03-02 02:56:49 +01:00
Michael Schuster
35de69f7bc ActivityListModel: Show full date and time as a Tooltip only
- Fixes issue #1780
- Limits the visual overhead

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-02 02:47:57 +01:00
Michael Schuster
1c44909254 Merge pull request #1821 from nextcloud/backport/1820/stable-2.6
[stable-2.6] Fix Explorer integration re-save and hide option on non-Windows
2020-03-01 05:45:51 +01:00
Michael Schuster
c3ccb72f2b Fix Explorer integration: Hide option on non-Windows
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-01 04:44:35 +00:00
Michael Schuster
890bd09ce9 Fix Explorer integration re-save (fixes issue #1807)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-03-01 04:44:35 +00:00
Nextcloud bot
b5309c47fc [tx-robot] updated from transifex 2020-03-01 03:27:09 +00:00
Nextcloud bot
d24a09e47f [tx-robot] updated from transifex 2020-02-29 03:26:28 +00:00
Nextcloud bot
bdae4a9be7 [tx-robot] updated from transifex 2020-02-28 03:26:27 +00:00
Nextcloud bot
d9ce9814da [tx-robot] updated from transifex 2020-02-27 03:27:46 +00:00
Nextcloud bot
f6d019af21 [tx-robot] updated from transifex 2020-02-26 03:27:49 +00:00
Nextcloud bot
423ee61817 [tx-robot] updated from transifex 2020-02-25 03:32:56 +00:00
Michael Schuster
4871776a4b Merge pull request #1813 from nextcloud/backport/1810/stable-2.6
[stable-2.6] l10n: Changes to improve source strings
2020-02-24 21:09:18 +01:00
rakekniven
f4e83891e1 l10n: Change spelling of "webdav"
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-24 20:08:10 +00:00
rakekniven
d589b524e0 l10n: Removed colon from translation
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-24 20:08:10 +00:00
rakekniven
de0158e2bb l10n: Removed blank before colon
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-24 20:08:10 +00:00
rakekniven
62856f9001 l10n: Change case of one word
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-24 20:08:10 +00:00
Nextcloud bot
1313bc573f [tx-robot] updated from transifex 2020-02-24 03:29:29 +00:00
Nextcloud bot
c2838fb8a8 [tx-robot] updated from transifex 2020-02-23 03:29:39 +00:00
Nextcloud bot
ba71bf13f4 [tx-robot] updated from transifex 2020-02-22 03:28:55 +00:00
Michael Schuster
7e36c7ba59 Merge pull request #1803 from nextcloud/backport/1802/stable-2.6
[stable-2.6] Updater: Add query-parameter 'updatesegment' to the update check
2020-02-21 23:42:15 +01:00
Michael Schuster
e9641a3b94 Updater: Add query-parameter 'updatesegment' to the update check
Used to throttle down desktop release rollout in order to keep the update servers alive at peak times.

See: https://github.com/nextcloud/client_updater_server/pull/36

Targeted issues: #1795, #1800

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-21 22:40:41 +00:00
Nextcloud bot
7ff1911492 [tx-robot] updated from transifex 2020-02-21 12:43:03 +00:00
Nextcloud bot
57f8b866a5 [tx-robot] updated from transifex 2020-02-21 12:16:08 +00:00
Nextcloud bot
883df2f4fa [tx-robot] updated from transifex 2020-02-21 12:13:24 +00:00
Michael Schuster
5880c4954e Bump version to 2.6.3
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-17 01:59:48 +01:00
Michael Schuster
b02bd066a9 Update translations
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-17 01:55:38 +01:00
Michael Schuster
6abec7cea9 Merge pull request #1789 from nextcloud/backport/1782/stable-2.6
[stable-2.6] Add UserInfo class and fetch quota via API instead of PropfindJob
2020-02-17 01:48:11 +01:00
Michael Schuster
cc4e6b236a Fix Tests linkage (missed UserInfo.cpp in CMakeLists.txt)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-17 00:31:37 +00:00
Michael Schuster
821946ad94 Code cleanup
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-17 00:31:37 +00:00
Michael Schuster
94c3e19ede Add UserInfo class and fetch quota via API instead of PropfindJob
The PropfindJob quota includes the size of shares and thus leads to confusion
in regard of the real space available, as shown in the UI.
This commit aims to streamline the behaviour with the Android and iOS apps,
which also utilize the API.

Details:
- Refactor the QuotaInfo class into UserInfo
- Use JsonApiJob (ocs/v1.php/cloud/user) instead of PropfindJob
- Let ConnectionValidator use the new UserInfo class to fetch
  the user and the avatar image (to avoid code duplication)
- Allow updating the avatar image upon AccountSettings visibility,
  using UserInfo's quota fetching

Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-17 00:31:37 +00:00
rakekniven
feba6910ce Changed product name to Nextcloud
Reported at Transifex.

Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
(cherry picked from commit dfdb872e7b)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-16 19:01:39 +01:00
Michael Schuster
4f37249750 Merge pull request #1787 from nextcloud/backport/1770/stable-2.6
[stable-2.6] l10n: Changed grammar and triple dots to ellipsis
2020-02-16 18:56:55 +01:00
rakekniven
fda8c406f6 l10n: Changed grammar
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-16 17:56:03 +00:00
rakekniven
a804c4650a l10n: Triple dot to ellipsis
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-16 17:56:02 +00:00
rakekniven
e1f4963973 l10n: Triple dot to ellipsis
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-16 17:56:02 +00:00
rakekniven
6ae761c43c Triple dot to ellipsis
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-16 17:56:02 +00:00
rakekniven
504bb34d26 l10n: Triple dot to ellipsis
Signed-off-by: rakekniven <mark.ziegler@rakekniven.de>
2020-02-16 17:56:02 +00:00
rakekniven
1136cee383 l10n: Changed spelling of "user name" to "username"
Using "username" like on > 200 strings over the whole Nextcloud project.

Signed-off-by: rakekniven mark.ziegler@rakekniven.de
(cherry picked from commit 32c2c062c0)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-16 18:51:34 +01:00
Michael Schuster
591d4c812b Merge pull request #1786 from nextcloud/backport/1765/stable-2.6
[stable-2.6] Start the client in background if activated by D-Bus
2020-02-16 18:46:49 +01:00
Corentin Noël
a105e3f758 Start the client in background if activated by D-Bus
The nextcloud client can be started by any other application consuming libcloudproviers.
Make sure that the client won't pop-up if we open the file manager.

Signed-off-by: Corentin Noël <corentin.noel@collabora.com>
2020-02-16 17:46:20 +00:00
Michael Schuster
a88687bfe3 Merge pull request #1785 from nextcloud/backport/1764/stable-2.6
[stable-2.6] Do not install files related to cloud providers under Xenial
2020-02-16 18:43:46 +01:00
István Váradi
879ed544e1 Do not install files related to cloud providers under Xenial
Signed-off-by: István Váradi <Istvan.Varadi@ericsson.com>
2020-02-16 17:42:22 +00:00
Michael Schuster
13aaffc46b Merge pull request #1784 from nextcloud/backport/1760/stable-2.6
[stable-2.6] Update autoupdate.rst
2020-02-16 18:38:00 +01:00
Andre-Schuiki
62fc12fe40 Update autoupdate.rst
Hi, you have the wrong registry path in the documentation? (tested client version: 2.6.0 x64 build: 20190927)
The Nextcloud Client checks the path "HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud GmbH\Nextcloud" not "HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud\Nextcloud" under HKLM.
2020-02-16 17:36:59 +00:00
Michael Schuster
f4543e0c79 Merge pull request #1783 from nextcloud/backport/1729/stable-2.6
[stable-2.6] Install libcloudproviders files by default on debian
2020-02-16 18:19:15 +01:00
Corentin Noël
d35f466773 Install libcloudproviders files by default on debian
Signed-off-by: Corentin Noël <corentin@elementary.io>
2020-02-16 17:18:39 +00:00
XNG
e3cb3b28ff apply http2 qt resend patch from owncloud
Signed-off-by: XNG <Milokita@users.noreply.github.com>
(cherry picked from commit 768cf7e1ae)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-14 17:45:33 +01:00
XNG
36049afbc4 apply http2 qt resend patch from owncloud
Signed-off-by: XNG <Milokita@users.noreply.github.com>
(cherry picked from commit d87a88e39f)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-14 17:45:33 +01:00
XNG
59c165aa1d apply http2 qt resend patch from owncloud
Signed-off-by: XNG <Milokita@users.noreply.github.com>
(cherry picked from commit 314c00a8b7)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-02-14 17:45:30 +01:00
Dominique Fuchs
071b4abeeb Merge pull request #1774 from nextcloud/backport/1763/stable-2.6
[stable-2.6] Make sure that the libcloudprovider integration is using a valid D-Bus path
2020-02-06 07:24:51 +01:00
Corentin Noël
93e04fc72b Make sure that the libcloudprovider integration is using a valid D-Bus path
Set a simple unique identifier per folder to ensure that it is always unique.

Fixes https://github.com/nextcloud/desktop/issues/1704

Signed-off-by: Corentin Noël <corentin@elementary.io>
2020-02-06 06:22:35 +00:00
Michael Schuster
a9915c4b46 Merge pull request #1752 from nextcloud/backport/1745/stable-2.6
[stable-2.6] Use system proxy by default if no config file is present
2020-01-23 18:13:10 +01:00
Julius Härtl
a265ff52e7 Use system proxy by default if no config file is present
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2020-01-23 17:12:33 +00:00
Michael Schuster
85b4965d7f Linux AppImage build script: Use QtKeyChain master
Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit a35aa58943)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:33:26 +01:00
Roeland Jago Douma
963beec760 Windows 7 is out of support
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
(cherry picked from commit a3aab00ca9)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:10:20 +01:00
Brandon
2f812063ac Correct wrong variable
Signed-off-by: Brandon <brandon.yeow@websparks.sg>
(cherry picked from commit d10bc1bb14)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:10:20 +01:00
Brandon
a485120a34 Correct wrong variable
Signed-off-by: Brandon <me@branbit.com>
Signed-off-by: Brandon <brandon.yeow@websparks.sg>
(cherry picked from commit 18a88fcecf)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:10:19 +01:00
ritsute
6337116de7 Handle broken shared file error gracefully
Signed-off-by: Brandon <me@branbit.com>
Signed-off-by: Brandon <brandon.yeow@websparks.sg>
(cherry picked from commit c92f520423)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:10:19 +01:00
JanDragon
a56eb2e95e Welcome to 2020
(cherry picked from commit 7565c547ae)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:09:25 +01:00
Michael Schuster
ac3246f9f2 Fix Explorer integration on Windows and the crash on other systems
- Ensure that the folder integration stays persistent in Explorer,
  the uninstaller removes the folder upon updating the client.
  Recreate all entries upon start. This has the benefit of removing
  old remains of non-working, outdated entries.

- Don't crash on the other systems when the user clicks the option
  button "Show sync folders in Explorer's Navigation Pane".
  Even though the option currently doesn't work on the other platforms,
  crashing is never good...

Signed-off-by: Michael Schuster <michael@schuster.ms>
(cherry picked from commit 8f9101773c)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:09:11 +01:00
Roeland Jago Douma
aa9849c112 Ask for password on password protected link shares
Fixes #1485

This was missed when creating the new share dialog.
Now it pops up with a nice share password dialog to enter for your link
share.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
(cherry picked from commit 05083e32c9)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:08:56 +01:00
JanDragon
a89e49ef84 Updated year in legalnotice.cpp
(cherry picked from commit 4a64e8da83)
Signed-off-by: Michael Schuster <michael@schuster.ms>
2020-01-18 16:08:37 +01:00
112 changed files with 49139 additions and 39837 deletions

View File

@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[cs_CZ]=@NAZEV_IKONY_APLIKACE@
Icon[cs_CZ]=@APPLICATION_ICON_NAME@
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]=@APPLICATION_ICON_NAME@
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
GenericName[de]=Synchronisationsordner
Icon[de_DE]=@APPLICATION_ICON_NAME@
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
GenericName[de_DE]=Synchronisierungsordner

View File

@@ -0,0 +1,204 @@
[Desktop Entry]
Categories=Utility;X-SuSE-SyncUtility;
Type=Application
Exec=@APPLICATION_EXECUTABLE@
Name=@APPLICATION_NAME@ desktop sync client
Comment=@APPLICATION_NAME@ desktop synchronization client
GenericName=Folder Sync
Icon=@APPLICATION_ICON_NAME@
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
X-GNOME-Autostart-Delay=3
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
Icon[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

@@ -0,0 +1,201 @@
[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
Comment[fa]=@ APPLICATION_NAME @ مشتری هماهنگ سازی دسکتاپ

View File

@@ -199,4 +199,6 @@ 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

@@ -198,6 +198,7 @@ X-GNOME-Autostart-Delay=3
# Translations
Icon[zh_TW]=@APPLICATION_ICON_NAME@
Name[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端
Comment[zh_TW]=@APPLICATION_NAME@ 桌面同步客戶端
GenericName[zh_TW]=資料夾同步

View File

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

View File

@@ -18,11 +18,11 @@ if [ $SUFFIX != "master" ]; then
SUFFIX="PR-$SUFFIX"
fi
#QtKeyChain 0.9.1
#QtKeyChain master
cd /build
git clone https://github.com/frankosterfeld/qtkeychain.git
cd qtkeychain
git checkout v0.9.1
git checkout master
mkdir build
cd build
cmake -D CMAKE_INSTALL_PREFIX=/usr ../

View File

@@ -0,0 +1,4 @@
usr/bin
usr/share/applications
usr/share/icons
debian/101-sync-inotify.conf etc/sysctl.d

View File

@@ -1,4 +1,6 @@
usr/bin
usr/share/applications
usr/share/cloud-providers/
usr/share/dbus-1/services/
usr/share/icons
debian/101-sync-inotify.conf etc/sysctl.d

View File

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

View File

@@ -87,7 +87,7 @@ To prevent automatic updates and disallow manual overrides:
1. Edit this Registry key:
``HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud\Nextcloud``
``HKEY_LOCAL_MACHINE\Software\Policies\Nextcloud GmbH\Nextcloud``
2. Add the key ``skipUpdateCheck`` (of type DWORD).

View File

@@ -27,7 +27,7 @@ download page.
System Requirements
----------------------------------
- Windows 7+
- Windows 8.1+
- macOS 10.7+ (**64-bit only**)
- CentOS 6 & 7 (64-bit only)
- Debian 8.0 & 9.0
@@ -36,8 +36,7 @@ System Requirements
- openSUSE Leap 42.2 & 42.3
.. note::
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous `LTS`_.
>>>>>>> b2da03441... update supported linux platforms
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous LTS.
Installation Wizard
-------------------

View File

@@ -1,4 +1,4 @@
[D-BUS Service]
Name=@LIBCLOUDPROVIDERS_DBUS_BUS_NAME@
Exec=@APPLICATION_EXECUTABLE@
Exec=@APPLICATION_EXECUTABLE@ --background

View File

@@ -182,20 +182,38 @@ IFACEMETHODIMP OCContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
std::wstring command;
CMINVOKECOMMANDINFOEX *piciEx = nullptr;
if (pici->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
piciEx = (CMINVOKECOMMANDINFOEX*)pici;
// For the Unicode case, if the high-order word is not zero, the
// command's verb string is in lpcmi->lpVerbW.
if (HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW))
{
command = ((CMINVOKECOMMANDINFOEX *)pici)->lpVerbW;
} else {
if (piciEx
&& (piciEx->fMask & CMIC_MASK_UNICODE)
&& HIWORD(((CMINVOKECOMMANDINFOEX*)pici)->lpVerbW)) {
command = piciEx->lpVerbW;
// Verify that we handle the verb
bool handled = false;
for (auto &item : m_info.menuItems) {
if (item.command == command) {
handled = true;
break;
}
}
if (!handled)
return E_FAIL;
} else if (IS_INTRESOURCE(pici->lpVerb)) {
// If the command cannot be identified through the verb string, then
// check the identifier offset.
auto offset = LOWORD(pici->lpVerb);
if (offset >= m_info.menuItems.size())
return E_FAIL;
command = m_info.menuItems[offset].command;
} else {
return E_FAIL;
}
OCClientInterface::SendRequest(command.data(), m_selectedFiles);

View File

@@ -90,7 +90,7 @@ set(client_SRCS
syncrunfilelog.cpp
systray.cpp
thumbnailjob.cpp
quotainfo.cpp
userinfo.cpp
accountstate.cpp
addcertificatedialog.cpp
authenticationdialog.cpp

View File

@@ -26,7 +26,7 @@
#include "configfile.h"
#include "account.h"
#include "accountstate.h"
#include "quotainfo.h"
#include "userinfo.h"
#include "accountmanager.h"
#include "owncloudsetupwizard.h"
#include "creds/abstractcredentials.h"
@@ -112,7 +112,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
, _ui(new Ui::AccountSettings)
, _wasDisabledBefore(false)
, _accountState(accountState)
, _quotaInfo(accountState)
, _userInfo(accountState, false, true)
, _menuShown(false)
{
_ui->setupUi(this);
@@ -192,7 +192,7 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged);
slotAccountStateChanged();
connect(&_quotaInfo, &QuotaInfo::quotaUpdated,
connect(&_userInfo, &UserInfo::quotaUpdated,
this, &AccountSettings::slotUpdateQuota);
// Connect E2E stuff
@@ -1238,7 +1238,7 @@ void AccountSettings::slotDeleteAccount()
bool AccountSettings::event(QEvent *e)
{
if (e->type() == QEvent::Hide || e->type() == QEvent::Show) {
_quotaInfo.setActive(isVisible());
_userInfo.setActive(isVisible());
}
if (e->type() == QEvent::Show) {
// Expand the folder automatically only if there's only one, see #4283

View File

@@ -22,7 +22,7 @@
#include <QTimer>
#include "folder.h"
#include "quotainfo.h"
#include "userinfo.h"
#include "progressdispatcher.h"
#include "owncloudgui.h"
#include "folderstatusmodel.h"
@@ -142,7 +142,7 @@ private:
QUrl _OCUrl;
bool _wasDisabledBefore;
AccountState *_accountState;
QuotaInfo _quotaInfo;
UserInfo _userInfo;
QAction *_toggleSignInOutAction;
QAction *_addAccountAction;

View File

@@ -208,7 +208,7 @@
<string/>
</property>
<property name="text">
<string>Storage space: ...</string>
<string>Storage space: </string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>

View File

@@ -229,7 +229,7 @@ void AccountState::checkConnectivity()
return;
}
ConnectionValidator *conValidator = new ConnectionValidator(account());
ConnectionValidator *conValidator = new ConnectionValidator(AccountStatePtr(this));
_connectionValidator = conValidator;
connect(conValidator, &ConnectionValidator::connectionResult,
this, &AccountState::slotConnectionValidatorResult);

View File

@@ -139,7 +139,9 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case ActivityItemDelegate::AccountRole:
return a._accName;
case ActivityItemDelegate::PointInTimeRole:
return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime()));
return Utility::timeAgoInWords(a._dateTime.toLocalTime());
case Qt::ToolTipRole:
return a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate);
case ActivityItemDelegate::AccountConnectedRole:
return (ast && ast->isConnected());
default:

View File

@@ -48,7 +48,7 @@
<item>
<widget class="QPushButton" name="pushButtonBrowseCertificate">
<property name="text">
<string>Browse...</string>
<string>Browse</string>
</property>
</widget>
</item>
@@ -57,7 +57,7 @@
<item row="1" column="0">
<widget class="QLabel" name="labelPWDCertificate">
<property name="text">
<string>Certificate password :</string>
<string>Certificate password:</string>
</property>
</widget>
</item>

View File

@@ -53,7 +53,7 @@ bool ClientProxy::isUsingSystemDefault()
return cfg.proxyType() == QNetworkProxy::DefaultProxy;
}
return false;
return true;
}
QString printQNetworkProxy(const QNetworkProxy &proxy)

View File

@@ -52,8 +52,8 @@ void CloudProviderManager::registerSignals()
CloudProviderManager::CloudProviderManager(QObject *parent) : QObject(parent)
{
_map = new QMap<QString, CloudProviderWrapper*>();
QString busName = QString(LIBCLOUDPROVIDERS_DBUS_BUS_NAME);
g_bus_own_name (G_BUS_TYPE_SESSION, busName.toAscii().data(), G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, on_name_acquired, nullptr, this, nullptr);
_folder_index = 0;
g_bus_own_name (G_BUS_TYPE_SESSION, LIBCLOUDPROVIDERS_DBUS_BUS_NAME, G_BUS_NAME_OWNER_FLAGS_NONE, nullptr, on_name_acquired, nullptr, this, nullptr);
}
void CloudProviderManager::slotFolderListChanged(const Folder::Map &folderMap)
@@ -72,7 +72,7 @@ void CloudProviderManager::slotFolderListChanged(const Folder::Map &folderMap)
while (j.hasNext()) {
j.next();
if (!_map->contains(j.key())) {
auto *cpo = new CloudProviderWrapper(this, j.value(), _providerExporter);
auto *cpo = new CloudProviderWrapper(this, j.value(), _folder_index++, _providerExporter);
_map->insert(j.key(), cpo);
}
}

View File

@@ -36,6 +36,7 @@ public slots:
private:
QMap<QString, CloudProviderWrapper*> *_map;
unsigned int _folder_index;
};
#endif // CLOUDPROVIDERMANAGER_H

View File

@@ -33,13 +33,13 @@ using namespace OCC;
GSimpleActionGroup *actionGroup = nullptr;
CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
CloudProviderWrapper::CloudProviderWrapper(QObject *parent, Folder *folder, int folderId, CloudProvidersProviderExporter* cloudprovider) : QObject(parent)
, _folder(folder)
{
GMenuModel *model;
GActionGroup *action_group;
_recentlyChanged = new QList<QPair<QString, QString>>();
QString accountName = QString("Account%1Folder%2").arg(folder->alias(), folder->accountState()->account()->id());
QString accountName = QString("Folder/%1").arg(folderId);
_cloudProvider = CLOUD_PROVIDERS_PROVIDER_EXPORTER(cloudprovider);
_cloudProviderAccount = cloud_providers_account_exporter_new(_cloudProvider, accountName.toUtf8().data());

View File

@@ -38,7 +38,7 @@ class CloudProviderWrapper : public QObject
{
Q_OBJECT
public:
explicit CloudProviderWrapper(QObject *parent = nullptr, Folder *folder = nullptr, CloudProvidersProviderExporter* cloudprovider = nullptr);
explicit CloudProviderWrapper(QObject *parent = nullptr, Folder *folder = nullptr, int folderId = 0, CloudProvidersProviderExporter* cloudprovider = nullptr);
~CloudProviderWrapper();
CloudProvidersAccountExporter* accountExporter();
Folder* folder();

View File

@@ -22,6 +22,8 @@
#include "connectionvalidator.h"
#include "account.h"
#include "accountstate.h"
#include "userinfo.h"
#include "networkjobs.h"
#include "clientproxy.h"
#include <creds/abstractcredentials.h>
@@ -34,9 +36,10 @@ Q_LOGGING_CATEGORY(lcConnectionValidator, "nextcloud.sync.connectionvalidator",
// This makes sure we get tried often enough without "ConnectionValidator already running"
static qint64 timeoutToUseMsec = qMax(1000, ConnectionValidator::DefaultCallingIntervalMsec - 5 * 1000);
ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
ConnectionValidator::ConnectionValidator(AccountStatePtr accountState, QObject *parent)
: QObject(parent)
, _account(account)
, _accountState(accountState)
, _account(accountState->account())
, _isCheckingServerAndAuth(false)
{
}
@@ -44,7 +47,7 @@ ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
void ConnectionValidator::checkServerAndAuth()
{
if (!_account) {
_errors << tr("No ownCloud account configured");
_errors << tr("No Nextcloud account configured");
reportResult(NotConfigured);
return;
}
@@ -265,10 +268,9 @@ void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPt
void ConnectionValidator::fetchUser()
{
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
job->setTimeout(timeoutToUseMsec);
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotUserFetched);
job->start();
UserInfo *userInfo = new UserInfo(_accountState.data(), true, true, this);
QObject::connect(userInfo, &UserInfo::fetchedLastInfo, this, &ConnectionValidator::slotUserFetched);
userInfo->setActive(true);
}
bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
@@ -300,34 +302,22 @@ bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
return true;
}
void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
void ConnectionValidator::slotUserFetched(UserInfo *userInfo)
{
QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
if (!user.isEmpty()) {
_account->setDavUser(user);
}
QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
if (!displayName.isEmpty()) {
_account->setDavDisplayName(displayName);
if(userInfo) {
userInfo->setActive(false);
userInfo->deleteLater();
}
#ifndef TOKEN_AUTH_ONLY
AvatarJob *job = new AvatarJob(_account, _account->davUser(), 128, this);
job->setTimeout(20 * 1000);
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
job->start();
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
_account->e2e()->initialize();
#else
reportResult(Connected);
#endif
}
#ifndef TOKEN_AUTH_ONLY
void ConnectionValidator::slotAvatarImage(const QImage &img)
{
_account->setAvatar(img);
connect(_account->e2e(), &ClientSideEncryption::initializationFinished, this, &ConnectionValidator::reportConnected);
_account->e2e()->initialize();
}
void ConnectionValidator::reportConnected() {
reportResult(Connected);
}

View File

@@ -67,23 +67,20 @@ namespace OCC {
+---------------------------------+
|
fetchUser
PropfindJob
|
+-> slotUserFetched
AvatarJob
|
+-> slotAvatarImage -->
Utilizes the UserInfo class to fetch the user and avatar image
+-----------------------------------+
|
+-> Client Side Encryption Checks --+ --reportResult()
\endcode
*/
class UserInfo;
class ConnectionValidator : public QObject
{
Q_OBJECT
public:
explicit ConnectionValidator(AccountPtr account, QObject *parent = nullptr);
explicit ConnectionValidator(AccountStatePtr accountState, QObject *parent = nullptr);
enum Status {
Undefined,
@@ -125,13 +122,12 @@ protected slots:
void slotAuthSuccess();
void slotCapabilitiesRecieved(const QJsonDocument &);
void slotUserFetched(const QJsonDocument &);
#ifndef TOKEN_AUTH_ONLY
void slotAvatarImage(const QImage &img);
#endif
void slotUserFetched(UserInfo *userInfo);
private:
#ifndef TOKEN_AUTH_ONLY
void reportConnected();
#endif
void reportResult(Status status);
void checkServerCapabilities();
void fetchUser();
@@ -144,6 +140,7 @@ private:
bool setAndCheckServerVersion(const QString &version);
QStringList _errors;
AccountStatePtr _accountState;
AccountPtr _account;
bool _isCheckingServerAndAuth;
};

View File

@@ -183,7 +183,11 @@ void WebFlowCredentials::askFromUser() {
void WebFlowCredentials::slotAskFromUserCredentialsProvided(const QString &user, const QString &pass, const QString &host) {
Q_UNUSED(host)
if (_user != user) {
// Compare the re-entered username case-insensitive and save the new value (avoid breaking the account)
// See issue: https://github.com/nextcloud/desktop/issues/1741
if (QString::compare(_user, user, Qt::CaseInsensitive) == 0) {
_user = user;
} else {
qCInfo(lcWebFlowCredentials()) << "Authed with the wrong user!";
QString msg = tr("Please login with the user: %1")

View File

@@ -41,7 +41,7 @@
<item>
<widget class="QPushButton" name="localFolderChooseBtn">
<property name="text">
<string>&amp;Choose...</string>
<string>&amp;Choose</string>
</property>
</widget>
</item>

View File

@@ -140,7 +140,7 @@
<item row="1" column="1">
<widget class="QPushButton" name="addFolderButton">
<property name="text">
<string>Create Folder</string>
<string>Create folder</string>
</property>
</widget>
</item>

View File

@@ -100,6 +100,9 @@ GeneralSettings::GeneralSettings(QWidget *parent)
if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows10)
#endif
_ui->showInExplorerNavigationPaneCheckBox->setVisible(false);
#else
// Hide on non-Windows
_ui->showInExplorerNavigationPaneCheckBox->setVisible(false);
#endif
/* Set the left contents margin of the layout to zero to make the checkboxes

View File

@@ -24,7 +24,7 @@ LegalNotice::LegalNotice(QDialog *parent)
{
_ui->setupUi(this);
QString notice = tr("<p>Copyright 2017-2019 Nextcloud GmbH<br />"
QString notice = tr("<p>Copyright 2017-2020 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,6 +32,11 @@ 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)
@@ -74,73 +79,78 @@ void NavigationPaneHelper::updateCloudStorageRegistry()
});
#endif
// Then re-save every folder that has a valid navigationPaneClsid to the registry.
// We currently don't distinguish between new and existing CLSIDs, if it's there we just
// save over it. We at least need to update the tile in case we are suddently using multiple accounts.
foreach (Folder *folder, _folderMan->map()) {
if (!folder->navigationPaneClsid().isNull()) {
// If it already exists, unmark it for removal, this is a valid sync root.
entriesToRemove.removeOne(folder->navigationPaneClsid());
// Only save folder entries if the option is enabled.
if (_showInExplorerNavigationPane) {
// Then re-save every folder that has a valid navigationPaneClsid to the registry.
// We currently don't distinguish between new and existing CLSIDs, if it's there we just
// save over it. We at least need to update the tile in case we are suddently using multiple accounts.
foreach (Folder *folder, _folderMan->map()) {
if (!folder->navigationPaneClsid().isNull()) {
// If it already exists, unmark it for removal, this is a valid sync root.
entriesToRemove.removeOne(folder->navigationPaneClsid());
QString clsidStr = folder->navigationPaneClsid().toString();
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr;
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
QString clsidStr = folder->navigationPaneClsid().toString();
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
QString clsidPathWow64 = QString() % "Software\\Classes\\Wow6432Node\\CLSID\\" % clsidStr;
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
QString title = folder->shortGuiRemotePathOrAppName();
// Write the account name in the sidebar only when using more than one account.
if (AccountManager::instance()->accounts().size() > 1)
title = title % " - " % folder->accountState()->account()->displayName();
QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
QString title = folder->shortGuiRemotePathOrAppName();
// Write the account name in the sidebar only when using more than one account.
if (AccountManager::instance()->accounts().size() > 1)
title = title % " - " % folder->accountState()->account()->displayName();
QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
#ifdef Q_OS_WIN
// Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
// Step 1: Add your CLSID and name your extension
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QString(), REG_SZ, title);
// Step 2: Set the image for your icon
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
// Step 3: Add your extension to the Navigation Pane and make it visible
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
// Step 4: Set the location for your extension in the Navigation Pane
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
// Step 5: Provide the dll that hosts your extension.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
// Step 6: Define the instance object
// Indicate that your namespace extension should function like other file folder structures in File Explorer.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
// Step 7: Provide the file system attributes of the target folder
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
// Step 8: Set the path for the sync root
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
// Step 9: Set appropriate shell flags
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
// Step 10: Set the appropriate flags to control your shell behavior
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
// Step 11: Register your extension in the namespace root
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
// Step 12: Hide your extension from the Desktop
Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
// Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
// Step 1: Add your CLSID and name your extension
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QString(), REG_SZ, title);
// Step 2: Set the image for your icon
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
// Step 3: Add your extension to the Navigation Pane and make it visible
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
// Step 4: Set the location for your extension in the Navigation Pane
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
// Step 5: Provide the dll that hosts your extension.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
// Step 6: Define the instance object
// Indicate that your namespace extension should function like other file folder structures in File Explorer.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
// Step 7: Provide the file system attributes of the target folder
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
// Step 8: Set the path for the sync root
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
// Step 9: Set appropriate shell flags
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
// Step 10: Set the appropriate flags to control your shell behavior
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPathWow64 + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
// Step 11: Register your extension in the namespace root
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
// Step 12: Hide your extension from the Desktop
Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
// For us, to later be able to iterate and find our own namespace entries and associated CLSID.
// Use the macro instead of the theme to make sure it matches with the uninstaller.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
// For us, to later be able to iterate and find our own namespace entries and associated CLSID.
// Use the macro instead of the theme to make sure it matches with the uninstaller.
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
#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.
Q_ASSERT(false);
// 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);
#endif
}
}
}

View File

@@ -97,7 +97,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>:</string>
<string notr="true">:</string>
</property>
</widget>
</item>

View File

@@ -404,7 +404,7 @@ void OwncloudSetupWizard::slotAuthError()
// Something else went wrong, maybe the response was 200 but with invalid data.
} else {
errorMsg = tr("There was an invalid response to an authenticated webdav request");
errorMsg = tr("There was an invalid response to an authenticated WebDAV request");
}
// bring wizard to top

View File

@@ -1,121 +0,0 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@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 "quotainfo.h"
#include "account.h"
#include "accountstate.h"
#include "networkjobs.h"
#include "folderman.h"
#include "creds/abstractcredentials.h"
#include <theme.h>
#include <QTimer>
namespace OCC {
namespace {
static const int defaultIntervalT = 30 * 1000;
static const int failIntervalT = 5 * 1000;
}
QuotaInfo::QuotaInfo(AccountState *accountState, QObject *parent)
: QObject(parent)
, _accountState(accountState)
, _lastQuotaTotalBytes(0)
, _lastQuotaUsedBytes(0)
, _active(false)
{
connect(accountState, &AccountState::stateChanged,
this, &QuotaInfo::slotAccountStateChanged);
connect(&_jobRestartTimer, &QTimer::timeout, this, &QuotaInfo::slotCheckQuota);
_jobRestartTimer.setSingleShot(true);
}
void QuotaInfo::setActive(bool active)
{
_active = active;
slotAccountStateChanged();
}
void QuotaInfo::slotAccountStateChanged()
{
if (canGetQuota()) {
auto elapsed = _lastQuotaRecieved.msecsTo(QDateTime::currentDateTime());
if (_lastQuotaRecieved.isNull() || elapsed >= defaultIntervalT) {
slotCheckQuota();
} else {
_jobRestartTimer.start(defaultIntervalT - elapsed);
}
} else {
_jobRestartTimer.stop();
}
}
void QuotaInfo::slotRequestFailed()
{
_lastQuotaTotalBytes = 0;
_lastQuotaUsedBytes = 0;
_jobRestartTimer.start(failIntervalT);
}
bool QuotaInfo::canGetQuota() const
{
if (!_accountState || !_active) {
return false;
}
AccountPtr account = _accountState->account();
return _accountState->isConnected()
&& account->credentials()
&& account->credentials()->ready();
}
QString QuotaInfo::quotaBaseFolder() const
{
return Theme::instance()->quotaBaseFolder();
}
void QuotaInfo::slotCheckQuota()
{
if (!canGetQuota()) {
return;
}
if (_job) {
// The previous job was not finished? Then we cancel it!
_job->deleteLater();
}
AccountPtr account = _accountState->account();
_job = new PropfindJob(account, quotaBaseFolder(), this);
_job->setProperties(QList<QByteArray>() << "quota-available-bytes"
<< "quota-used-bytes");
connect(_job.data(), &PropfindJob::result, this, &QuotaInfo::slotUpdateLastQuota);
connect(_job.data(), &AbstractNetworkJob::networkError, this, &QuotaInfo::slotRequestFailed);
_job->start();
}
void QuotaInfo::slotUpdateLastQuota(const QVariantMap &result)
{
// The server can return fractional bytes (#1374)
// <d:quota-available-bytes>1374532061.2</d:quota-available-bytes>
qint64 avail = result["quota-available-bytes"].toDouble();
_lastQuotaUsedBytes = result["quota-used-bytes"].toDouble();
// negative value of the available quota have special meaning (#3940)
_lastQuotaTotalBytes = avail >= 0 ? _lastQuotaUsedBytes + avail : avail;
emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
_jobRestartTimer.start(defaultIntervalT);
_lastQuotaRecieved = QDateTime::currentDateTime();
}
}

View File

@@ -28,6 +28,7 @@
#include <QFileInfo>
#include <QFileIconProvider>
#include <QInputDialog>
#include <QPointer>
#include <QPushButton>
#include <QFrame>
@@ -137,6 +138,7 @@ 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);
}
}
@@ -303,6 +305,24 @@ 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,6 +64,7 @@ private slots:
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
void slotDeleteShare();
void slotCreateLinkShare();
void slotLinkShareRequiresPassword();
void slotAdjustScrollWidgetSize();
signals:

View File

@@ -33,7 +33,6 @@ namespace Ui {
}
class AbstractCredentials;
class QuotaInfo;
class SyncResult;
class LinkShare;
class Share;

View File

@@ -38,7 +38,6 @@ namespace Ui {
}
class AbstractCredentials;
class QuotaInfo;
class SyncResult;
class Share;
class Sharee;

View File

@@ -56,7 +56,7 @@
<item>
<widget class="QLineEdit" name="shareeLineEdit">
<property name="placeholderText">
<string>Share with users or groups ...</string>
<string>Share with users or groups </string>
</property>
</widget>
</item>

View File

@@ -63,7 +63,7 @@
</sizepolicy>
</property>
<property name="text">
<string>User name</string>
<string>Username</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>

View File

@@ -49,8 +49,10 @@
#include <QLocalSocket>
#include <QStringBuilder>
#include <QMessageBox>
#include <QInputDialog>
#include <QClipboard>
#include <QDesktopServices>
#include <QStandardPaths>
@@ -290,6 +292,12 @@ void SocketApi::slotReadSocket()
int indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
QString argument = line.remove(0, command.length() + 1);
if (indexOfMethod == -1) {
// Fallback: Try upper-case command
functionWithArguments = "command_" + command.toUpper() + "(QString,SocketListener*)";
indexOfMethod = staticMetaObject.indexOfMethod(functionWithArguments);
}
if (indexOfMethod != -1) {
staticMetaObject.method(indexOfMethod).invoke(this, Q_ARG(QString, argument), Q_ARG(SocketListener *, listener));
} else {
@@ -477,6 +485,8 @@ public:
this, &GetOrCreatePublicLinkShare::linkShareCreated);
connect(&_shareManager, &ShareManager::serverError,
this, &GetOrCreatePublicLinkShare::serverError);
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
this, &GetOrCreatePublicLinkShare::passwordRequired);
}
void run()
@@ -512,6 +522,24 @@ 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;
@@ -565,6 +593,24 @@ void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListene
job->run();
}
// Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
#ifdef Q_OS_WIN
void SocketApi::command_COPYASPATH(const QString &localFile, SocketListener *)
{
QApplication::clipboard()->setText(localFile);
}
void SocketApi::command_OPENNEWWINDOW(const QString &localFile, SocketListener *)
{
QDesktopServices::openUrl(QUrl::fromLocalFile(localFile));
}
void SocketApi::command_OPEN(const QString &localFile, SocketListener *socketListener)
{
command_OPENNEWWINDOW(localFile, socketListener);
}
#endif
// Fetches the private link url asynchronously and then calls the target slot
void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
{

View File

@@ -105,6 +105,13 @@ private:
Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
// Windows Shell / Explorer pinning fallbacks, see issue: https://github.com/nextcloud/desktop/issues/1599
#ifdef Q_OS_WIN
Q_INVOKABLE void command_COPYASPATH(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_OPENNEWWINDOW(const QString &localFile, SocketListener *listener);
Q_INVOKABLE void command_OPEN(const QString &localFile, SocketListener *listener);
#endif
// Fetch the private link and call targetFun
void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun);

View File

@@ -25,6 +25,7 @@
#include "version.h"
#include "config.h"
#include "configfile.h"
namespace OCC {
@@ -75,6 +76,11 @@ QUrlQuery Updater::getQueryParams()
// to beta channel
}
// updateSegment (see configfile.h)
ConfigFile cfg;
auto updateSegment = cfg.updateSegment();
query.addQueryItem(QLatin1String("updatesegment"), QString::number(updateSegment));
return query;
}

156
src/gui/userinfo.cpp Normal file
View File

@@ -0,0 +1,156 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "userinfo.h"
#include "account.h"
#include "accountstate.h"
#include "networkjobs.h"
#include "folderman.h"
#include "creds/abstractcredentials.h"
#include <theme.h>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
namespace OCC {
namespace {
static const int defaultIntervalT = 30 * 1000;
static const int failIntervalT = 5 * 1000;
}
UserInfo::UserInfo(AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent)
: QObject(parent)
, _accountState(accountState)
, _allowDisconnectedAccountState(allowDisconnectedAccountState)
, _fetchAvatarImage(fetchAvatarImage)
, _lastQuotaTotalBytes(0)
, _lastQuotaUsedBytes(0)
, _active(false)
{
connect(accountState, &AccountState::stateChanged,
this, &UserInfo::slotAccountStateChanged);
connect(&_jobRestartTimer, &QTimer::timeout, this, &UserInfo::slotFetchInfo);
_jobRestartTimer.setSingleShot(true);
}
void UserInfo::setActive(bool active)
{
_active = active;
slotAccountStateChanged();
}
void UserInfo::slotAccountStateChanged()
{
if (canGetInfo()) {
auto elapsed = _lastInfoReceived.msecsTo(QDateTime::currentDateTime());
if (_lastInfoReceived.isNull() || elapsed >= defaultIntervalT) {
slotFetchInfo();
} else {
_jobRestartTimer.start(defaultIntervalT - elapsed);
}
} else {
_jobRestartTimer.stop();
}
}
void UserInfo::slotRequestFailed()
{
_lastQuotaTotalBytes = 0;
_lastQuotaUsedBytes = 0;
_jobRestartTimer.start(failIntervalT);
}
bool UserInfo::canGetInfo() const
{
if (!_accountState || !_active) {
return false;
}
AccountPtr account = _accountState->account();
return (_accountState->isConnected() || _allowDisconnectedAccountState)
&& account->credentials()
&& account->credentials()->ready();
}
void UserInfo::slotFetchInfo()
{
if (!canGetInfo()) {
return;
}
if (_job) {
// The previous job was not finished? Then we cancel it!
_job->deleteLater();
}
AccountPtr account = _accountState->account();
_job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"), this);
_job->setTimeout(20 * 1000);
connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserInfo::slotUpdateLastInfo);
connect(_job.data(), &AbstractNetworkJob::networkError, this, &UserInfo::slotRequestFailed);
_job->start();
}
void UserInfo::slotUpdateLastInfo(const QJsonDocument &json)
{
auto objData = json.object().value("ocs").toObject().value("data").toObject();
AccountPtr account = _accountState->account();
// User Info
QString user = objData.value("id").toString();
if (!user.isEmpty()) {
account->setDavUser(user);
}
QString displayName = objData.value("display-name").toString();
if (!displayName.isEmpty()) {
account->setDavDisplayName(displayName);
}
// Quota
auto objQuota = objData.value("quota").toObject();
qint64 used = objQuota.value("used").toDouble();
qint64 total = objQuota.value("total").toDouble();
if(_lastInfoReceived.isNull() || _lastQuotaUsedBytes != used || _lastQuotaTotalBytes != total) {
_lastQuotaUsedBytes = used;
_lastQuotaTotalBytes = total;
emit quotaUpdated(_lastQuotaTotalBytes, _lastQuotaUsedBytes);
}
_jobRestartTimer.start(defaultIntervalT);
_lastInfoReceived = QDateTime::currentDateTime();
// Avatar Image
if(_fetchAvatarImage) {
AvatarJob *job = new AvatarJob(account, account->davUser(), 128, this);
job->setTimeout(20 * 1000);
QObject::connect(job, &AvatarJob::avatarPixmap, this, &UserInfo::slotAvatarImage);
job->start();
}
else
emit fetchedLastInfo(this);
}
void UserInfo::slotAvatarImage(const QImage &img)
{
_accountState->account()->setAvatar(img);
emit fetchedLastInfo(this);
}
} // namespace OCC

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
* Copyright (C) by Michael Schuster <michael@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -12,8 +13,8 @@
* for more details.
*/
#ifndef QUOTAINFO_H
#define QUOTAINFO_H
#ifndef USERINFO_H
#define USERINFO_H
#include <QObject>
#include <QPointer>
@@ -23,31 +24,51 @@
namespace OCC {
class AccountState;
class PropfindJob;
class JsonApiJob;
/**
* @brief handles getting the quota to display in the UI
* @brief handles getting the user info and quota to display in the UI
*
* It is typically owned by the AccountSetting page.
*
* The quota is requested if these 3 conditions are met:
* The user info and quota is requested if these 3 conditions are met:
* - This object is active via setActive() (typically if the settings page is visible.)
* - The account is connected.
* - Every 30 seconds (defaultIntervalT) or 5 seconds in case of failure (failIntervalT)
*
* We only request the quota when the UI is visible otherwise this might slow down the server with
* We only request the info when the UI is visible otherwise this might slow down the server with
* too many requests. But we still need to do it every 30 seconds otherwise user complains that the
* quota is not updated fast enough when changed on the server.
*
* If the quota job is not finished within 30 seconds, it is cancelled and another one is started
* If the fetch job is not finished within 30 seconds, it is cancelled and another one is started
*
* Constructor notes:
* - allowDisconnectedAccountState: set to true if you want to ignore AccountState's isConnected() state,
* this is used by ConnectionValidator (prior having a valid AccountState).
* - fetchAvatarImage: set to false if you don't want to fetch the avatar image
*
* @ingroup gui
*/
class QuotaInfo : public QObject
*
* Here follows the state machine
\code{.unparsed}
*---> slotFetchInfo
JsonApiJob (ocs/v1.php/cloud/user)
|
+-> slotUpdateLastInfo
AvatarJob (if _fetchAvatarImage is true)
|
+-> slotAvatarImage -->
+-----------------------------------+
|
+-> Client Side Encryption Checks --+ --reportResult()
\endcode
*/
class UserInfo : public QObject
{
Q_OBJECT
public:
explicit QuotaInfo(OCC::AccountState *accountState, QObject *parent = nullptr);
explicit UserInfo(OCC::AccountState *accountState, bool allowDisconnectedAccountState, bool fetchAvatarImage, QObject *parent = nullptr);
qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; }
qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; }
@@ -60,32 +81,34 @@ public:
void setActive(bool active);
public Q_SLOTS:
void slotCheckQuota();
void slotFetchInfo();
private Q_SLOTS:
void slotUpdateLastQuota(const QVariantMap &);
void slotUpdateLastInfo(const QJsonDocument &json);
void slotAccountStateChanged();
void slotRequestFailed();
void slotAvatarImage(const QImage &img);
Q_SIGNALS:
void quotaUpdated(qint64 total, qint64 used);
void fetchedLastInfo(UserInfo *userInfo);
private:
bool canGetQuota() const;
/// Returns the folder that quota shall be retrieved for
QString quotaBaseFolder() const;
bool canGetInfo() const;
QPointer<AccountState> _accountState;
bool _allowDisconnectedAccountState;
bool _fetchAvatarImage;
qint64 _lastQuotaTotalBytes;
qint64 _lastQuotaUsedBytes;
QTimer _jobRestartTimer;
QDateTime _lastQuotaRecieved; // the time at which the quota was received last
QDateTime _lastInfoReceived; // the time at which the user info and quota was received last
bool _active; // if we should check at regular interval (when the UI is visible)
QPointer<PropfindJob> _job; // the currently running job
QPointer<JsonApiJob> _job; // the currently running job
};
} // namespace OCC
#endif //QUOTAINFO_H
#endif //USERINFO_H

View File

@@ -178,11 +178,11 @@ void OwncloudSetupPage::slotUrlChanged(const QString &url)
if (!url.startsWith(QLatin1String("https://"))) {
_ui.urlLabel->setPixmap(QPixmap(Theme::hidpiFileName(":/client/resources/lock-http.png")));
_ui.urlLabel->setToolTip(tr("This url is NOT secure as it is not encrypted.\n"
_ui.urlLabel->setToolTip(tr("This URL is NOT secure as it is not encrypted.\n"
"It is not advisable to use it."));
} else {
_ui.urlLabel->setPixmap(QPixmap(Theme::hidpiFileName(":/client/resources/lock-https.png")));
_ui.urlLabel->setToolTip(tr("This url is secure. You can use it."));
_ui.urlLabel->setToolTip(tr("This URL is secure. You can use it."));
}
}

View File

@@ -95,8 +95,15 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op,
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 4)
// only enable HTTP2 with Qt 5.9.4 because old Qt have too many bugs (e.g. QTBUG-64359 is fixed in >= Qt 5.9.4)
/* Disable http2 for now due to Qt bug but allow enabling it via env var, see: https://github.com/owncloud/client/pull/7620
* and: https://github.com/nextcloud/desktop/pull/1806
* Issue: https://github.com/nextcloud/desktop/issues/1503
*/
if (newRequest.url().scheme() == "https") { // Not for "http": QTBUG-61397
newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
static const bool http2EnabledEnv = qEnvironmentVariableIntValue("OWNCLOUD_HTTP2_ENABLED") == 1;
newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, http2EnabledEnv);
}
#endif

View File

@@ -52,7 +52,6 @@ namespace OCC {
class AbstractCredentials;
class Account;
typedef QSharedPointer<Account> AccountPtr;
class QuotaInfo;
class AccessManager;
class SimpleNetworkJob;
@@ -306,7 +305,6 @@ private:
Capabilities _capabilities;
QString _serverVersion;
QScopedPointer<AbstractSslErrorHandler> _sslErrorHandler;
QuotaInfo *_quotaInfo;
QSharedPointer<QNetworkAccessManager> _am;
QScopedPointer<AbstractCredentials> _credentials;
bool _http2Supported = false;

View File

@@ -64,6 +64,7 @@ static const char optionalServerNotificationsC[] = "optionalServerNotifications"
static const char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane";
static const char skipUpdateCheckC[] = "skipUpdateCheck";
static const char updateCheckIntervalC[] = "updateCheckInterval";
static const char updateSegmentC[] = "updateSegment";
static const char geometryC[] = "geometry";
static const char timeoutC[] = "timeout";
static const char chunkSizeC[] = "chunkSize";
@@ -576,6 +577,21 @@ void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection)
settings.sync();
}
int ConfigFile::updateSegment() const
{
QSettings settings(configFile(), QSettings::IniFormat);
int segment = settings.value(QLatin1String(updateSegmentC), -1).toInt();
// Invalid? (Unset at the very first launch)
if(segment < 0 || segment > 99) {
// Save valid segment value, normally has to be done only once.
segment = qrand() % 99;
settings.setValue(QLatin1String(updateSegmentC), segment);
}
return segment;
}
int ConfigFile::maxLogLines() const
{
QSettings settings(configFile(), QSettings::IniFormat);

View File

@@ -149,6 +149,11 @@ public:
bool skipUpdateCheck(const QString &connection = QString()) const;
void setSkipUpdateCheck(bool, const QString &);
/** Query-parameter 'updatesegment' for the update check, value between 0 and 99.
Used to throttle down desktop release rollout in order to keep the update servers alive at peak times.
See: https://github.com/nextcloud/client_updater_server/pull/36 */
int updateSegment() const;
void saveGeometryHeader(QHeaderView *header);
void restoreGeometryHeader(QHeaderView *header);

View File

@@ -384,7 +384,14 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
propertyMapToFileStat(map, file_stat.get());
if (file_stat->type == ItemTypeDirectory)
file_stat->size = 0;
if (file_stat->type == ItemTypeSkip
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
|| file_stat->size == -1
|| file_stat->remotePerm.isNull()
|| file_stat->etag.isEmpty()

View File

@@ -134,10 +134,6 @@ void GETFileJob::start()
_bandwidthManager->registerDownloadJob(this);
}
if (reply()->error() != QNetworkReply::NoError) {
qCWarning(lcGetJob) << " Network error: " << errorString();
}
connect(this, &AbstractNetworkJob::networkActivity, account().data(), &Account::propagatorNetworkActivity);
AbstractNetworkJob::start();
@@ -168,7 +164,6 @@ void GETFileJob::slotMetaDataChanged()
// If the status code isn't 2xx, don't write the reply body to the file.
// For any error: handle it when the job is finished, not here.
if (httpStatus / 100 != 2) {
_device->close();
return;
}
if (reply()->error() != QNetworkReply::NoError) {
@@ -295,15 +290,13 @@ void GETFileJob::slotReadyRead()
return;
}
if (_device->isOpen() && _saveBodyToFile) {
qint64 w = _device->write(buffer.constData(), r);
if (w != r) {
_errorString = _device->errorString();
_errorStatus = SyncFileItem::NormalError;
qCWarning(lcGetJob) << "Error while writing to file" << w << r << _errorString;
reply()->abort();
return;
}
qint64 w = _device->write(buffer.constData(), r);
if (w != r) {
_errorString = _device->errorString();
_errorStatus = SyncFileItem::NormalError;
qCWarning(lcGetJob) << "Error while writing to file" << w << r << _errorString;
reply()->abort();
return;
}
}

View File

@@ -64,6 +64,7 @@ list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
list(APPEND FolderMan_SRC ../src/gui/navigationpanehelper.cpp )
list(APPEND FolderMan_SRC ../src/gui/userinfo.cpp )
list(APPEND FolderMan_SRC ../src/gui/connectionvalidator.cpp )
list(APPEND FolderMan_SRC ../src/gui/clientproxy.cpp )
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
@@ -75,6 +76,7 @@ nextcloud_add_test(FolderMan "${FolderMan_SRC}")
SET(RemoteWipe_SRC ../src/gui/remotewipe.cpp)
list(APPEND RemoteWipe_SRC ../src/gui/clientproxy.cpp )
list(APPEND RemoteWipe_SRC ../src/gui/guiutility.cpp )
list(APPEND RemoteWipe_SRC ../src/gui/userinfo.cpp )
list(APPEND RemoteWipe_SRC ../src/gui/connectionvalidator.cpp )
list(APPEND RemoteWipe_SRC ../src/gui/accountstate.cpp )
list(APPEND RemoteWipe_SRC ../src/gui/socketapi.cpp )

View File

@@ -716,22 +716,26 @@ class FakeErrorReply : public QNetworkReply
public:
FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
QObject *parent, int httpErrorCode)
: QNetworkReply{parent}, _httpErrorCode(httpErrorCode) {
: QNetworkReply{parent}, _httpErrorCode(httpErrorCode){
setRequest(request);
setUrl(request.url());
setOperation(op);
open(QIODevice::ReadOnly);
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, httpErrorCode);
setError(InternalServerError, "Internal Server Fake Error");
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
Q_INVOKABLE void respond() {
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, _httpErrorCode);
setError(InternalServerError, "Internal Server Fake Error");
emit metaDataChanged();
emit readyRead();
// finishing can come strictly after readyRead was called
QTimer::singleShot(5, this, &FakeErrorReply::slotSetFinished);
}
// make public to give tests easy interface
using QNetworkReply::setError;
using QNetworkReply::setAttribute;
public slots:
void slotSetFinished() {

248
test/testdownload.cpp Normal file
View File

@@ -0,0 +1,248 @@
/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*
*/
#include <QtTest>
#include "syncenginetestutils.h"
#include <syncengine.h>
#include <owncloudpropagator.h>
using namespace OCC;
static constexpr qint64 stopAfter = 3'123'668;
/* A FakeGetReply that sends max 'fakeSize' bytes, but whose ContentLength has the corect size */
class BrokenFakeGetReply : public FakeGetReply
{
Q_OBJECT
public:
using FakeGetReply::FakeGetReply;
int fakeSize = stopAfter;
qint64 bytesAvailable() const override
{
if (aborted)
return 0;
return std::min(size, fakeSize) + QIODevice::bytesAvailable();
}
qint64 readData(char *data, qint64 maxlen) override
{
qint64 len = std::min(qint64{ fakeSize }, maxlen);
std::fill_n(data, len, payload);
size -= len;
fakeSize -= len;
return len;
}
};
SyncFileItemPtr getItem(const QSignalSpy &spy, const QString &path)
{
for (const QList<QVariant> &args : spy) {
auto item = args[0].value<SyncFileItemPtr>();
if (item->destination() == path)
return item;
}
return {};
}
class TestDownload : public QObject
{
Q_OBJECT
private slots:
void testResume()
{
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
auto size = 30 * 1000 * 1000;
fakeFolder.remoteModifier().insert("A/a0", size);
// First, download only the first 3 MB of the file
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) {
return new BrokenFakeGetReply(fakeFolder.remoteModifier(), op, request, this);
}
return nullptr;
});
QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because not all the file was downloaded
QCOMPARE(getItem(completeSpy, "A/a0")->_status, SyncFileItem::SoftError);
QCOMPARE(getItem(completeSpy, "A/a0")->_errorString, QString("The file could not be downloaded completely."));
QVERIFY(fakeFolder.syncEngine().isAnotherSyncNeeded());
// Now, we need to restart, this time, it should resume.
QByteArray ranges;
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/a0")) {
ranges = request.rawHeader("Range");
}
return nullptr;
});
QVERIFY(fakeFolder.syncOnce()); // now this succeeds
QCOMPARE(ranges, QByteArray("bytes=" + QByteArray::number(stopAfter) + "-"));
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testErrorMessage () {
// This test's main goal is to test that the error string from the server is shown in the UI
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
auto size = 3'500'000;
fakeFolder.remoteModifier().insert("A/broken", size);
QByteArray serverMessage = "The file was not downloaded because the tests wants so!";
// First, download only the first 3 MB of the file
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/broken")) {
return new FakeErrorReply(op, request, this, 400,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n"
"<s:exception>Sabre\\DAV\\Exception\\Forbidden</s:exception>\n"
"<s:message>"+serverMessage+"</s:message>\n"
"</d:error>");
}
return nullptr;
});
bool timedOut = false;
QTimer::singleShot(10000, &fakeFolder.syncEngine(), [&]() { timedOut = true; fakeFolder.syncEngine().abort(); });
QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken
QVERIFY(!timedOut);
QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::NormalError);
QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains(serverMessage));
}
void serverMaintenence() {
// Server in maintenance must abort the sync.
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("A/broken");
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation) {
return new FakeErrorReply(op, request, this, 503,
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<d:error xmlns:d=\"DAV:\" xmlns:s=\"http://sabredav.org/ns\">\n"
"<s:exception>Sabre\\DAV\\Exception\\ServiceUnavailable</s:exception>\n"
"<s:message>System in maintenance mode.</s:message>\n"
"</d:error>");
}
return nullptr;
});
QSignalSpy completeSpy(&fakeFolder.syncEngine(), &SyncEngine::itemCompleted);
QVERIFY(!fakeFolder.syncOnce()); // Fail because A/broken
// FatalError means the sync was aborted, which is what we want
QCOMPARE(getItem(completeSpy, "A/broken")->_status, SyncFileItem::FatalError);
QVERIFY(getItem(completeSpy, "A/broken")->_errorString.contains("System in maintenance mode"));
}
void testMoveFailsInAConflict() {
#ifdef Q_OS_WIN
QSKIP("Not run on windows because permission on directory does not do what is expected");
#endif
// Test for https://github.com/owncloud/client/issues/7015
// We want to test the case in which the renaming of the original to the conflict file succeeds,
// but renaming the temporary file fails.
// This tests uses the fact that a "touchedFile" notification will be sent at the right moment.
// Note that there will be first a notification on the file and the conflict file before.
FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
fakeFolder.syncEngine().setIgnoreHiddenFiles(true);
fakeFolder.remoteModifier().setContents("A/a1", 'A');
fakeFolder.localModifier().setContents("A/a1", 'B');
bool propConnected = false;
QString conflictFile;
auto transProgress = connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
[&](const ProgressInfo &pi) {
auto propagator = fakeFolder.syncEngine().getPropagator();
if (pi.status() != ProgressInfo::Propagation || propConnected || !propagator)
return;
propConnected = true;
connect(propagator.data(), &OwncloudPropagator::touchedFile, [&](const QString &s) {
if (s.contains("conflicted copy")) {
QCOMPARE(conflictFile, QString());
conflictFile = s;
return;
}
if (!conflictFile.isEmpty()) {
// Check that the temporary file is still there
QCOMPARE(QDir(fakeFolder.localPath() + "A/").entryList({"*.~*"}, QDir::Files | QDir::Hidden).count(), 1);
// Set the permission to read only on the folder, so the rename of the temporary file will fail
QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x5555));
}
});
});
QVERIFY(!fakeFolder.syncOnce()); // The sync must fail because the rename failed
QVERIFY(!conflictFile.isEmpty());
// restore permissions
QFile(fakeFolder.localPath() + "A/").setPermissions(QFile::Permissions(0x7777));
QObject::disconnect(transProgress);
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation)
QTest::qFail("There shouldn't be any download", __FILE__, __LINE__);
return nullptr;
});
QVERIFY(fakeFolder.syncOnce());
// The a1 file is still tere and have the right content
QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
QCOMPARE(fakeFolder.currentRemoteState().find("A/a1")->contentChar, 'A');
QVERIFY(QFile::remove(conflictFile)); // So the comparison succeeds;
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
void testHttp2Resend() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("A/resendme", 300);
QByteArray serverMessage = "Needs to be resend on a new connection!";
int resendActual = 0;
int resendExpected = 2;
// First, download only the first 3 MB of the file
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
if (op == QNetworkAccessManager::GetOperation && request.url().path().endsWith("A/resendme") && resendActual < resendExpected) {
auto errorReply = new FakeErrorReply(op, request, this, 400, "ignore this body");
errorReply->setError(QNetworkReply::ContentReSendError, serverMessage);
errorReply->setAttribute(QNetworkRequest::HTTP2WasUsedAttribute, true);
errorReply->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, QVariant());
resendActual += 1;
return errorReply;
}
return nullptr;
});
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(resendActual, 2);
fakeFolder.remoteModifier().appendByte("A/resendme");
resendActual = 0;
resendExpected = 10;
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
QVERIFY(!fakeFolder.syncOnce());
QCOMPARE(resendActual, 4); // the 4th fails because it only resends 3 times
QCOMPARE(getItem(completeSpy, "A/resendme")->_status, SyncFileItem::NormalError);
QVERIFY(getItem(completeSpy, "A/resendme")->_errorString.contains(serverMessage));
}
};
QTEST_GUILESS_MAIN(TestDownload)
#include "testdownload.moc"

4227
translations/client_af.ts Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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