1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2026-04-07 09:46:48 +02:00

Compare commits

..

58 Commits

Author SHA1 Message Date
Matthieu Gallien
fa32c10014 Merge pull request #4097 from nextcloud/bumpVersion
Bump version
2021-12-17 16:52:51 +01:00
Matthieu Gallien
fe67d66d3d Release 3.4.1
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-17 16:46:27 +01:00
Nextcloud bot
3a3a6dd6b8 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-17 03:55:43 +00:00
Matthieu Gallien
328c673c29 Merge pull request #4094 from nextcloud/backport/4092/stable-3.4
[stable-3.4] ensure any errors after calling FileSystem::getModTime are handled
2021-12-16 18:52:03 +01:00
Matthieu Gallien
49afad0474 ensure any errors after calling FileSystem::getModTime are handled
be sure that even in release mode no errors when calling getModTime
could be ignored

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-16 15:01:20 +00:00
allexzander
7c3e91202e Merge pull request #4083 from nextcloud/backport/4058/stable-3.4
[stable-3.4] Do not crash on findAndCancelDeletedJob
2021-12-16 15:37:07 +02:00
alex-z
e94b18f97f Added sync stop when failed to cancel delete jobs.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-16 13:09:41 +00:00
alex-z
79a0b937f5 Do not crash on findAndCancelDeletedJob.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-16 13:09:41 +00:00
Nextcloud bot
e5fbc8c2dd [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-16 03:51:22 +00:00
Matthieu Gallien
3632cc659b Merge pull request #4080 from nextcloud/backport/4076/stable-3.4
[stable-3.4] Bugfix/avoid sync getting stuck
2021-12-15 11:51:55 +01:00
Matthieu Gallien
1731bf7c86 fix review comment
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-15 10:01:02 +00:00
Matthieu Gallien
936d37fd0b ensure bulk upload jobs finished after an error
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-15 10:01:02 +00:00
Nextcloud bot
7259a0bc0d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-15 03:50:48 +00:00
allexzander
06de878b4b Merge pull request #4081 from nextcloud/backport/4079/stable-3.4
[stable-3.4] Fix CMake error in ECMAddAppIcon for mac
2021-12-14 22:59:39 +02:00
alex-z
179ff27ab6 Fix CMake error in ECMAddAppIcon for mac.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-14 18:18:01 +00:00
allexzander
44da2f2ce2 Merge pull request #4078 from nextcloud/backport/4073/stable-3.4
[stable-3.4] Enforce VFS. Disable 'Make always available locally'.
2021-12-14 18:20:56 +02:00
alex-z
92b302fb37 Save folder settings to config when force-switching VFS.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-14 16:07:19 +00:00
alex-z
d2febdf17c Enforce VFS. Disable 'Make always available locally'.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-14 16:07:19 +00:00
Matthieu Gallien
9c4b4c6183 Merge pull request #4075 from nextcloud/backport/4074/stable-3.4
[stable-3.4] Bugfix/force download local invalid files
2021-12-14 16:27:31 +01:00
Matthieu Gallien
45029e9012 force download from server for local files that have invalid dates
will trigger if local state is incoherent
like the file itself haveing 0 or negative modtime and the database not

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-14 10:15:55 +00:00
Nextcloud bot
969b0e8e2e [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-14 03:50:21 +00:00
Nextcloud bot
4b46da9370 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-13 03:50:36 +00:00
Nextcloud bot
8b5dd53519 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-12 03:52:44 +00:00
Nextcloud bot
5ee5b19406 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-11 03:51:31 +00:00
Matthieu Gallien
8e1c62cc70 Merge pull request #4066 from nextcloud/backport/4064/stable-3.4
[stable-3.4] Bugfix/sync stuck on error
2021-12-10 15:01:00 +01:00
Matthieu Gallien
151e9300cd do not get stuck forever in sync in case of errors
when a local file has invalid date and we try to upload it, properly
handle the error such that we are not stuck forever in sync state

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-10 12:37:54 +00:00
Nextcloud bot
efc3116f30 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-10 03:54:52 +00:00
Matthieu Gallien
4296a6041a Merge pull request #4057 from nextcloud/backport/4055/stable-3.4
[stable-3.4] Bugfix/3.4.1 rc1
2021-12-09 12:13:11 +01:00
Matthieu Gallien
0d1e0057b3 remove files which gets downloaded with an invalid modified time
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-09 09:09:22 +00:00
Nextcloud bot
5aadc7a62d [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-09 03:54:43 +00:00
allexzander
3c28e38089 Merge pull request #4050 from nextcloud/backport/4014/stable-3.4
[stable-3.4] Feature/folder logo variations
2021-12-08 14:41:12 +02:00
alex-z
dba8fd7c76 Use different icon for a sync folder on Windows depending on zoom level.
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-08 11:36:40 +00:00
Camila
39c2bb555a Merge pull request #4051 from nextcloud/backport/4031/stable-3.4
[stable-3.4] Always prefill username from Windows login name based on server version
2021-12-08 11:15:39 +01:00
alex-z
39fc86cbcf Always prefill username from Windows login name based on server version
Signed-off-by: alex-z <blackslayer4@gmail.com>
2021-12-08 09:13:33 +00:00
Matthieu Gallien
8d574c11e8 Merge pull request #4049 from nextcloud/backport/4045/stable-3.4
[stable-3.4] Bugfix/assert invalid modtime
2021-12-08 09:10:25 +01:00
Matthieu Gallien
c02d87f283 add log statements for each new assert about invalid modified time
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
ddb5375c68 recover from local invalid modifie time: force download from server
force file download if local modified time is invalid and server has
valid modified time

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
bd78604468 prevent cases where desktop client would store invalid modified time
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
4920b4d4af prevent injecting invalid modified time through CfApi calls
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
d12d00562f do not consider that a file has changed if its mtime is invalid
a mtime should never be 0 or negative

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
b17bbb2b22 avoid downloading a file from server when modified time is invalid
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
fc64edba11 prevent invalid modified time from being propagated
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Matthieu Gallien
b6c7581414 assert on invalid modtime
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-08 08:01:27 +00:00
Nextcloud bot
962850f307 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-08 04:03:04 +00:00
Matthieu Gallien
88ab5557bd Merge pull request #4046 from nextcloud/backport/4033/stable-3.4
[stable-3.4] do not forget the path when renaming files with invalid names
2021-12-07 16:28:07 +01:00
Matthieu Gallien
e3fb3bbe73 do not forget the path when renaming files with invalid names
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-07 12:42:44 +00:00
Matthieu Gallien
a86a1b4c17 test files that should be renamed in sub-directory
Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-12-07 12:42:44 +00:00
Nextcloud bot
4326a70ede [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-07 05:07:03 +00:00
Nextcloud bot
2880bd62ce [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-06 03:59:01 +00:00
Nextcloud bot
b49633a9f7 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-05 04:06:08 +00:00
Nextcloud bot
adfe7ad953 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-04 04:01:04 +00:00
Nextcloud bot
e45a01bc03 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-03 03:56:48 +00:00
Nextcloud bot
9bcbc15834 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-02 03:56:35 +00:00
Nextcloud bot
b4a19bb6d3 [tx-robot] updated from transifex 2021-12-01 18:45:48 +00:00
Nextcloud bot
52db45c2b1 [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-12-01 03:59:02 +00:00
Nextcloud bot
b3d8cacf8c [tx-robot] updated from transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2021-11-30 03:56:41 +00:00
Camila
c004db2070 Merge pull request #4013 from nextcloud/backport/4012/stable-3.4
[stable-3.4] fix random error when updating CfApi metadata
2021-11-29 18:26:00 +01:00
Matthieu Gallien
48ada55e77 fix random error when updating CfApi metadata
initialiazing all fields in a structure is required to not have random
behavior

Signed-off-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
2021-11-29 17:24:46 +00:00
164 changed files with 5904 additions and 14054 deletions

View File

@@ -9,7 +9,7 @@ steps:
path: /drone/build path: /drone/build
commands: commands:
- cd /drone/build - cd /drone/build
- cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src - cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Debug -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
- name: compile - name: compile
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4 image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4
volumes: volumes:
@@ -53,7 +53,7 @@ steps:
path: /drone/build path: /drone/build
commands: commands:
- cd /drone/build - cd /drone/build
- cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src - cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_BUILD_TYPE=Debug -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
- name: compile - name: compile
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4 image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4
volumes: volumes:

View File

@@ -21,7 +21,7 @@ Icon=@APPLICATION_EXECUTABLE@
# Translations # Translations
Icon[de]=@APPLICATION_ICON_NAME@ Icon[de_DE]=@APPLICATION_ICON_NAME@
Name[de]=@APPLICATION_NAME@ Desktop Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
GenericName[de]=Ordner-Synchronisation GenericName[de_DE]=Ordnersynchronisierung

View File

@@ -1,24 +0,0 @@
[Desktop Entry]
Categories=Utility;X-SuSE-SyncUtility;
Type=Application
Exec=@APPLICATION_EXECUTABLE@
Name=@APPLICATION_NAME@ Desktop
Comment=@APPLICATION_NAME@ desktop synchronization client
GenericName=Folder Sync
Icon=@APPLICATION_ICON_NAME@
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
X-GNOME-Autostart-Delay=3
MimeType=application/vnd.@APPLICATION_EXECUTABLE@;
Actions=Quit;
# Translations
[Desktop Action Quit]
Exec=@APPLICATION_EXECUTABLE@ --quit
Name=Quit @APPLICATION_NAME@
Icon=@APPLICATION_EXECUTABLE@
# Translations
GenericName[id]=Sinkronisasi Folder

View File

@@ -22,6 +22,5 @@ Icon=@APPLICATION_EXECUTABLE@
# Translations # Translations
Icon[oc]=@APPLICATION_ICON_NAME@ Icon[oc]=@APPLICATION_ICON_NAME@
Name[oc]=@APPLICATION_NAME@ Burèu
Comment[oc]=@APPLICATION_NAME@ client de sincronizacion Comment[oc]=@APPLICATION_NAME@ client de sincronizacion
GenericName[oc]=Sincro. dossièr GenericName[oc]=Sincro. dossièr

View File

@@ -22,6 +22,5 @@ Icon=@APPLICATION_EXECUTABLE@
# Translations # Translations
Icon[sv]=@APPLICATION_ICON_NAME@ Icon[sv]=@APPLICATION_ICON_NAME@
Name[sv]=@APPLICATION_NAME@ Skrivbord
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
GenericName[sv]=Mappsynkronisering GenericName[sv]=Mappsynkronisering

View File

@@ -1,6 +1,5 @@
cmake_minimum_required(VERSION 3.6) cmake_minimum_required(VERSION 3.6)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 14)
cmake_policy(SET CMP0071 NEW) # Enable use of QtQuick compiler/generated code
project(client) project(client)
@@ -97,7 +96,7 @@ endif()
message(STATUS "GIT_SHA1 ${GIT_SHA1}") message(STATUS "GIT_SHA1 ${GIT_SHA1}")
set(SYSCONFDIR ${SYSCONF_INSTALL_DIR}) set(SYSCONFDIR ${SYSCONF_INSTALL_DIR})
set(SHAREDIR ${CMAKE_INSTALL_FULL_DATADIR}) set(SHAREDIR ${CMAKE_INSTALL_DATADIR})
# Build MacOS app bundle if wished # Build MacOS app bundle if wished
if(APPLE AND BUILD_OWNCLOUD_OSX_BUNDLE) if(APPLE AND BUILD_OWNCLOUD_OSX_BUNDLE)
@@ -107,9 +106,6 @@ if(APPLE AND BUILD_OWNCLOUD_OSX_BUNDLE)
set(BIN_INSTALL_DIR "${APPLICATION_NAME}.app/Contents/MacOS") set(BIN_INSTALL_DIR "${APPLICATION_NAME}.app/Contents/MacOS")
endif() endif()
option(QUICK_COMPILER "Use QtQuick compiler to improve performance" OFF)
# this option removes Http authentication, keychain, shibboleth etc and is intended for # this option removes Http authentication, keychain, shibboleth etc and is intended for
# external authentication mechanisms # external authentication mechanisms
option(TOKEN_AUTH_ONLY "TOKEN_AUTH_ONLY" OFF) option(TOKEN_AUTH_ONLY "TOKEN_AUTH_ONLY" OFF)
@@ -123,8 +119,11 @@ if(NO_MSG_HANDLER)
add_definitions(-DNO_MSG_HANDLER=1) add_definitions(-DNO_MSG_HANDLER=1)
endif() endif()
# this option builds the updater
option(BUILD_UPDATER "BUILD_UPDATER" OFF)
if(BUILD_UPDATER) if(BUILD_UPDATER)
message("Compiling with updater") message("Compiling with updater")
add_definitions(-DBUILD_UPDATER=1)
else() else()
message("Compiling without updater") message("Compiling without updater")
endif() endif()
@@ -183,7 +182,6 @@ if(BUILD_CLIENT)
pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET) pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
if(CLOUDPROVIDERS_FOUND) if(CLOUDPROVIDERS_FOUND)
pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET) pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET) pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
endif() endif()

View File

@@ -30,14 +30,12 @@ option( WITH_CRASHREPORTER "Build crashreporter" OFF )
#set( CRASHREPORTER_ICON ":/owncloud-icon.png" ) #set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
## Updater options ## Updater options
option( BUILD_UPDATER "Build updater" ON ) option( BUILD_UPDATER "Build updater" OFF )
option( WITH_PROVIDERS "Build with providers list" ON ) option( WITH_PROVIDERS "Build with providers list" ON )
option( ENFORCE_VIRTUAL_FILES_SYNC_FOLDER "Enforce use of virtual files sync folder when available" OFF ) option( ENFORCE_VIRTUAL_FILES_SYNC_FOLDER "Enforce use of virtual files sync folder when available" OFF )
option( DO_NOT_USE_PROXY "Do not use system wide proxy, instead always do a direct connection to server" OFF )
## Theming options ## Theming options
set(NEXTCLOUD_BACKGROUND_COLOR "#0082c9" CACHE STRING "Default Nextcloud background color") set(NEXTCLOUD_BACKGROUND_COLOR "#0082c9" CACHE STRING "Default Nextcloud background color")
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CACHE STRING "Hex color of the wizard header background") set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CACHE STRING "Hex color of the wizard header background")

View File

@@ -1,6 +1,6 @@
set( MIRALL_VERSION_MAJOR 3 ) set( MIRALL_VERSION_MAJOR 3 )
set( MIRALL_VERSION_MINOR 4 ) set( MIRALL_VERSION_MINOR 4 )
set( MIRALL_VERSION_PATCH 50 ) set( MIRALL_VERSION_PATCH 1 )
set( MIRALL_VERSION_YEAR 2021 ) set( MIRALL_VERSION_YEAR 2021 )
set( MIRALL_SOVERSION 0 ) set( MIRALL_SOVERSION 0 )

View File

@@ -15,10 +15,10 @@ OBS_PROJECT_BETA=home:ivaradi:beta
OBS_PACKAGE=nextcloud-desktop OBS_PACKAGE=nextcloud-desktop
if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then
UBUNTU_DISTRIBUTIONS="bionic focal impish jammy" UBUNTU_DISTRIBUTIONS="bionic focal hirsute impish"
DEBIAN_DISTRIBUTIONS="buster stretch testing" DEBIAN_DISTRIBUTIONS="buster stretch testing"
else else
UBUNTU_DISTRIBUTIONS="focal impish jammy" UBUNTU_DISTRIBUTIONS="focal hirsute impish"
DEBIAN_DISTRIBUTIONS="testing" DEBIAN_DISTRIBUTIONS="testing"
fi fi

View File

@@ -26,8 +26,6 @@ install(FILES
${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat ${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat
Platform.wxi Platform.wxi
Nextcloud.wxs Nextcloud.wxs
RegistryCleanup.vbs
RegistryCleanupCustomAction.wxs
gui/banner.bmp gui/banner.bmp
gui/dialog.bmp gui/dialog.bmp
DESTINATION msi/) DESTINATION msi/)

View File

@@ -76,16 +76,12 @@
<!-- Uninstall: Remove sync folders from Explorer's Navigation Pane, only effective for the current user (home users) --> <!-- Uninstall: Remove sync folders from Explorer's Navigation Pane, only effective for the current user (home users) -->
<Custom Action="RemoveNavigationPaneEntries" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom> <Custom Action="RemoveNavigationPaneEntries" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
<!-- Uninstall: Cleanup the Registry -->
<Custom Action="RegistryCleanupCustomAction" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
<!-- Schedule Reboot for the Shell Extensions (in silent installation mode only, or if SCHEDULE_REBOOT argument is set--> <!-- Schedule Reboot for the Shell Extensions (in silent installation mode only, or if SCHEDULE_REBOOT argument is set-->
<ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR NOT (UILevel=2)</ScheduleReboot> <ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR NOT (UILevel=2)</ScheduleReboot>
</InstallExecuteSequence> </InstallExecuteSequence>
<!-- "Add or Remove" Programs Entries --> <!-- "Add or Remove" Programs Entries -->
<Property Id="APPNAME">$(var.AppName)</Property>
<Property Id="ARPPRODUCTICON">$(var.AppIcon)</Property> <Property Id="ARPPRODUCTICON">$(var.AppIcon)</Property>
<Property Id="ARPHELPLINK">$(var.AppHelpLink)</Property> <Property Id="ARPHELPLINK">$(var.AppHelpLink)</Property>
<Property Id="ARPURLINFOABOUT">$(var.AppInfoLink)</Property> <Property Id="ARPURLINFOABOUT">$(var.AppInfoLink)</Property>

View File

@@ -1,54 +0,0 @@
On Error goto 0
Const HKEY_LOCAL_MACHINE = &H80000002
Const strObjRegistry = "winmgmts:\\.\root\default:StdRegProv"
Function RegistryDeleteKeyRecursive(regRoot, strKeyPath)
Set objRegistry = GetObject(strObjRegistry)
objRegistry.EnumKey regRoot, strKeyPath, arrSubkeys
If IsArray(arrSubkeys) Then
For Each strSubkey In arrSubkeys
RegistryDeleteKeyRecursive regRoot, strKeyPath & "\" & strSubkey
Next
End If
objRegistry.DeleteKey regRoot, strKeyPath
End Function
Function RegistryListSubkeys(regRoot, strKeyPath)
Set objRegistry = GetObject(strObjRegistry)
objRegistry.EnumKey regRoot, strKeyPath, arrSubkeys
RegistryListSubkeys = arrSubkeys
End Function
Function GetUserSID()
Dim objWshNetwork, objUserAccount
Set objWshNetwork = CreateObject("WScript.Network")
Set objUserAccount = GetObject("winmgmts://" & objWshNetwork.UserDomain & "/root/cimv2").Get("Win32_UserAccount.Domain='" & objWshNetwork.ComputerName & "',Name='" & objWshNetwork.UserName & "'")
GetUserSID = objUserAccount.SID
End Function
Function RegistryCleanupSyncRootManager()
strSyncRootManagerKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager"
arrSubKeys = RegistryListSubkeys(HKEY_LOCAL_MACHINE, strSyncRootManagerKeyPath)
If IsArray(arrSubkeys) Then
arrSubkeys=Filter(arrSubkeys, Session.Property("APPNAME"))
End If
If IsArray(arrSubkeys) Then
arrSubkeys=Filter(arrSubkeys, GetUserSID())
End If
If IsArray(arrSubkeys) Then
For Each strSubkey In arrSubkeys
RegistryDeleteKeyRecursive HKEY_LOCAL_MACHINE, strSyncRootManagerKeyPath & "\" & strSubkey
Next
End If
End Function
Function RegistryCleanup()
RegistryCleanupSyncRootManager()
End Function

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<Binary Id="RegistryCleanup" SourceFile="RegistryCleanup.vbs"/>
<CustomAction Id='RegistryCleanupCustomAction' BinaryKey="RegistryCleanup" VBScriptCall="RegistryCleanup" Return="ignore" Execute="immediate"/>
</Fragment>
</Wix>

View File

@@ -17,10 +17,10 @@ Rem Generate collect.wxs
if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% if %ERRORLEVEL% neq 0 exit %ERRORLEVEL%
Rem Compile en-US (https://www.firegiant.com/wix/tutorial/transforms/morphing-installers/) Rem Compile en-US (https://www.firegiant.com/wix/tutorial/transforms/morphing-installers/)
"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs RegistryCleanupCustomAction.wxs "%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs
if %ERRORLEVEL% neq 0 exit %ERRORLEVEL% if %ERRORLEVEL% neq 0 exit %ERRORLEVEL%
Rem Link MSI package Rem Link MSI package
"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "@MSI_INSTALLER_FILENAME@" "%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj -out "@MSI_INSTALLER_FILENAME@"
exit %ERRORLEVEL% exit %ERRORLEVEL%

View File

@@ -33,7 +33,6 @@
#cmakedefine APPLICATION_FORBID_BAD_SSL "@APPLICATION_FORBID_BAD_SSL@" #cmakedefine APPLICATION_FORBID_BAD_SSL "@APPLICATION_FORBID_BAD_SSL@"
#define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX #define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX
#cmakedefine01 ENFORCE_VIRTUAL_FILES_SYNC_FOLDER #cmakedefine01 ENFORCE_VIRTUAL_FILES_SYNC_FOLDER
#cmakedefine DO_NOT_USE_PROXY "@DO_NOT_USE_PROXY@"
#cmakedefine ZLIB_FOUND @ZLIB_FOUND@ #cmakedefine ZLIB_FOUND @ZLIB_FOUND@
@@ -42,6 +41,4 @@
#cmakedefine01 GUI_TESTING #cmakedefine01 GUI_TESTING
#cmakedefine BUILD_UPDATER "@BUILD_UPDATER@"
#endif #endif

View File

@@ -21,7 +21,7 @@ result, the Nextcloud Client runs on Linux, Windows, and MacOS.
The Synchronization Process The Synchronization Process
--------------------------- ---------------------------
The process of synchronization keeps files in two separate repositories the The process of synchronization keeps files in two separate repositories the
same. When synchronized: same. When synchronized:
- If a file is added to one repository it is copied to the other synchronized repository. - If a file is added to one repository it is copied to the other synchronized repository.
@@ -93,7 +93,7 @@ traverses the file tree and compares the modification time of each file with an
expected value stored in its database. If the value is not the same, the client expected value stored in its database. If the value is not the same, the client
determines that the file has been modified in the local repository. determines that the file has been modified in the local repository.
.. note:: On the local side, the modification time is a good attribute to use for .. note:: On the local side, the modification time is a good attribute to use for
detecting changes, because detecting changes, because
the value does not depend on time shifts and such. the value does not depend on time shifts and such.
@@ -122,7 +122,7 @@ Conflict files are always created on the client and never on the server.
In ownCloud 10.0 we implemented a checksum feature which checks the file integrity on upload and download by computing a checksum after the file transfer finishes. In ownCloud 10.0 we implemented a checksum feature which checks the file integrity on upload and download by computing a checksum after the file transfer finishes.
The client queries the server capabilities after login to decide which checksum algorithm to use. The client queries the server capabilities after login to decide which checksum algorithm to use.
Currently, SHA1 is hard-coded in the official server release and can't be changed by the end-user. Currently, SHA1 is hard-coded in the official server release and can't be changed by the end-user.
Note that the server additionally also supports MD5 and Adler-32, but the desktop client will always use the checksum algorithm announced in the capabilities: Note that the server additionally also supports MD5 and Adler-32, but the desktop client will always use the checksum algorithm announced in the capabilities:
:: ::
@@ -202,14 +202,14 @@ Conflict files are always created on the client and never on the server.
Upload Upload
~~~~~~ ~~~~~~
A checksum is calculated with the previously negotiated algorithm by the client and sent along with the file in an HTTP Header. A checksum is calculated with the previously negotiated algorithm by the client and sent along with the file in an HTTP Header.
```OC-Checksum: [algorithm]:[checksum]``` ```OC-Checksum: [algorithm]:[checksum]```
.. image:: ./images/checksums/client-activity.png .. image:: ./images/checksums/client-activity.png
During file upload, the server computes SHA1, MD5, and Adler-32 checksums and compares one of them to the checksum supplied by the client. During file upload, the server computes SHA1, MD5, and Adler-32 checksums and compares one of them to the checksum supplied by the client.
On mismatch, the server returns HTTP Status code 400 (Bad Request) thus signaling the client that the upload failed. On mismatch, the server returns HTTP Status code 400 (Bad Request) thus signaling the client that the upload failed.
The server then discards the upload, and the client blacklists the file: The server then discards the upload, and the client blacklists the file:
.. image:: ./images/checksums/testing-checksums.png .. image:: ./images/checksums/testing-checksums.png
@@ -223,29 +223,29 @@ Conflict files are always created on the client and never on the server.
client.</s:message> client.</s:message>
</d:error> </d:error>
The client retries the upload using exponential back-off. The client retries the upload using exponential back-off.
On success (matching checksum) the computed checksums are stored by the server in ``oc_filecache`` alongside the file. On success (matching checksum) the computed checksums are stored by the server in ``oc_filecache`` alongside the file.
Chunked Upload Chunked Upload
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
Mostly same as above. Mostly same as above.
The checksum of the full file is sent with every chunk of the file. The checksum of the full file is sent with every chunk of the file.
But the server only compares the checksum after receiving the checksum sent with the last chunk. But the server only compares the checksum after receiving the checksum sent with the last chunk.
Download Download
~~~~~~~~ ~~~~~~~~
The server sends the checksum in an HTTP header with the file. (same format as above). The server sends the checksum in an HTTP header with the file. (same format as above).
If no checksum is found in ``oc_filecache`` (freshly mounted external storage) it is computed and stored in ``oc_filecache`` on the first download. If no checksum is found in ``oc_filecache`` (freshly mounted external storage) it is computed and stored in ``oc_filecache`` on the first download.
The checksum is then provided on all subsequent downloads but not on the first. The checksum is then provided on all subsequent downloads but not on the first.
.. _ignored-files-label: .. _ignored-files-label:
Ignored Files Ignored Files
------------- -------------
The Nextcloud Client supports the ability to exclude or ignore certain files from the synchronization process. The Nextcloud Client supports the ability to exclude or ignore certain files from the synchronization process.
Some system wide file patterns that are used to exclude or ignore files are included with the client by default and the Nextcloud Client provides the ability to add custom patterns. Some system wide file patterns that are used to exclude or ignore files are included with the client by default and the Nextcloud Client provides the ability to add custom patterns.
By default, the Nextcloud Client ignores the following files: By default, the Nextcloud Client ignores the following files:
@@ -262,18 +262,18 @@ By default, the Nextcloud Client ignores the following files:
If a pattern selected using a checkbox in the `ignoredFilesEditor-label` (or if If a pattern selected using a checkbox in the `ignoredFilesEditor-label` (or if
a line in the exclude file starts with the character ``]`` directly followed by a line in the exclude file starts with the character ``]`` directly followed by
the file pattern), files matching the pattern are considered *fleeting meta the file pattern), files matching the pattern are considered *fleeting meta
data*. data*.
These files are ignored and *removed* by the client if found in the These files are ignored and *removed* by the client if found in the
synchronized folder. synchronized folder.
This is suitable for meta files created by some applications that have no sustainable meaning. This is suitable for meta files created by some applications that have no sustainable meaning.
If a pattern ends with the forward slash (``/``) character, only directories are matched. If a pattern ends with the forward slash (``/``) character, only directories are matched.
The pattern is only applied for directory components of filenames selected using the checkbox. The pattern is only applied for directory components of filenames selected using the checkbox.
To match filenames against the exclude patterns, the UNIX standard C library To match filenames against the exclude patterns, the UNIX standard C library
function ``fnmatch`` is used. function ``fnmatch`` is used.
This process checks the filename against the specified pattern using standard shell wildcard pattern matching. This process checks the filename against the specified pattern using standard shell wildcard pattern matching.
For more information, please refer to `The opengroup website For more information, please refer to `The opengroup website
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01>`_. <http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01>`_.
@@ -369,7 +369,7 @@ is renamed or moved.
Example: Example:
<oc:id>00000020oc5cfy6qqizm</oc:id> <oc:id>00000020oc5cfy6qqizm</oc:id>
End-to-end Encryption End-to-end Encryption
--------------------- ---------------------
@@ -440,3 +440,13 @@ Files that must be removed from the local storage only, need to be dehydrated vi
.. note:: .. note::
* End-to-end Encryption works with Virtual Files (VFS) but only on a per-folder level. Folders with E2EE can be made available offline in their entirety, but the individual files in them can not be retrieved on demand. This is mainly due to two technical reasons. First, the Windows VFS API is not designed for handling encrypted files. Second, while the VFS is designed to deal mostly with large files, E2EE is mostly recommended for use with small files as encrypting and decrypting large files puts large demands on the computer infrastructure. * End-to-end Encryption works with Virtual Files (VFS) but only on a per-folder level. Folders with E2EE can be made available offline in their entirety, but the individual files in them can not be retrieved on demand. This is mainly due to two technical reasons. First, the Windows VFS API is not designed for handling encrypted files. Second, while the VFS is designed to deal mostly with large files, E2EE is mostly recommended for use with small files as encrypting and decrypting large files puts large demands on the computer infrastructure.
User Status
-----------
Starting from 3.2.0, user status is displayed in the Nextcloud desktop client's tray window. The icon and a text message are displayed as long as those are set in the user's account menu in the Web UI (server's website). At the moment, setting the status from the desktop client is not available.
The status is updated almost immediately after it is set in the Web UI. Default user status is always "Online" if no other status is available from the server-side.
.. image:: images/status_feature_example.png
:alt: User Status feature in the tray window

View File

@@ -50,7 +50,7 @@ copyright = u'2013-2021, The Nextcloud developers'
# The short X.Y version. # The short X.Y version.
version = '3.4' version = '3.4'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '3.4.50' release = '3.4.1'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -8,9 +8,7 @@ There are clients for Linux, macOs, and Microsoft Windows.
The currently supported server releases are the latest three stable versions The currently supported server releases are the latest three stable versions
at time of publication. It means that the |version| release series is supporting at time of publication. It means that the |version| release series is supporting
stable server major versions. server major version 20, 21 and 22.
See https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule for
supported major versions.
Installation on Mac OS X and Windows is the same as for any software Installation on Mac OS X and Windows is the same as for any software
application: download the program and then double-click it to launch the application: download the program and then double-click it to launch the

View File

@@ -1,4 +1,3 @@
===========
Visual Tour Visual Tour
=========== ===========
@@ -12,7 +11,23 @@ as an icon in the system tray (Windows, KDE), status bar
(macOS), or notification area (Ubuntu). (macOS), or notification area (Ubuntu).
.. image:: images/icon.png .. image:: images/icon.png
:alt: desktop client icon
Main dialog
~~~~~~~~~~~
.. index:: activity, recent changes, sync activity, main dialog, adding account, account, add account, remove account
The main dialog, which can be invoked from the tray icon in the
taskbar, will show files information about the activities of the sync
client and Nextcloud. If there are any synchronization issues, they
will show up here. The dialog also gives information about other
activities or notifications like Talk mentions or file changes. It
does also show the status of the user.
.. image:: images/main_dialog.png
When clicking on the avatar a menu opens where it is possible to add a
new account or removing an existing account.
Menu Menu
---- ----
@@ -30,9 +45,8 @@ A right click on the icon provides the following menu:
.. NOTE:: .. NOTE::
This menu is not available on macOS. This menu is not available on macOS.
Settings Settings
~~~~~~~~ --------
Account Settings Account Settings
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@@ -204,98 +218,3 @@ while syncing for a three times. These are listed in the activity view.
There also is a button to retry the sync for another three times. There also is a button to retry the sync for another three times.
For more detailed information see :ref:`ignored-files-label`. For more detailed information see :ref:`ignored-files-label`.
Main dialog
-----------
.. index:: activity, recent changes, sync activity, main dialog, adding account, account, add account, remove account, sync state, user status, unified search, share dialog
Sync State
~~~~~~~~~~
The main dialog, which can be invoked from the tray icon in the
taskbar, will show files information about the activities of the sync
client and Nextcloud.
.. image:: images/sync-state-paused.png
:alt: sync state paused
.. image:: images/sync-state-syncing.png
:alt: sync state syncing
.. image:: images/sync-state-synced.png
:alt: sync state synced
If there are any synchronization issues, they will show up here:
.. image:: images/sync-state-warnings.png
:alt: sync state warnings
For more information on how to solve these issues see :doc:`troubleshooting`.
When clicking on the avatar a menu opens where it is
possible to add a new account or removing an existing account.
.. image:: images/user-account-options.png
:alt: user account options
User Status
~~~~~~~~~~~
User status is displayed in the Nextcloud desktop client's tray window.
Default user status is always "Online" if no other status is available from the server-side.
.. image:: images/status_feature_example.png
:alt: User Status feature in the tray window
When clicking on ``Set status`` you can edit the emoji, message and the timer to clear your user status:
.. image:: images/set-user-status.png
:alt: set user status menu option
|
.. image:: images/set-user-status-menu.png
:alt: changing the user status
Activities list
~~~~~~~~~~~~~~~
The dialog also gives information about other activities or
notifications like Talk mentions or file changes.
It does also show the status of the user.
.. image:: images/main_dialog.png
:alt: main dialog activities list
Unified search
~~~~~~~~~~~~~~
With the unified search you can find everything you have in your server - files,
Talk messages, calendar appointments:
.. image:: images/unified-search-files.png
:alt: unified search files search result
.. image:: images/unified-search-talk.png
:alt: unified search Talk conversations search result
.. image:: images/unified-search-events.png
:alt: unified search calendar appointments search result
Share dialog: Talk options and View Profile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can now share a file directly in a conversation in Talk and view the sharee user profile:
.. image:: images/open-share-dialog.png
:alt: open share dialog option
|
.. image:: images/share-dialog-view-profile.png
:alt: shared dialog sharing options

View File

@@ -23,10 +23,5 @@
<file>src/gui/tray/UnifiedSearchResultListItem.qml</file> <file>src/gui/tray/UnifiedSearchResultListItem.qml</file>
<file>src/gui/tray/UnifiedSearchResultNothingFound.qml</file> <file>src/gui/tray/UnifiedSearchResultNothingFound.qml</file>
<file>src/gui/tray/UnifiedSearchResultSectionItem.qml</file> <file>src/gui/tray/UnifiedSearchResultSectionItem.qml</file>
<file>src/gui/tray/CustomButton.qml</file>
<file>src/gui/tray/CustomTextButton.qml</file>
<file>src/gui/tray/ActivityItemContextMenu.qml</file>
<file>src/gui/tray/ActivityItemActions.qml</file>
<file>src/gui/tray/ActivityItemContent.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -337,7 +337,7 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) { if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader; qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader;
emit validationFailed(tr("The checksum header is malformed."), _calculatedChecksumType, _calculatedChecksum, ChecksumHeaderMalformed); emit validationFailed(tr("The checksum header is malformed."));
return nullptr; return nullptr;
} }
@@ -360,30 +360,15 @@ void ValidateChecksumHeader::start(std::unique_ptr<QIODevice> device, const QByt
calculator->start(std::move(device)); calculator->start(std::move(device));
} }
QByteArray ValidateChecksumHeader::calculatedChecksumType() const
{
return _calculatedChecksumType;
}
QByteArray ValidateChecksumHeader::calculatedChecksum() const
{
return _calculatedChecksum;
}
void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType, void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType,
const QByteArray &checksum) const QByteArray &checksum)
{ {
_calculatedChecksumType = checksumType;
_calculatedChecksum = checksum;
if (checksumType != _expectedChecksumType) { if (checksumType != _expectedChecksumType) {
emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)), emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)));
_calculatedChecksumType, _calculatedChecksum, ChecksumTypeUnknown);
return; return;
} }
if (checksum != _expectedChecksum) { if (checksum != _expectedChecksum) {
emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)), emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)));
_calculatedChecksumType, _calculatedChecksum, ChecksumMismatch);
return; return;
} }
emit validated(checksumType, checksum); emit validated(checksumType, checksum);

View File

@@ -140,14 +140,6 @@ class OCSYNC_EXPORT ValidateChecksumHeader : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum FailureReason {
Success,
ChecksumHeaderMalformed,
ChecksumTypeUnknown,
ChecksumMismatch,
};
Q_ENUM(FailureReason)
explicit ValidateChecksumHeader(QObject *parent = nullptr); explicit ValidateChecksumHeader(QObject *parent = nullptr);
/** /**
@@ -169,13 +161,9 @@ public:
*/ */
void start(std::unique_ptr<QIODevice> device, const QByteArray &checksumHeader); void start(std::unique_ptr<QIODevice> device, const QByteArray &checksumHeader);
QByteArray calculatedChecksumType() const;
QByteArray calculatedChecksum() const;
signals: signals:
void validated(const QByteArray &checksumType, const QByteArray &checksum); void validated(const QByteArray &checksumType, const QByteArray &checksum);
void validationFailed(const QString &errMsg, const QByteArray &calculatedChecksumType, void validationFailed(const QString &errMsg);
const QByteArray &calculatedChecksum, const ValidateChecksumHeader::FailureReason reason);
private slots: private slots:
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum); void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
@@ -185,9 +173,6 @@ private:
QByteArray _expectedChecksumType; QByteArray _expectedChecksumType;
QByteArray _expectedChecksum; QByteArray _expectedChecksum;
QByteArray _calculatedChecksumType;
QByteArray _calculatedChecksum;
}; };
/** /**

View File

@@ -1,14 +1,5 @@
project(gui) project(gui)
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 Xml Network) find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 Xml Network)
if(QUICK_COMPILER)
find_package(Qt5QuickCompiler)
set_package_properties(Qt5QuickCompiler PROPERTIES
DESCRIPTION "Compile QML at build time"
TYPE REQUIRED
)
endif()
if (NOT TARGET Qt5::GuiPrivate) if (NOT TARGET Qt5::GuiPrivate)
message(FATAL_ERROR "Could not find GuiPrivate component of Qt5. It might be shipped as a separate package, please check that.") message(FATAL_ERROR "Could not find GuiPrivate component of Qt5. It might be shipped as a separate package, please check that.")
endif() endif()
@@ -22,12 +13,14 @@ IF(BUILD_UPDATER)
endif() endif()
configure_file(${CMAKE_SOURCE_DIR}/theme.qrc.in ${CMAKE_SOURCE_DIR}/theme.qrc) configure_file(${CMAKE_SOURCE_DIR}/theme.qrc.in ${CMAKE_SOURCE_DIR}/theme.qrc)
set(MIRALL_RC_SRC ../../resources.qrc)
list(APPEND MIRALL_RC_SRC ${CMAKE_SOURCE_DIR}/theme.qrc)
set(theme_dir ${CMAKE_SOURCE_DIR}/theme) set(theme_dir ${CMAKE_SOURCE_DIR}/theme)
set(client_UI_SRCS set(client_UI_SRCS
accountsettings.ui accountsettings.ui
conflictdialog.ui conflictdialog.ui
internallinkwidget.ui
invalidfilenamedialog.ui invalidfilenamedialog.ui
foldercreationdialog.ui foldercreationdialog.ui
folderwizardsourcepage.ui folderwizardsourcepage.ui
@@ -44,9 +37,23 @@ set(client_UI_SRCS
shareuserline.ui shareuserline.ui
sslerrordialog.ui sslerrordialog.ui
addcertificatedialog.ui addcertificatedialog.ui
passwordinputdialog.ui
proxyauthdialog.ui proxyauthdialog.ui
mnemonicdialog.ui mnemonicdialog.ui
UserStatusSelector.qml
UserStatusSelectorDialog.qml
tray/ActivityActionButton.qml
tray/ActivityItem.qml
tray/ActivityList.qml
tray/Window.qml
tray/UserLine.qml
tray/UnifiedSearchInputContainer.qml
tray/UnifiedSearchResultFetchMoreTrigger.qml
tray/UnifiedSearchResultItem.qml
tray/UnifiedSearchResultItemSkeleton.qml
tray/UnifiedSearchResultItemSkeletonContainer.qml
tray/UnifiedSearchResultListItem.qml
tray/UnifiedSearchResultNothingFound.qml
tray/UnifiedSearchResultSectionItem.qml
wizard/flow2authwidget.ui wizard/flow2authwidget.ui
wizard/owncloudadvancedsetuppage.ui wizard/owncloudadvancedsetuppage.ui
wizard/owncloudconnectionmethoddialog.ui wizard/owncloudconnectionmethoddialog.ui
@@ -57,12 +64,6 @@ set(client_UI_SRCS
wizard/welcomepage.ui wizard/welcomepage.ui
) )
if(QUICK_COMPILER)
qtquick_compiler_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc)
else()
qt_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc)
endif()
set(client_SRCS set(client_SRCS
accountmanager.cpp accountmanager.cpp
accountsettings.cpp accountsettings.cpp
@@ -81,7 +82,6 @@ set(client_SRCS
folderwizard.cpp folderwizard.cpp
generalsettings.cpp generalsettings.cpp
legalnotice.cpp legalnotice.cpp
internallinkwidget.cpp
ignorelisteditor.cpp ignorelisteditor.cpp
ignorelisttablewidget.cpp ignorelisttablewidget.cpp
lockwatcher.cpp lockwatcher.cpp
@@ -95,7 +95,6 @@ set(client_SRCS
openfilemanager.cpp openfilemanager.cpp
owncloudgui.cpp owncloudgui.cpp
owncloudsetupwizard.cpp owncloudsetupwizard.cpp
passwordinputdialog.cpp
selectivesyncdialog.cpp selectivesyncdialog.cpp
settingsdialog.cpp settingsdialog.cpp
sharedialog.cpp sharedialog.cpp
@@ -233,6 +232,7 @@ IF( WIN32 )
ENDIF() ENDIF()
set( final_src set( final_src
${MIRALL_RC_SRC}
${client_SRCS} ${client_SRCS}
${client_UI_SRCS} ${client_UI_SRCS}
${guiMoc} ${guiMoc}
@@ -451,6 +451,7 @@ endif()
set_target_properties(nextcloudCore set_target_properties(nextcloudCore
PROPERTIES PROPERTIES
AUTOUIC ON AUTOUIC ON
AUTORCC ON
AUTOMOC ON AUTOMOC ON
) )

View File

@@ -24,7 +24,11 @@ ColumnLayout {
spacing: 0 spacing: 0
property NC.UserStatusSelectorModel userStatusSelectorModel property NC.UserStatusSelectorModel userStatusSelectorModel
Label { FontMetrics {
id: metrics
}
Text {
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
@@ -85,7 +89,7 @@ ColumnLayout {
} }
} }
Label { Text {
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 8 Layout.leftMargin: 8
Layout.rightMargin: 8 Layout.rightMargin: 8
@@ -104,8 +108,8 @@ ColumnLayout {
Layout.fillWidth: true Layout.fillWidth: true
Button { Button {
Layout.preferredWidth: userStatusMessageTextField.height Layout.preferredWidth: userStatusMessageTextField.height // metrics.height * 2
Layout.preferredHeight: userStatusMessageTextField.height Layout.preferredHeight: userStatusMessageTextField.height // metrics.height * 2
text: userStatusSelectorModel.userStatusEmoji text: userStatusSelectorModel.userStatusEmoji
onClicked: emojiDialog.open() onClicked: emojiDialog.open()
} }
@@ -157,7 +161,7 @@ ColumnLayout {
Layout.bottomMargin: 8 Layout.bottomMargin: 8
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
Label { Text {
text: qsTr("Clear status message after") text: qsTr("Clear status message after")
} }

View File

@@ -221,19 +221,6 @@ void AccountState::setDesktopNotificationsAllowed(bool isAllowed)
emit desktopNotificationsAllowedChanged(); emit desktopNotificationsAllowedChanged();
} }
AccountState::ConnectionStatus AccountState::lastConnectionStatus() const
{
return _lastConnectionValidatorStatus;
}
void AccountState::trySignIn()
{
if (isSignedOut() && account()) {
account()->resetRejectedCertificates();
signIn();
}
}
void AccountState::checkConnectivity() void AccountState::checkConnectivity()
{ {
if (isSignedOut() || _waitingForNewCredentials) { if (isSignedOut() || _waitingForNewCredentials) {
@@ -298,8 +285,6 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
return; return;
} }
_lastConnectionValidatorStatus = status;
// Come online gradually from 503 or maintenance mode // Come online gradually from 503 or maintenance mode
if (status == ConnectionValidator::Connected if (status == ConnectionValidator::Connected
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable && (_connectionStatus == ConnectionValidator::ServiceUnavailable

View File

@@ -171,10 +171,6 @@ public:
*/ */
void setDesktopNotificationsAllowed(bool isAllowed); void setDesktopNotificationsAllowed(bool isAllowed);
ConnectionStatus lastConnectionStatus() const;
void trySignIn();
public slots: public slots:
/// Triggers a ping to the server to update state and /// Triggers a ping to the server to update state and
/// connection status and errors. /// connection status and errors.
@@ -209,7 +205,6 @@ private:
AccountPtr _account; AccountPtr _account;
State _state; State _state;
ConnectionStatus _connectionStatus; ConnectionStatus _connectionStatus;
ConnectionStatus _lastConnectionValidatorStatus = ConnectionStatus::Undefined;
QStringList _connectionErrors; QStringList _connectionErrors;
bool _waitingForNewCredentials; bool _waitingForNewCredentials;
QDateTime _timeOfLastETagCheck; QDateTime _timeOfLastETagCheck;

View File

@@ -250,10 +250,6 @@ Application::Application(int &argc, char **argv)
} }
} }
if (_theme->doNotUseProxy()) {
ConfigFile().setProxyType(QNetworkProxy::NoProxy);
}
parseOptions(arguments()); parseOptions(arguments());
//no need to waste time; //no need to waste time;
if (_helpOnly || _versionOnly) if (_helpOnly || _versionOnly)
@@ -469,9 +465,6 @@ void Application::slotCheckConnection()
if (state != AccountState::SignedOut && state != AccountState::ConfigurationError if (state != AccountState::SignedOut && state != AccountState::ConfigurationError
&& state != AccountState::AskingCredentials && !pushNotificationsAvailable) { && state != AccountState::AskingCredentials && !pushNotificationsAvailable) {
accountState->checkConnectivity(); accountState->checkConnectivity();
} else if (state == AccountState::SignedOut && accountState->lastConnectionStatus() == AccountState::ConnectionStatus::SslError) {
qCWarning(lcApplication) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt...";
accountState->trySignIn();
} }
} }

View File

@@ -136,7 +136,7 @@ void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &in
void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply) void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply)
{ {
auto job = qobject_cast<CheckServerJob *>(sender()); auto job = qobject_cast<CheckServerJob *>(sender());
qCWarning(lcConnectionValidator) << reply->error() << reply->errorString() << job->errorString() << reply->peek(1024); qCWarning(lcConnectionValidator) << reply->error() << job->errorString() << reply->peek(1024);
if (reply->error() == QNetworkReply::SslHandshakeFailedError) { if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
reportResult(SslError); reportResult(SslError);
return; return;

View File

@@ -36,6 +36,8 @@
#include "common/utility.h" #include "common/utility.h"
#include "logger.h" #include "logger.h"
#include "config.h"
#include "legalnotice.h" #include "legalnotice.h"
#include <QFileDialog> #include <QFileDialog>

View File

@@ -15,8 +15,6 @@
#ifndef MIRALL_GENERALSETTINGS_H #ifndef MIRALL_GENERALSETTINGS_H
#define MIRALL_GENERALSETTINGS_H #define MIRALL_GENERALSETTINGS_H
#include "config.h"
#include <QWidget> #include <QWidget>
#include <QPointer> #include <QPointer>

View File

@@ -1,86 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "internallinkwidget.h"
#include "accountstate.h"
#include "folderman.h"
#include "theme.h"
#include "QProgressIndicator.h"
#include <QClipboard>
namespace OCC {
Q_LOGGING_CATEGORY(lcInternalLink, "nextcloud.gui.internallink", QtInfoMsg)
InternalLinkWidget::InternalLinkWidget(const QString &localPath,
QWidget *parent)
: QWidget(parent)
, _localPath(localPath)
{
_ui->setupUi(this);
const auto folder = FolderMan::instance()->folderForPath(_localPath);
const auto folderRelativePath = _localPath.mid(folder->cleanPath().length() + 1);
const auto serverRelativePath = QDir(folder->remotePath()).filePath(folderRelativePath);
const auto bindLinkSlot = [this](QString link) { slotLinkFetched(link); };
fetchPrivateLinkUrl(
folder->accountState()->account(),
serverRelativePath,
{},
this,
bindLinkSlot
);
_ui->copyInternalLinkButton->setEnabled(false);
_ui->internalLinkProgressIndicator->setVisible(true);
_ui->internalLinkProgressIndicator->startAnimation();
connect(_ui->copyInternalLinkButton, &QPushButton::clicked, this, &InternalLinkWidget::slotCopyInternalLink);
}
void InternalLinkWidget::slotLinkFetched(const QString &url)
{
_internalUrl = url;
_ui->copyInternalLinkButton->setEnabled(true);
_ui->internalLinkProgressIndicator->setVisible(false);
_ui->internalLinkProgressIndicator->stopAnimation();
_ui->horizontalSpacer->changeSize(0, 0);
_ui->horizontalSpacer_2->changeSize(0, 0);
}
void InternalLinkWidget::slotCopyInternalLink() const
{
QApplication::clipboard()->setText(_internalUrl);
}
void InternalLinkWidget::setupUiOptions()
{
customizeStyle();
}
void InternalLinkWidget::slotStyleChanged()
{
customizeStyle();
}
void InternalLinkWidget::customizeStyle()
{
_ui->copyInternalLinkButton->setIcon(Theme::createColorAwareIcon(":/client/theme/copy.svg"));
_ui->internalLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/theme/external.svg"));
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#ifndef INTERNALLINKWIDGET_H
#define INTERNALLINKWIDGET_H
#include "QProgressIndicator.h"
#include <QList>
#include <QPushButton>
#include "ui_internallinkwidget.h"
namespace OCC {
/**
* @brief The ShareDialog class
* @ingroup gui
*/
class InternalLinkWidget : public QWidget
{
Q_OBJECT
public:
explicit InternalLinkWidget(const QString &localPath,
QWidget *parent = nullptr);
~InternalLinkWidget() override = default;
void setupUiOptions();
public slots:
void slotStyleChanged();
private slots:
void slotLinkFetched(const QString &url);
void slotCopyInternalLink() const;
private:
void customizeStyle();
std::unique_ptr<Ui::InternalLinkWidget> _ui = std::make_unique<Ui::InternalLinkWidget>();
QString _localPath;
QString _internalUrl;
QPushButton *_copyInternalLinkButton{};
};
}
#endif // INTERNALLINKWIDGET_H

View File

@@ -1,168 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OCC::InternalLinkWidget</class>
<widget class="QWidget" name="OCC::InternalLinkWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>238</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="internalLinkIconLabel">
<property name="text">
<string notr="true"/>
</property>
<property name="pixmap">
<pixmap resource="../../theme.qrc">:/client/theme/external.svg</pixmap>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalTextLayout">
<item>
<widget class="QLabel" name="internalLinkLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Internal link</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="infoMessage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(118, 118, 118)</string>
</property>
<property name="text">
<string>Only works for users with access to this folder</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</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="internalLinkProgressIndicator" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>28</width>
<height>27</height>
</size>
</property>
</widget>
</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="copyInternalLinkButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../theme.qrc">
<normaloff>:/client/theme/copy.svg</normaloff>:/client/theme/copy.svg</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QProgressIndicator</class>
<extends>QWidget</extends>
<header>QProgressIndicator.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../theme.qrc"/>
</resources>
<connections/>
</ui>

View File

@@ -34,51 +34,42 @@ NetworkSettings::NetworkSettings(QWidget *parent)
{ {
_ui->setupUi(this); _ui->setupUi(this);
_ui->proxyGroupBox->setVisible(!Theme::instance()->doNotUseProxy()); _ui->hostLineEdit->setPlaceholderText(tr("Hostname of proxy server"));
_ui->userLineEdit->setPlaceholderText(tr("Username for proxy server"));
_ui->passwordLineEdit->setPlaceholderText(tr("Password for proxy server"));
if (!Theme::instance()->doNotUseProxy()) { _ui->typeComboBox->addItem(tr("HTTP(S) proxy"), QNetworkProxy::HttpProxy);
_ui->hostLineEdit->setPlaceholderText(tr("Hostname of proxy server")); _ui->typeComboBox->addItem(tr("SOCKS5 proxy"), QNetworkProxy::Socks5Proxy);
_ui->userLineEdit->setPlaceholderText(tr("Username for proxy server"));
_ui->passwordLineEdit->setPlaceholderText(tr("Password for proxy server"));
_ui->typeComboBox->addItem(tr("HTTP(S) proxy"), QNetworkProxy::HttpProxy); _ui->authRequiredcheckBox->setEnabled(true);
_ui->typeComboBox->addItem(tr("SOCKS5 proxy"), QNetworkProxy::Socks5Proxy);
_ui->authRequiredcheckBox->setEnabled(true); // Explicitly set up the enabled status of the proxy auth widgets to ensure
// toggling the parent enables/disables the children
_ui->userLineEdit->setEnabled(true);
_ui->passwordLineEdit->setEnabled(true);
_ui->authWidgets->setEnabled(_ui->authRequiredcheckBox->isChecked());
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled,
_ui->authWidgets, &QWidget::setEnabled);
// Explicitly set up the enabled status of the proxy auth widgets to ensure connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
// toggling the parent enables/disables the children _ui->manualSettings, &QWidget::setEnabled);
_ui->userLineEdit->setEnabled(true); connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
_ui->passwordLineEdit->setEnabled(true); _ui->typeComboBox, &QWidget::setEnabled);
_ui->authWidgets->setEnabled(_ui->authRequiredcheckBox->isChecked()); connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, _ui->authWidgets, &QWidget::setEnabled); this, &NetworkSettings::checkAccountLocalhost);
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, _ui->manualSettings, &QWidget::setEnabled);
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, _ui->typeComboBox, &QWidget::setEnabled);
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, this, &NetworkSettings::checkAccountLocalhost);
loadProxySettings();
connect(_ui->typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&NetworkSettings::saveProxySettings);
connect(_ui->proxyButtonGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this,
&NetworkSettings::saveProxySettings);
connect(_ui->hostLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->userLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->passwordLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->portSpinBox, &QAbstractSpinBox::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, this, &NetworkSettings::saveProxySettings);
// Warn about empty proxy host
connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost);
checkEmptyProxyHost();
checkAccountLocalhost();
} else {
_ui->noProxyRadioButton->setChecked(false);
}
loadProxySettings();
loadBWLimitSettings(); loadBWLimitSettings();
// proxy
connect(_ui->typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &NetworkSettings::saveProxySettings);
connect(_ui->proxyButtonGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &NetworkSettings::saveProxySettings);
connect(_ui->hostLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->userLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->passwordLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->portSpinBox, &QAbstractSpinBox::editingFinished, this, &NetworkSettings::saveProxySettings);
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, this, &NetworkSettings::saveProxySettings);
connect(_ui->uploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); connect(_ui->uploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
connect(_ui->noUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); connect(_ui->noUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
connect(_ui->autoUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); connect(_ui->autoUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
@@ -87,6 +78,11 @@ NetworkSettings::NetworkSettings(QWidget *parent)
connect(_ui->autoDownloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings); connect(_ui->autoDownloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
connect(_ui->downloadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings); connect(_ui->downloadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings);
connect(_ui->uploadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings); connect(_ui->uploadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings);
// Warn about empty proxy host
connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost);
checkEmptyProxyHost();
checkAccountLocalhost();
} }
NetworkSettings::~NetworkSettings() NetworkSettings::~NetworkSettings()

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@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 "passwordinputdialog.h"
#include "ui_passwordinputdialog.h"
namespace OCC {
PasswordInputDialog::PasswordInputDialog(const QString &description, const QString &error, QWidget *parent)
: QDialog(parent)
, _ui(new Ui::PasswordInputDialog)
{
_ui->setupUi(this);
_ui->passwordLineEditLabel->setText(description);
_ui->passwordLineEditLabel->setVisible(!description.isEmpty());
_ui->labelErrorMessage->setText(error);
_ui->labelErrorMessage->setVisible(!error.isEmpty());
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
PasswordInputDialog::~PasswordInputDialog() = default;
QString PasswordInputDialog::password() const
{
return _ui->passwordLineEdit->text();
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#include <QDialog>
namespace OCC {
namespace Ui {
class PasswordInputDialog;
}
class PasswordInputDialog : public QDialog
{
Q_OBJECT
public:
explicit PasswordInputDialog(const QString &description, const QString &error, QWidget *parent = nullptr);
~PasswordInputDialog() override;
QString password() const;
private:
std::unique_ptr<Ui::PasswordInputDialog> _ui;
};
}

View File

@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OCC::PasswordInputDialog</class>
<widget class="QDialog" name="OCC::PasswordInputDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>276</width>
<height>125</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Password for share required</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="passwordLineEditLabel">
<property name="text">
<string>Please enter a password for your share:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="inputMask">
<string notr="true"/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelErrorMessage">
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(118, 118, 118)</string>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OCC::PasswordInputDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OCC::PasswordInputDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -1,4 +1,4 @@
/* /*
* Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl> * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -16,9 +16,7 @@
#include "sharedialog.h" #include "sharedialog.h"
#include "sharee.h" #include "sharee.h"
#include "sharelinkwidget.h" #include "sharelinkwidget.h"
#include "internallinkwidget.h"
#include "shareusergroupwidget.h" #include "shareusergroupwidget.h"
#include "passwordinputdialog.h"
#include "sharemanager.h" #include "sharemanager.h"
@@ -137,24 +135,28 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError); connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError);
job->start(); job->start();
initShareManager(); bool sharingPossible = true;
if (!accountState->account()->capabilities().sharePublicLink()) {
_scrollAreaViewPort = new QWidget(_ui->scrollArea); qCWarning(lcSharing) << "Link shares have been disabled";
_scrollAreaLayout = new QVBoxLayout(_scrollAreaViewPort); sharingPossible = false;
_scrollAreaLayout->setContentsMargins(0, 0, 0, 0); } else if (!(maxSharingPermissions & SharePermissionShare)) {
_ui->scrollArea->setWidget(_scrollAreaViewPort); qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
sharingPossible = false;
}
_internalLinkWidget = new InternalLinkWidget(localPath, this); if (sharingPossible) {
_ui->verticalLayout->addWidget(_internalLinkWidget); _manager = new ShareManager(accountState->account(), this);
_internalLinkWidget->setupUiOptions(); connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
connect(this, &ShareDialog::styleChanged, _internalLinkWidget, &InternalLinkWidget::slotStyleChanged); connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
}
} }
ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare) ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
{ {
const auto linkShareWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea); _linkWidgetList.append(new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this));
_linkWidgetList.append(linkShareWidget);
const auto linkShareWidget = _linkWidgetList.at(_linkWidgetList.size() - 1);
linkShareWidget->setLinkShare(linkShare); linkShareWidget->setLinkShare(linkShare);
connect(linkShare.data(), &Share::serverError, linkShareWidget, &ShareLinkWidget::slotServerError); connect(linkShare.data(), &Share::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
@@ -169,13 +171,13 @@ ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare>
connect(linkShareWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare); connect(linkShareWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
connect(linkShareWidget, &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare); connect(linkShareWidget, &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
connect(linkShareWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare); connect(linkShareWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
//connect(_linkWidgetList.at(index), &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareDialog::styleChanged, linkShareWidget, &ShareLinkWidget::slotStyleChanged); connect(this, &ShareDialog::styleChanged, linkShareWidget, &ShareLinkWidget::slotStyleChanged);
_ui->verticalLayout->insertWidget(_linkWidgetList.size() + 1, linkShareWidget); _ui->verticalLayout->insertWidget(_linkWidgetList.size() + 1, linkShareWidget);
_scrollAreaLayout->addWidget(linkShareWidget);
linkShareWidget->setupUiOptions(); linkShareWidget->setupUiOptions();
return linkShareWidget; return linkShareWidget;
@@ -184,17 +186,18 @@ ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare>
void ShareDialog::initLinkShareWidget() void ShareDialog::initLinkShareWidget()
{ {
if(_linkWidgetList.size() == 0) { if(_linkWidgetList.size() == 0) {
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea); _emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
_linkWidgetList.append(_emptyShareLinkWidget); _linkWidgetList.append(_emptyShareLinkWidget);
connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
connect(this, &ShareDialog::toggleShareLinkAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleShareLinkAnimation); connect(this, &ShareDialog::toggleShareLinkAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare); connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
connect(_emptyShareLinkWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare); connect(_emptyShareLinkWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
_ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget); _ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget);
_scrollAreaLayout->addWidget(_emptyShareLinkWidget);
_emptyShareLinkWidget->show(); _emptyShareLinkWidget->show();
} else if (_emptyShareLinkWidget) { } else if(_emptyShareLinkWidget) {
_emptyShareLinkWidget->hide(); _emptyShareLinkWidget->hide();
_ui->verticalLayout->removeWidget(_emptyShareLinkWidget); _ui->verticalLayout->removeWidget(_emptyShareLinkWidget);
_linkWidgetList.removeAll(_emptyShareLinkWidget); _linkWidgetList.removeAll(_emptyShareLinkWidget);
@@ -207,7 +210,6 @@ void ShareDialog::slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkSh
emit toggleShareLinkAnimation(true); emit toggleShareLinkAnimation(true);
const auto addedLinkShareWidget = addLinkShareWidget(linkShare); const auto addedLinkShareWidget = addLinkShareWidget(linkShare);
initLinkShareWidget(); initLinkShareWidget();
adjustScrollWidgetSize();
if (linkShare->isPasswordSet()) { if (linkShare->isPasswordSet()) {
addedLinkShareWidget->focusPasswordLineEdit(); addedLinkShareWidget->focusPasswordLineEdit();
} }
@@ -220,7 +222,6 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
const QString versionString = _accountState->account()->serverVersion(); const QString versionString = _accountState->account()->serverVersion();
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares"; qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
foreach (auto share, shares) { foreach (auto share, shares) {
if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) { if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
continue; continue;
@@ -231,20 +232,17 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
} }
initLinkShareWidget(); initLinkShareWidget();
adjustScrollWidgetSize();
emit toggleShareLinkAnimation(false); emit toggleShareLinkAnimation(false);
} }
void ShareDialog::adjustScrollWidgetSize() void ShareDialog::slotAdjustScrollWidgetSize()
{ {
const auto count = _scrollAreaLayout->count(); int count = this->findChildren<ShareLinkWidget *>().count();
const auto margin = 10; _ui->scrollArea->setVisible(count > 0);
const auto height = _linkWidgetList.empty() ? 0 : _linkWidgetList.last()->sizeHint().height() + margin; if (count > 0 && count <= 3) {
const auto totalHeight = height * count; _ui->scrollArea->setFixedHeight(_ui->scrollArea->widget()->sizeHint().height());
_ui->scrollArea->setFixedWidth(_ui->verticalLayout->sizeHint().width()); }
_ui->scrollArea->setFixedHeight(totalHeight > 400 ? 400 : totalHeight); _ui->scrollArea->setFrameShape(count > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
_ui->scrollArea->setVisible(height > 0);
_ui->scrollArea->setFrameShape(count > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
} }
ShareDialog::~ShareDialog() ShareDialog::~ShareDialog()
@@ -305,19 +303,21 @@ void ShareDialog::showSharingUi()
return; return;
} }
if (theme->userGroupSharing()) { // We only do user/group sharing from 8.2.0
_userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, _ui->scrollArea); bool userGroupSharing =
_userGroupWidget->getShares(); theme->userGroupSharing()
&& _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0);
if (userGroupSharing) {
_userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged); connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged);
_ui->verticalLayout->insertWidget(1, _userGroupWidget); _ui->verticalLayout->insertWidget(1, _userGroupWidget);
_scrollAreaLayout->addLayout(_userGroupWidget->shareUserGroupLayout()); _userGroupWidget->getShares();
} }
initShareManager();
if (theme->linkSharing()) { if (theme->linkSharing()) {
if(_manager) { if(_manager) {
_manager->fetchShares(_sharePath); _manager->fetchShares(_sharePath);
@@ -325,25 +325,6 @@ void ShareDialog::showSharingUi()
} }
} }
void ShareDialog::initShareManager()
{
bool sharingPossible = true;
if (!_accountState->account()->capabilities().sharePublicLink()) {
qCWarning(lcSharing) << "Link shares have been disabled";
sharingPossible = false;
} else if (!(_maxSharingPermissions & SharePermissionShare)) {
qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
sharingPossible = false;
}
if (!_manager && sharingPossible) {
_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);
}
}
void ShareDialog::slotCreateLinkShare() void ShareDialog::slotCreateLinkShare()
{ {
if(_manager) { if(_manager) {
@@ -378,21 +359,26 @@ void ShareDialog::slotCreatePasswordForLinkShareProcessed()
} }
} }
void ShareDialog::slotLinkShareRequiresPassword(const QString &message) void ShareDialog::slotLinkShareRequiresPassword()
{ {
const auto passwordInputDialog = new PasswordInputDialog(tr("Please enter a password for your link share:"), message, this); bool ok = false;
passwordInputDialog->setWindowTitle(tr("Password for share required")); QString password = QInputDialog::getText(this,
passwordInputDialog->setAttribute(Qt::WA_DeleteOnClose); tr("Password for share required"),
passwordInputDialog->open(); tr("Please enter a password for your link share:"),
QLineEdit::Password,
QString(),
&ok);
connect(passwordInputDialog, &QDialog::finished, this, [this, passwordInputDialog](const int result) { if (!ok) {
if (result == QDialog::Accepted && _manager) { // The dialog was canceled so no need to do anything
// Try to create the link share again with the newly entered password
_manager->createLinkShare(_sharePath, QString(), passwordInputDialog->password());
return;
}
emit toggleShareLinkAnimation(false); emit toggleShareLinkAnimation(false);
}); return;
}
if(_manager) {
// Try to create the link share again with the newly entered password
_manager->createLinkShare(_sharePath, QString(), password);
}
} }
void ShareDialog::slotDeleteShare() void ShareDialog::slotDeleteShare()
@@ -400,10 +386,8 @@ void ShareDialog::slotDeleteShare()
auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender()); auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender());
sharelinkWidget->hide(); sharelinkWidget->hide();
_ui->verticalLayout->removeWidget(sharelinkWidget); _ui->verticalLayout->removeWidget(sharelinkWidget);
_scrollAreaLayout->removeWidget(sharelinkWidget);
_linkWidgetList.removeAll(sharelinkWidget); _linkWidgetList.removeAll(sharelinkWidget);
initLinkShareWidget(); initLinkShareWidget();
adjustScrollWidgetSize();
} }
void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply) void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)

View File

@@ -26,7 +26,6 @@
#include <QWidget> #include <QWidget>
class QProgressIndicator; class QProgressIndicator;
class QVBoxLayout;
namespace OCC { namespace OCC {
@@ -35,7 +34,6 @@ namespace Ui {
} }
class ShareLinkWidget; class ShareLinkWidget;
class InternalLinkWidget;
class ShareUserGroupWidget; class ShareUserGroupWidget;
class ShareManager; class ShareManager;
class LinkShare; class LinkShare;
@@ -68,7 +66,8 @@ private slots:
void slotCreateLinkShare(); void slotCreateLinkShare();
void slotCreatePasswordForLinkShare(const QString &password); void slotCreatePasswordForLinkShare(const QString &password);
void slotCreatePasswordForLinkShareProcessed(); void slotCreatePasswordForLinkShareProcessed();
void slotLinkShareRequiresPassword(const QString &message); void slotLinkShareRequiresPassword();
void slotAdjustScrollWidgetSize();
signals: signals:
void toggleShareLinkAnimation(bool start); void toggleShareLinkAnimation(bool start);
@@ -79,10 +78,8 @@ protected:
private: private:
void showSharingUi(); void showSharingUi();
void initShareManager();
ShareLinkWidget *addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare); ShareLinkWidget *addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
void initLinkShareWidget(); void initLinkShareWidget();
void adjustScrollWidgetSize();
Ui::ShareDialog *_ui; Ui::ShareDialog *_ui;
@@ -97,12 +94,8 @@ private:
QList<ShareLinkWidget*> _linkWidgetList; QList<ShareLinkWidget*> _linkWidgetList;
ShareLinkWidget* _emptyShareLinkWidget = nullptr; ShareLinkWidget* _emptyShareLinkWidget = nullptr;
InternalLinkWidget* _internalLinkWidget = nullptr;
ShareUserGroupWidget *_userGroupWidget = nullptr; ShareUserGroupWidget *_userGroupWidget = nullptr;
QProgressIndicator *_progressIndicator = nullptr; QProgressIndicator *_progressIndicator = nullptr;
QWidget *_scrollAreaViewPort = nullptr;
QVBoxLayout *_scrollAreaLayout = nullptr;
}; };
} // namespace OCC } // namespace OCC

View File

@@ -6,36 +6,18 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>480</width> <width>385</width>
<height>280</height> <height>150</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>480</width>
<height>250</height>
</size>
</property>
<layout class="QVBoxLayout" name="shareDialogVerticalLayout"> <layout class="QVBoxLayout" name="shareDialogVerticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum> <enum>QLayout::SetFixedSize</enum>
</property> </property>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum> <enum>QLayout::SetFixedSize</enum>
</property> </property>
<item> <item>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0"> <layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
@@ -51,31 +33,6 @@
<property name="spacing"> <property name="spacing">
<number>10</number> <number>10</number>
</property> </property>
<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>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="label_name"> <widget class="QLabel" name="label_name">
<property name="sizePolicy"> <property name="sizePolicy">
@@ -132,36 +89,49 @@
</property> </property>
</widget> </widget>
</item> </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> </layout>
</item> </item>
<item> <item>
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::NoFrame</enum> <enum>QFrame::NoFrame</enum>
</property> </property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="horizontalScrollBarPolicy"> <property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="sizeAdjustPolicy"> <property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum> <enum>QAbstractScrollArea::AdjustIgnored</enum>
</property> </property>
<property name="widgetResizable"> <property name="widgetResizable">
<bool>true</bool> <bool>true</bool>
@@ -171,10 +141,11 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>460</width> <width>69</width>
<height>200</height> <height>69</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
</widget> </widget>
</widget> </widget>
</item> </item>

View File

@@ -411,6 +411,22 @@ void ShareLinkWidget::slotPasswordSetError(const int code, const QString &messag
emit createPasswordProcessed(); emit createPasswordProcessed();
} }
void ShareLinkWidget::startAnimation(const int start, const int end)
{
auto *animation = new QPropertyAnimation(this, "maximumHeight", this);
animation->setDuration(500);
animation->setStartValue(start);
animation->setEndValue(end);
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
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);
animation->start();
}
void ShareLinkWidget::slotDeleteShareFetched() void ShareLinkWidget::slotDeleteShareFetched()
{ {
slotToggleShareLinkAnimation(false); slotToggleShareLinkAnimation(false);
@@ -434,6 +450,12 @@ void ShareLinkWidget::toggleNoteOptions(const bool enable)
} }
} }
void ShareLinkWidget::slotAnimationFinished()
{
emit resizeRequested();
deleteLater();
}
void ShareLinkWidget::slotCreateLabel() void ShareLinkWidget::slotCreateLabel()
{ {
const auto labelText = _shareLinkEdit->text(); const auto labelText = _shareLinkEdit->text();
@@ -452,6 +474,14 @@ void ShareLinkWidget::slotLabelSet()
displayShareLinkLabel(); displayShareLinkLabel();
} }
void ShareLinkWidget::slotDeleteAnimationFinished()
{
// There is a painting bug where a small line of this widget isn't
// properly cleared. This explicit repaint() call makes sure any trace of
// the share widget is removed once it's destroyed. #4189
connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
}
void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message) void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
{ {
slotToggleShareLinkAnimation(message.isEmpty()); slotToggleShareLinkAnimation(message.isEmpty());

View File

@@ -90,6 +90,9 @@ private slots:
void slotContextMenuButtonClicked(); void slotContextMenuButtonClicked();
void slotLinkContextMenuActionTriggered(QAction *action); void slotLinkContextMenuActionTriggered(QAction *action);
void slotDeleteAnimationFinished();
void slotAnimationFinished();
void slotCreateLabel(); void slotCreateLabel();
void slotLabelSet(); void slotLabelSet();
@@ -97,6 +100,7 @@ private slots:
signals: signals:
void createLinkShare(); void createLinkShare();
void deleteLinkShare(); void deleteLinkShare();
void resizeRequested();
void visualDeletionDone(); void visualDeletionDone();
void createPassword(const QString &password); void createPassword(const QString &password);
void createPasswordProcessed(); void createPasswordProcessed();
@@ -115,6 +119,8 @@ private:
/** Retrieve a share's name, accounting for _namesSupported */ /** Retrieve a share's name, accounting for _namesSupported */
QString shareName() const; QString shareName() const;
void startAnimation(const int start, const int end);
void customizeStyle(); void customizeStyle();
void displayShareLinkLabel(); void displayShareLinkLabel();

View File

@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>365</width>
<height>238</height> <height>238</height>
</rect> </rect>
</property> </property>
@@ -17,29 +17,8 @@
</sizepolicy> </sizepolicy>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item> <item>
<widget class="QLabel" name="shareLinkIconLabel"> <widget class="QLabel" name="shareLinkIconLabel">
<property name="text"> <property name="text">

View File

@@ -154,18 +154,16 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
_completionTimer.setInterval(600); _completionTimer.setInterval(600);
_ui->errorLabel->hide(); _ui->errorLabel->hide();
// TODO Progress Indicator where should it go?
// Setup the sharee search progress indicator
//_ui->shareeHorizontalLayout->addWidget(&_pi_sharee);
_parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea"); _parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea");
_shareUserGroup = new QVBoxLayout(_parentScrollArea);
_shareUserGroup->setContentsMargins(0, 0, 0, 0);
customizeStyle(); customizeStyle();
} }
QVBoxLayout *ShareUserGroupWidget::shareUserGroupLayout()
{
return _shareUserGroup;
}
ShareUserGroupWidget::~ShareUserGroupWidget() ShareUserGroupWidget::~ShareUserGroupWidget()
{ {
delete _ui; delete _ui;
@@ -249,16 +247,17 @@ void ShareUserGroupWidget::slotShareCreated(const QSharedPointer<Share> &share)
void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares) void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
{ {
QScrollArea *scrollArea = _parentScrollArea;
auto newViewPort = new QWidget(scrollArea);
auto layout = new QVBoxLayout(newViewPort);
layout->setContentsMargins(0, 0, 0, 0);
int x = 0; int x = 0;
int height = 0;
QList<QString> linkOwners({}); QList<QString> linkOwners({});
ShareUserLine *justCreatedShareThatNeedsPassword = nullptr; ShareUserLine *justCreatedShareThatNeedsPassword = nullptr;
while (QLayoutItem *shareUserLine = _shareUserGroup->takeAt(0)) {
delete shareUserLine->widget();
delete shareUserLine;
}
foreach (const auto &share, shares) { foreach (const auto &share, shares) {
// We don't handle link shares, only TypeUser or TypeGroup // We don't handle link shares, only TypeUser or TypeGroup
if (share->getShareType() == Share::TypeLink) { if (share->getShareType() == Share::TypeLink) {
@@ -279,12 +278,14 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
Q_ASSERT(Share::isShareTypeUserGroupEmailRoomOrRemote(share->getShareType())); Q_ASSERT(Share::isShareTypeUserGroupEmailRoomOrRemote(share->getShareType()));
auto userGroupShare = qSharedPointerDynamicCast<UserGroupShare>(share); auto userGroupShare = qSharedPointerDynamicCast<UserGroupShare>(share);
auto *s = new ShareUserLine(_account, userGroupShare, _maxSharingPermissions, _isFile, _parentScrollArea); auto *s = new ShareUserLine(_account, userGroupShare, _maxSharingPermissions, _isFile, _parentScrollArea);
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares); connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
s->setBackgroundRole(_shareUserGroup->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase); s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching) // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged); connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged);
_shareUserGroup->addWidget(s);
layout->addWidget(s);
if (!_lastCreatedShareId.isEmpty() && share->getId() == _lastCreatedShareId) { if (!_lastCreatedShareId.isEmpty() && share->getId() == _lastCreatedShareId) {
_lastCreatedShareId = QString(); _lastCreatedShareId = QString();
@@ -294,14 +295,27 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
} }
x++; x++;
if (x <= 3) {
height = newViewPort->sizeHint().height();
}
} }
foreach (const QString &owner, linkOwners) { foreach (const QString &owner, linkOwners) {
auto ownerLabel = new QLabel(QString(owner + " shared via link")); auto ownerLabel = new QLabel(QString(owner + " shared via link"));
_shareUserGroup->addWidget(ownerLabel); layout->addWidget(ownerLabel);
ownerLabel->setVisible(true); ownerLabel->setVisible(true);
x++;
if (x <= 6) {
height = newViewPort->sizeHint().height();
}
} }
scrollArea->setFrameShape(x > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
scrollArea->setVisible(!shares.isEmpty());
scrollArea->setFixedHeight(height);
scrollArea->setWidget(newViewPort);
_disableCompleterActivated = false; _disableCompleterActivated = false;
activateShareeLineEdit(); activateShareeLineEdit();
@@ -311,6 +325,24 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
} }
} }
void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
{
QScrollArea *scrollArea = _parentScrollArea;
const auto shareUserLineChilds = scrollArea->findChildren<ShareUserLine *>();
// Ask the child widgets to calculate their size
for (const auto shareUserLineChild : shareUserLineChilds) {
shareUserLineChild->adjustSize();
}
const auto shareUserLineChildsCount = shareUserLineChilds.count();
scrollArea->setVisible(shareUserLineChildsCount > 0);
if (shareUserLineChildsCount > 0 && shareUserLineChildsCount <= 3) {
scrollArea->setFixedHeight(scrollArea->widget()->sizeHint().height());
}
scrollArea->setFrameShape(shareUserLineChildsCount > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
}
void ShareUserGroupWidget::slotPrivateLinkShare() void ShareUserGroupWidget::slotPrivateLinkShare()
{ {
auto menu = new QMenu(this); auto menu = new QMenu(this);
@@ -348,11 +380,22 @@ void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index)
return; return;
} }
// TODO Progress Indicator where should it go?
// auto indicator = new QProgressIndicator(viewPort);
// indicator->startAnimation();
// if (layout->count() == 1) {
// // No shares yet! Remove the label, add some stretch.
// delete layout->itemAt(0)->widget();
// layout->addStretch(1);
// }
// layout->insertWidget(layout->count() - 1, indicator);
/* /*
* Don't send the reshare permissions for federated shares for servers <9.1 * Don't send the reshare permissions for federated shares for servers <9.1
* https://github.com/owncloud/core/issues/22122#issuecomment-185637344 * https://github.com/owncloud/core/issues/22122#issuecomment-185637344
* https://github.com/owncloud/client/issues/4996 * https://github.com/owncloud/client/issues/4996
*/ */
_lastCreatedShareId = QString(); _lastCreatedShareId = QString();
QString password; QString password;

View File

@@ -77,8 +77,6 @@ public:
const QString &privateLinkUrl, const QString &privateLinkUrl,
QWidget *parent = nullptr); QWidget *parent = nullptr);
~ShareUserGroupWidget() override; ~ShareUserGroupWidget() override;
QVBoxLayout *shareUserGroupLayout();
signals: signals:
void togglePublicLinkShare(bool); void togglePublicLinkShare(bool);
@@ -100,6 +98,7 @@ private slots:
void slotCompleterActivated(const QModelIndex &index); void slotCompleterActivated(const QModelIndex &index);
void slotCompleterHighlighted(const QModelIndex &index); void slotCompleterHighlighted(const QModelIndex &index);
void slotShareesReady(); void slotShareesReady();
void slotAdjustScrollWidgetSize();
void slotPrivateLinkShare(); void slotPrivateLinkShare();
void displayError(int code, const QString &message); void displayError(int code, const QString &message);
@@ -114,7 +113,6 @@ private:
Ui::ShareUserGroupWidget *_ui; Ui::ShareUserGroupWidget *_ui;
QScrollArea *_parentScrollArea; QScrollArea *_parentScrollArea;
QVBoxLayout *_shareUserGroup;
AccountPtr _account; AccountPtr _account;
QString _sharePath; QString _sharePath;
QString _localPath; QString _localPath;

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>350</width> <width>350</width>
<height>106</height> <height>70</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@@ -17,21 +17,6 @@
</sizepolicy> </sizepolicy>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item> <item>
<widget class="QLabel" name="mainOwnerLabel"> <widget class="QLabel" name="mainOwnerLabel">
<property name="sizePolicy"> <property name="sizePolicy">

View File

@@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>899</width>
<height>310</height> <height>310</height>
</rect> </rect>
</property> </property>
@@ -26,26 +26,8 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>20</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>0</number>
</property> </property>
@@ -93,11 +75,11 @@
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Minimum</enum> <enum>QSizePolicy::Expanding</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>20</width> <width>40</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>

View File

@@ -1,65 +1,109 @@
import QtQuick 2.15 import QtQuick 2.5
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.15
import Style 1.0 import Style 1.0
Item { Item {
id: root id: root
readonly property bool labelVisible: label.visible
readonly property bool iconVisible: icon.visible
// label value
property string text: "" property string text: ""
property string toolTipText: ""
// font value
property bool bold: false property var font: label.font
// icon value
property string imageSource: "" property string imageSource: ""
property string imageSourceHover: ""
property color textColor: Style.unifiedSearchResulTitleColor // Tooltip value
property color textColorHovered: Style.unifiedSearchResulSublineColor property string tooltipText: text
// text color
property color textColor: Style.ncTextColor
property color textColorHovered: Style.lightHover
// text background color
property color textBgColor: "transparent"
property color textBgColorHovered: Style.lightHover
// icon background color
property color iconBgColor: "transparent"
property color iconBgColorHovered: Style.lightHover
// text border color
property color textBorderColor: "transparent"
property alias hovered: mouseArea.containsMouse
signal clicked() signal clicked()
Loader { Accessible.role: Accessible.Button
active: root.imageSource === "" Accessible.name: text !== "" ? text : (tooltipText !== "" ? tooltipText : qsTr("Activity action button"))
Accessible.onPressAction: clicked()
// background with border around the Text
Rectangle {
visible: parent.labelVisible
anchors.fill: parent anchors.fill: parent
sourceComponent: CustomTextButton { // padding
anchors.fill: parent anchors.topMargin: 10
text: root.text anchors.bottomMargin: 10
toolTipText: root.toolTipText
textColor: root.textColor border.color: parent.textBorderColor
textColorHovered: root.textColorHovered border.width: 1
onClicked: root.clicked() color: parent.hovered ? parent.textBgColorHovered : parent.textBgColor
}
radius: 25
} }
Loader { // background with border around the Image
active: root.imageSource !== "" Rectangle {
visible: parent.iconVisible
anchors.fill: parent anchors.fill: parent
sourceComponent: CustomButton { color: parent.hovered ? parent.iconBgColorHovered : parent.iconBgColor
anchors.fill: parent }
anchors.topMargin: Style.roundedButtonBackgroundVerticalMargins
anchors.bottomMargin: Style.roundedButtonBackgroundVerticalMargins
text: root.text // label
toolTipText: root.toolTipText Text {
id: label
visible: parent.text !== ""
text: parent.text
font: parent.font
color: parent.hovered ? parent.textColorHovered : parent.textColor
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
textColor: root.textColor // icon
textColorHovered: root.textColorHovered Image {
id: icon
visible: parent.imageSource !== ""
anchors.centerIn: parent
source: parent.imageSource
sourceSize.width: visible ? 32 : 0
sourceSize.height: visible ? 32 : 0
}
bold: root.bold MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: parent.clicked()
hoverEnabled: true
}
imageSource: root.imageSource ToolTip {
imageSourceHover: root.imageSourceHover text: parent.tooltipText
delay: 1000
bgColor: Style.ncBlue visible: text != "" && parent.hovered
onClicked: root.clicked()
}
} }
} }

View File

@@ -1,84 +1,260 @@
import QtQml 2.15 import QtQml 2.12
import QtQuick 2.15 import QtQuick 2.9
import QtQuick.Controls 2.15 import QtQuick.Controls 2.2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.2
import Style 1.0 import Style 1.0
import com.nextcloud.desktopclient 1.0 import com.nextcloud.desktopclient 1.0
MouseArea { MouseArea {
id: root id: activityMouseArea
readonly property int maxActionButtons: 2
property Flickable flickable property Flickable flickable
property bool isFileActivityList: false
property bool isChatActivity: model.objectType === "chat" || model.objectType === "room"
signal fileActivityButtonClicked(string absolutePath) signal fileActivityButtonClicked(string absolutePath)
enabled: (model.path !== "" || model.link !== "" || model.isCurrentUserFileActivity === true) enabled: (path !== "" || link !== "")
hoverEnabled: true hoverEnabled: true
height: childrenRect.height
ToolTip.visible: containsMouse && !activityContent.childHovered && model.displayLocation !== ""
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("In %1").arg(model.displayLocation)
Accessible.role: Accessible.ListItem
Accessible.name: (model.path !== "" && model.displayPath !== "") ? qsTr("Open %1 locally").arg(model.displayPath) : model.message
Accessible.onPressAction: root.clicked()
Rectangle { Rectangle {
id: activityHover
anchors.fill: parent anchors.fill: parent
color: (parent.containsMouse ? Style.lightHover : "transparent") color: (parent.containsMouse ? Style.lightHover : "transparent")
} }
ColumnLayout { RowLayout {
anchors.left: root.left id: activityItem
anchors.right: root.right
anchors.leftMargin: 15 readonly property variant links: model.links
anchors.rightMargin: 10
readonly property int itemIndex: model.index
width: activityMouseArea.width
height: Style.trayWindowHeaderHeight
spacing: 0 spacing: 0
ActivityItemContent { Accessible.role: Accessible.ListItem
id: activityContent Accessible.name: path !== "" ? qsTr("Open %1 locally").arg(displayPath)
: message
Layout.fillWidth: true Accessible.onPressAction: activityMouseArea.clicked()
showDismissButton: model.links.length > 0 && model.linksForActionButtons.length === 0 Image {
id: activityIcon
activityData: model Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.leftMargin: 20
Layout.preferredHeight: Style.trayWindowHeaderHeight Layout.preferredWidth: shareButton.icon.width
Layout.preferredHeight: shareButton.icon.height
onShareButtonClicked: Systray.openShareDialog(model.displayPath, model.absolutePath) verticalAlignment: Qt.AlignCenter
onDismissButtonClicked: activityModel.slotTriggerDismiss(model.index) cache: true
source: icon
sourceSize.height: 64
sourceSize.width: 64
} }
ActivityItemActions { Column {
id: activityActions id: activityTextColumn
Layout.leftMargin: 14
visible: !root.isFileActivityList && model.linksForActionButtons.length > 0 Layout.topMargin: 4
Layout.bottomMargin: 4
Layout.preferredHeight: Style.trayWindowHeaderHeight * 0.85
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 40 spacing: 4
Layout.bottomMargin: model.links.length > 1 ? 5 : 0 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Text {
id: activityTextTitle
text: (type === "Activity" || type === "Notification") ? subject : message
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.topLinePixelSize
color: activityTextTitleColor
}
Text {
id: activityTextInfo
text: (type === "Sync") ? displayPath
: (type === "File") ? subject
: (type === "Notification") ? message
: ""
height: (text === "") ? 0 : activityTextTitle.height
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.subLinePixelSize
}
Text {
id: activityTextDateTime
text: dateTime
height: (text === "") ? 0 : activityTextTitle.height
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.subLinePixelSize
color: "#808080"
}
}
RowLayout {
id: activityActionsLayout
spacing: 0
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.minimumWidth: 28
Layout.fillWidth: true
function actionButtonIcon(actionIndex) {
const verb = String(model.links[actionIndex].verb);
if (verb === "WEB" && (model.objectType === "chat" || model.objectType === "call")) {
return "qrc:///client/theme/reply.svg";
} else if (verb === "DELETE") {
return "qrc:///client/theme/close.svg";
}
return "qrc:///client/theme/confirm.svg";
}
Repeater {
model: activityItem.links.length > maxActionButtons ? 1 : activityItem.links.length
ActivityActionButton {
id: activityActionButton
readonly property int actionIndex: model.index
readonly property bool primary: model.index === 0 && String(activityItem.links[actionIndex].verb) !== "DELETE"
Layout.fillHeight: true
text: !primary ? "" : activityItem.links[actionIndex].label
imageSource: !primary ? activityActionsLayout.actionButtonIcon(actionIndex) : ""
textColor: primary ? Style.ncBlue : "black"
textColorHovered: Style.lightHover
textBorderColor: Style.ncBlue
textBgColor: "transparent"
textBgColorHovered: Style.ncBlue
tooltipText: activityItem.links[actionIndex].label
Layout.minimumWidth: primary ? 80 : -1
Layout.minimumHeight: parent.height
Layout.preferredWidth: primary ? -1 : parent.height
onClicked: activityModel.triggerAction(activityItem.itemIndex, actionIndex)
}
}
displayActions: model.displayActions Button {
objectType: model.objectType id: shareButton
linksForActionButtons: model.linksForActionButtons
linksContextMenu: model.linksContextMenu Layout.preferredWidth: parent.height
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight
flat: true
hoverEnabled: true
visible: displayActions && (path !== "")
display: AbstractButton.IconOnly
icon.source: "qrc:///client/theme/share.svg"
icon.color: "transparent"
background: Rectangle {
color: parent.hovered ? Style.lightHover : "transparent"
}
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Open share dialog")
onClicked: Systray.openShareDialog(displayPath, absolutePath)
Accessible.role: Accessible.Button
Accessible.name: qsTr("Share %1").arg(displayPath)
Accessible.onPressAction: shareButton.clicked()
}
Button {
id: moreActionsButton
Layout.preferredWidth: parent.height
Layout.preferredHeight: parent.height
Layout.alignment: Qt.AlignRight
flat: true
hoverEnabled: true
visible: displayActions && ((path !== "") || (activityItem.links.length > maxActionButtons))
display: AbstractButton.IconOnly
icon.source: "qrc:///client/theme/more.svg"
icon.color: "transparent"
background: Rectangle {
color: parent.hovered ? Style.lightHover : "transparent"
}
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Show more actions")
Accessible.role: Accessible.Button
Accessible.name: qsTr("Show more actions")
Accessible.onPressAction: moreActionsButton.clicked()
onClicked: moreActionsButtonContextMenu.popup();
Connections {
target: flickable
function onMovementStarted() {
moreActionsButtonContextMenu.close();
}
}
Container {
id: moreActionsButtonContextMenuContainer
visible: moreActionsButtonContextMenu.opened
width: moreActionsButtonContextMenu.width
height: moreActionsButtonContextMenu.height
anchors.right: moreActionsButton.right
anchors.top: moreActionsButton.top
Menu {
id: moreActionsButtonContextMenu
anchors.centerIn: parent
// transform model to contain indexed actions with primary action filtered out
function actionListToContextMenuList(actionList) {
// early out with non-altered data
if (activityItem.links.length <= maxActionButtons) {
return actionList;
}
// add index to every action and filter 'primary' action out
var reducedActionList = actionList.reduce(function(reduced, action, index) {
if (!action.primary) {
var actionWithIndex = { actionIndex: index, label: action.label };
reduced.push(actionWithIndex);
}
return reduced;
}, []);
return reducedActionList;
}
moreActionsButtonColor: activityHover.color MenuItem {
maxActionButtons: activityModel.maxActionButtons text: qsTr("View activity")
onClicked: fileActivityButtonClicked(absolutePath)
flickable: root.flickable }
onTriggerAction: activityModel.slotTriggerAction(model.index, actionIndex) Repeater {
id: moreActionsButtonContextMenuRepeater
model: moreActionsButtonContextMenu.actionListToContextMenuList(activityItem.links)
delegate: MenuItem {
id: moreActionsButtonContextMenuEntry
text: model.modelData.label
onTriggered: activityModel.triggerAction(activityItem.itemIndex, model.modelData.actionIndex)
}
}
}
}
}
} }
} }
} }

View File

@@ -1,103 +0,0 @@
import QtQml 2.15
import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import Style 1.0
RowLayout {
id: root
spacing: 20
property string objectType: ""
property variant linksForActionButtons: []
property variant linksContextMenu: []
property bool displayActions: false
property color moreActionsButtonColor: "transparent"
property int maxActionButtons: 0
property Flickable flickable
signal triggerAction(int actionIndex)
Repeater {
id: actionsRepeater
// a max of maxActionButtons will get dispayed as separate buttons
model: root.linksForActionButtons
ActivityActionButton {
id: activityActionButton
readonly property bool primary: model.index === 0 && model.modelData.verb !== "DELETE"
Layout.minimumWidth: primary ? Style.activityItemActionPrimaryButtonMinWidth : Style.activityItemActionSecondaryButtonMinWidth
Layout.preferredHeight: primary ? parent.height : parent.height * 0.3
Layout.preferredWidth: primary ? -1 : parent.height
text: model.modelData.label
toolTipText: model.modelData.label
imageSource: model.modelData.imageSource
imageSourceHover: model.modelData.imageSourceHovered
textColor: imageSource !== "" ? Style.ncBlue : Style.unifiedSearchResulSublineColor
textColorHovered: imageSource !== "" ? Style.lightHover : Style.unifiedSearchResulTitleColor
bold: primary
onClicked: root.triggerAction(model.index)
}
}
Loader {
// actions that do not fit maxActionButtons limit, must be put into a context menu
id: moreActionsButtonContainer
Layout.preferredWidth: parent.height
Layout.topMargin: Style.roundedButtonBackgroundVerticalMargins
Layout.bottomMargin: Style.roundedButtonBackgroundVerticalMargins
Layout.fillHeight: true
active: root.displayActions && (root.linksContextMenu.length > 0)
sourceComponent: Button {
id: moreActionsButton
icon.source: "qrc:///client/theme/more.svg"
background: Rectangle {
color: parent.hovered ? "white" : root.moreActionsButtonColor
radius: width / 2
}
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Show more actions")
Accessible.name: qsTr("Show more actions")
onClicked: moreActionsButtonContextMenu.popup(moreActionsButton.x, moreActionsButton.y);
Connections {
target: root.flickable
function onMovementStarted() {
moreActionsButtonContextMenu.close();
}
}
ActivityItemContextMenu {
id: moreActionsButtonContextMenu
maxActionButtons: root.maxActionButtons
linksContextMenu: root.linksContextMenu
onMenuEntryTriggered: function(entryIndex) {
root.triggerAction(entryIndex)
}
}
}
}
}

View File

@@ -1,127 +0,0 @@
import QtQml 2.15
import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import Style 1.0
import com.nextcloud.desktopclient 1.0
RowLayout {
id: root
property variant activityData: {{}}
property color activityTextTitleColor: Style.ncTextColor
property bool showDismissButton: false
property bool childHovered: shareButton.hovered || dismissActionButton.hovered
signal dismissButtonClicked()
signal shareButtonClicked()
spacing: 10
Image {
id: activityIcon
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.preferredWidth: 32
Layout.preferredHeight: 32
verticalAlignment: Qt.AlignCenter
source: icon
sourceSize.height: 64
sourceSize.width: 64
}
Column {
id: activityTextColumn
Layout.topMargin: 4
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
spacing: 4
Label {
id: activityTextTitle
text: (root.activityData.type === "Activity" || root.activityData.type === "Notification") ? root.activityData.subject : root.activityData.message
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.topLinePixelSize
color: root.activityData.activityTextTitleColor
}
Label {
id: activityTextInfo
text: (root.activityData.type === "Sync") ? root.activityData.displayPath
: (root.activityData.type === "File") ? root.activityData.subject
: (root.activityData.type === "Notification") ? root.activityData.message
: ""
height: (text === "") ? 0 : activityTextTitle.height
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.subLinePixelSize
}
Label {
id: activityTextDateTime
text: root.activityData.dateTime
height: (text === "") ? 0 : activityTextTitle.height
width: parent.width
elide: Text.ElideRight
font.pixelSize: Style.subLinePixelSize
color: "#808080"
}
}
Button {
id: dismissActionButton
Layout.preferredWidth: parent.height * 0.40
Layout.preferredHeight: parent.height * 0.40
Layout.alignment: Qt.AlignCenter
Layout.margins: Style.roundButtonBackgroundVerticalMargins
ToolTip.visible: hovered
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
ToolTip.text: qsTr("Dismiss")
Accessible.name: qsTr("Dismiss")
visible: root.showDismissButton && !shareButton.visible
background: Rectangle {
color: "transparent"
}
contentItem: Image {
anchors.fill: parent
source: parent.hovered ? "image://svgimage-custom-color/clear.svg/black" : "image://svgimage-custom-color/clear.svg/grey"
sourceSize.width: 24
sourceSize.height: 24
}
onClicked: root.dismissButtonClicked()
}
CustomButton {
id: shareButton
Layout.preferredWidth: parent.height * 0.70
Layout.preferredHeight: parent.height * 0.70
visible: root.activityData.isShareable
imageSource: "image://svgimage-custom-color/share.svg" + "/" + Style.ncBlue
imageSourceHover: "image://svgimage-custom-color/share.svg" + "/" + Style.ncTextColor
toolTipText: qsTr("Open share dialog")
bgColor: Style.ncBlue
onClicked: root.shareButtonClicked()
}
}

View File

@@ -1,25 +0,0 @@
import QtQml 2.15
import QtQuick 2.15
import QtQuick.Controls 2.3
AutoSizingMenu {
id: moreActionsButtonContextMenu
property int maxActionButtons: 0
property var linksContextMenu: []
signal menuEntryTriggered(int index)
Repeater {
id: moreActionsButtonContextMenuRepeater
model: moreActionsButtonContextMenu.linksContextMenu
delegate: MenuItem {
id: moreActionsButtonContextMenuEntry
text: model.modelData.label
onTriggered: menuEntryTriggered(model.modelData.actionIndex)
}
}
}

View File

@@ -1,14 +1,14 @@
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import Style 1.0
import com.nextcloud.desktopclient 1.0 as NC import com.nextcloud.desktopclient 1.0 as NC
ScrollView { ScrollView {
id: controlRoot id: controlRoot
property alias model: activityList.model property alias model: activityList.model
property bool isFileActivityList: false
signal showFileActivity(string displayPath, string absolutePath) signal showFileActivity(string displayPath, string absolutePath)
signal activityItemClicked(int index) signal activityItemClicked(int index)
@@ -31,19 +31,12 @@ ScrollView {
clip: true clip: true
spacing: 10
delegate: ActivityItem { delegate: ActivityItem {
isFileActivityList: controlRoot.isFileActivityList
width: activityList.contentWidth width: activityList.contentWidth
height: Style.trayWindowHeaderHeight
flickable: activityList flickable: activityList
onClicked: { onClicked: activityItemClicked(model.index)
if (model.isCurrentUserFileActivity) { onFileActivityButtonClicked: showFileActivity(displayPath, absolutePath)
showFileActivity(model.displayPath, model.absolutePath)
} else {
activityItemClicked(model.index)
}
}
} }
} }
} }

View File

@@ -1,61 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
Button {
id: root
property string imageSource: ""
property string imageSourceHover: ""
property string toolTipText: ""
property color textColor
property color textColorHovered
property color bgColor: "transparent"
property bool bold: false
background: Rectangle {
color: root.bgColor
opacity: parent.hovered ? 1.0 : 0.3
radius: width / 2
}
leftPadding: root.text === "" ? 5 : 10
rightPadding: root.text === "" ? 5 : 10
contentItem: RowLayout {
Image {
id: icon
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
source: root.hovered ? root.imageSourceHover : root.imageSource
}
Label {
Layout.maximumWidth: icon.width > 0 ? parent.width - icon.width - parent.spacing : parent.width
Layout.fillWidth: icon.status !== Image.Ready
text: root.text
font.bold: root.bold
visible: root.text !== ""
color: root.hovered ? root.textColorHovered : root.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
ToolTip {
text: root.toolTipText
delay: Qt.styleHints.mousePressAndHoldInterval
visible: root.toolTipText !== "" && root.hovered
}
}

View File

@@ -1,49 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.3
import Style 1.0
Label {
id: root
property string toolTipText: ""
property Action action: null
property alias acceptedButtons: mouseArea.acceptedButtons
property bool hovered: mouseArea.containsMouse
height: implicitHeight
property color textColor: Style.unifiedSearchResulTitleColor
property color textColorHovered: Style.unifiedSearchResulSublineColor
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.onPressAction: root.clicked(null)
text: action ? action.text : ""
enabled: !action || action.enabled
onClicked: if (action) action.trigger()
font.underline: true
color: root.hovered ? root.textColorHovered : root.textColor
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
signal pressed(QtObject mouse)
signal clicked(QtObject mouse)
ToolTip {
text: root.toolTipText
delay: Qt.styleHints.mousePressAndHoldInterval
visible: root.toolTipText !== "" && root.hovered
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.clicked(mouse)
onPressed: root.pressed(mouse)
}
}

View File

@@ -15,7 +15,6 @@ Window {
height: 500 height: 500
ActivityList { ActivityList {
isFileActivityList: true
anchors.fill: parent anchors.fill: parent
model: dialog.model model: dialog.model
} }

View File

@@ -111,12 +111,14 @@ MenuItem {
width: parent.width width: parent.width
Label { Label {
id: emoji id: emoji
height: Style.topLinePixelSize
visible: model.statusEmoji !== "" visible: model.statusEmoji !== ""
text: statusEmoji text: statusEmoji
topPadding: -Style.accountLabelsSpacing topPadding: -Style.accountLabelsSpacing
} }
Label { Label {
id: message id: message
height: Style.topLinePixelSize
width: parent.width - parent.spacing - emoji.width width: parent.width - parent.spacing - emoji.width
visible: model.statusMessage !== "" visible: model.statusMessage !== ""
text: statusMessage text: statusMessage
@@ -167,8 +169,9 @@ MenuItem {
y: userMoreButton.y + 1 y: userMoreButton.y + 1
} }
AutoSizingMenu { Menu {
id: userMoreButtonMenu id: userMoreButtonMenu
width: 120
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
background: Rectangle { background: Rectangle {

View File

@@ -174,22 +174,6 @@ Window {
radius: Style.currentAccountButtonRadius radius: Style.currentAccountButtonRadius
} }
contentItem: ScrollView {
id: accMenuScrollView
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
data: WheelHandler {
target: accMenuScrollView.contentItem
}
ListView {
implicitHeight: contentHeight
model: accountMenu.contentModel
interactive: true
clip: true
currentIndex: accountMenu.currentIndex
}
}
onClosed: { onClosed: {
// HACK: reload account Instantiator immediately by restting it - could be done better I guess // HACK: reload account Instantiator immediately by restting it - could be done better I guess
// see also onVisibleChanged above // see also onVisibleChanged above
@@ -748,7 +732,7 @@ Window {
openFileActivityDialog(displayPath, absolutePath) openFileActivityDialog(displayPath, absolutePath)
} }
onActivityItemClicked: { onActivityItemClicked: {
model.slotTriggerDefaultAction(index) model.triggerDefaultAction(index)
} }
} }

View File

@@ -33,15 +33,4 @@ Activity::Identifier Activity::ident() const
{ {
return Identifier(_id, _accName); return Identifier(_id, _accName);
} }
ActivityLink ActivityLink::createFomJsonObject(const QJsonObject &obj)
{
ActivityLink activityLink;
activityLink._label = QUrl::fromPercentEncoding(obj.value(QStringLiteral("label")).toString().toUtf8());
activityLink._link = obj.value(QStringLiteral("link")).toString();
activityLink._verb = obj.value(QStringLiteral("type")).toString().toUtf8();
activityLink._primary = obj.value(QStringLiteral("primary")).toBool();
return activityLink;
}
} }

View File

@@ -17,7 +17,6 @@
#include <QtCore> #include <QtCore>
#include <QIcon> #include <QIcon>
#include <QJsonObject>
namespace OCC { namespace OCC {
/** /**
@@ -29,20 +28,13 @@ namespace OCC {
class ActivityLink class ActivityLink
{ {
Q_GADGET Q_GADGET
Q_PROPERTY(QString imageSource MEMBER _imageSource)
Q_PROPERTY(QString imageSourceHovered MEMBER _imageSourceHovered)
Q_PROPERTY(QString label MEMBER _label) Q_PROPERTY(QString label MEMBER _label)
Q_PROPERTY(QString link MEMBER _link) Q_PROPERTY(QString link MEMBER _link)
Q_PROPERTY(QByteArray verb MEMBER _verb) Q_PROPERTY(QByteArray verb MEMBER _verb)
Q_PROPERTY(bool primary MEMBER _primary) Q_PROPERTY(bool primary MEMBER _primary)
public: public:
static ActivityLink createFomJsonObject(const QJsonObject &obj);
public:
QString _imageSource;
QString _imageSourceHovered;
QString _label; QString _label;
QString _link; QString _link;
QByteArray _verb; QByteArray _verb;
@@ -69,32 +61,19 @@ public:
SyncFileItemType SyncFileItemType
}; };
struct RichSubjectParameter {
QString type; // Required
QString id; // Required
QString name; // Required
QString path; // Required (for files only)
QUrl link; // Optional (files only)
};
Type _type; Type _type;
qlonglong _id; qlonglong _id;
QString _fileAction; QString _fileAction;
QString _objectType; QString _objectType;
QString _subject; QString _subject;
QString _subjectRich;
QHash<QString, RichSubjectParameter> _subjectRichParameters;
QString _subjectDisplay;
QString _message; QString _message;
QString _folder; QString _folder;
QString _file; QString _file;
QString _renamedFile;
QUrl _link; QUrl _link;
QDateTime _dateTime; QDateTime _dateTime;
qint64 _expireAtMsecs = -1; qint64 _expireAtMsecs = -1;
QString _accName; QString _accName;
QString _icon; QString _icon;
bool _isCurrentUserFileActivity = false;
// Stores information about the error // Stores information about the error
int _status; int _status;

View File

@@ -54,25 +54,20 @@ ActivityListModel::ActivityListModel(AccountState *accountState,
QHash<int, QByteArray> ActivityListModel::roleNames() const QHash<int, QByteArray> ActivityListModel::roleNames() const
{ {
auto roles = QAbstractListModel::roleNames(); QHash<int, QByteArray> roles;
roles[DisplayPathRole] = "displayPath"; roles[DisplayPathRole] = "displayPath";
roles[PathRole] = "path"; roles[PathRole] = "path";
roles[AbsolutePathRole] = "absolutePath"; roles[AbsolutePathRole] = "absolutePath";
roles[DisplayLocationRole] = "displayLocation";
roles[LinkRole] = "link"; roles[LinkRole] = "link";
roles[MessageRole] = "message"; roles[MessageRole] = "message";
roles[ActionRole] = "type"; roles[ActionRole] = "type";
roles[ActionIconRole] = "icon"; roles[ActionIconRole] = "icon";
roles[ActionTextRole] = "subject"; roles[ActionTextRole] = "subject";
roles[ActionsLinksRole] = "links"; roles[ActionsLinksRole] = "links";
roles[ActionsLinksContextMenuRole] = "linksContextMenu";
roles[ActionsLinksForActionButtonsRole] = "linksForActionButtons";
roles[ActionTextColorRole] = "activityTextTitleColor"; roles[ActionTextColorRole] = "activityTextTitleColor";
roles[ObjectTypeRole] = "objectType"; roles[ObjectTypeRole] = "objectType";
roles[PointInTimeRole] = "dateTime"; roles[PointInTimeRole] = "dateTime";
roles[DisplayActions] = "displayActions"; roles[DisplayActions] = "displayActions";
roles[ShareableRole] = "isShareable";
roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
return roles; return roles;
} }
@@ -81,11 +76,6 @@ void ActivityListModel::setAccountState(AccountState *state)
_accountState = state; _accountState = state;
} }
void ActivityListModel::setCurrentItem(const int currentItem)
{
_currentItem = currentItem;
}
void ActivityListModel::setCurrentlyFetching(bool value) void ActivityListModel::setCurrentlyFetching(bool value)
{ {
_currentlyFetching = value; _currentlyFetching = value;
@@ -123,41 +113,15 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
if (!ast && _accountState != ast.data()) if (!ast && _accountState != ast.data())
return QVariant(); return QVariant();
const auto getFilePath = [&]() { switch (role) {
const auto fileName = a._fileAction == QStringLiteral("file_renamed") ? a._renamedFile : a._file; case DisplayPathRole:
if (!fileName.isEmpty()) {
const auto folder = FolderMan::instance()->folder(a._folder);
const QString relPath = folder ? folder->remotePath() + fileName : fileName;
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (localFiles.isEmpty()) {
return QString();
}
// If this is an E2EE file or folder, pretend we got no path, hiding the share button which is what we want
if (folder) {
SyncJournalFileRecord rec;
folder->journalDb()->getFileRecord(fileName.mid(1), &rec);
if (rec.isValid() && (rec._isE2eEncrypted || !rec._e2eMangledName.isEmpty())) {
return QString();
}
}
return localFiles.constFirst();
}
return QString();
};
const auto getDisplayPath = [&a, &ast]() {
if (!a._file.isEmpty()) { if (!a._file.isEmpty()) {
const auto folder = FolderMan::instance()->folder(a._folder); auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
QString relPath = folder ? folder->remotePath() + a._file : a._file; if (folder) {
relPath.prepend(folder->remotePath());
}
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (localFiles.count() > 0) { if (localFiles.count() > 0) {
if (relPath.startsWith('/') || relPath.startsWith('\\')) { if (relPath.startsWith('/') || relPath.startsWith('\\')) {
return relPath.remove(0, 1); return relPath.remove(0, 1);
@@ -167,22 +131,54 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
} }
} }
return QString(); return QString();
};
const auto displayLocation = [&]() {
const auto displayPath = QFileInfo(getDisplayPath()).path();
return displayPath == "." || displayPath == "/" ? QString() : displayPath;
};
switch (role) {
case DisplayPathRole:
return getDisplayPath();
case PathRole: case PathRole:
return QFileInfo(getFilePath()).path(); if (!a._file.isEmpty()) {
case AbsolutePathRole: const auto folder = FolderMan::instance()->folder(a._folder);
return getFilePath();
case DisplayLocationRole: QString relPath(a._file);
return displayLocation(); if (folder) {
relPath.prepend(folder->remotePath());
}
// get relative path to the file so we can open it in the file manager
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
if (localFiles.isEmpty()) {
return QString();
}
// If this is an E2EE file or folder, pretend we got no path, this leads to
// hiding the share button which is what we want
if (folder) {
SyncJournalFileRecord rec;
folder->journalDb()->getFileRecord(a._file.mid(1), &rec);
if (rec.isValid() && (rec._isE2eEncrypted || !rec._e2eMangledName.isEmpty())) {
return QString();
}
}
return QUrl::fromLocalFile(localFiles.constFirst());
}
return QString();
case AbsolutePathRole: {
const auto folder = FolderMan::instance()->folder(a._folder);
QString relPath(a._file);
if (!a._file.isEmpty()) {
if (folder) {
relPath.prepend(folder->remotePath());
}
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
if (!localFiles.empty()) {
return localFiles.constFirst();
} else {
qWarning("File not local folders while processing absolute path request.");
return QString();
}
} else {
qWarning("Received an absolute path request for an activity without a file path.");
return QString();
}
}
case ActionsLinksRole: { case ActionsLinksRole: {
QList<QVariant> customList; QList<QVariant> customList;
foreach (ActivityLink activityLink, a._links) { foreach (ActivityLink activityLink, a._links) {
@@ -190,15 +186,6 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
} }
return customList; return customList;
} }
case ActionsLinksContextMenuRole: {
return ActivityListModel::convertLinksToMenuEntries(a);
}
case ActionsLinksForActionButtonsRole: {
return ActivityListModel::convertLinksToActionButtons(a);
}
case ActionIconRole: { case ActionIconRole: {
if (a._type == Activity::NotificationType) { if (a._type == Activity::NotificationType) {
return "qrc:///client/theme/black/bell.svg"; return "qrc:///client/theme/black/bell.svg";
@@ -254,11 +241,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
} }
} }
case ActionTextRole: case ActionTextRole:
if(a._subjectDisplay.isEmpty()) { return a._subject;
return a._subject;
}
return a._subjectDisplay;
case ActionTextColorRole: case ActionTextColorRole:
return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry
case MessageRole: case MessageRole:
@@ -267,7 +250,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
if (a._link.isEmpty()) { if (a._link.isEmpty()) {
return ""; return "";
} else { } else {
return a._link.toString(); return a._link;
} }
} }
case AccountRole: case AccountRole:
@@ -279,22 +262,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return (ast && ast->isConnected()); return (ast && ast->isConnected());
case DisplayActions: case DisplayActions:
return _displayActions; return _displayActions;
case ShareableRole:
return !data(index, PathRole).toString().isEmpty() && a._objectType == QStringLiteral("files") && _displayActions && a._fileAction != "file_deleted" && a._status != SyncFileItem::FileIgnored;
case IsCurrentUserFileActivityRole:
return a._isCurrentUserFileActivity;
default: default:
return QVariant(); return QVariant();
} }
return QVariant(); return QVariant();
} }
int ActivityListModel::rowCount(const QModelIndex &parent) const int ActivityListModel::rowCount(const QModelIndex &) const
{ {
if(parent.isValid()) {
return 0;
}
return _finalList.count(); return _finalList.count();
} }
@@ -330,21 +305,6 @@ void ActivityListModel::startFetchJob()
job->start(); job->start();
} }
void ActivityListModel::setFinalList(const ActivityList &finalList)
{
_finalList = finalList;
}
const ActivityList &ActivityListModel::finalList() const
{
return _finalList;
}
int ActivityListModel::currentItem() const
{
return _currentItem;
}
void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode) void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode)
{ {
auto activities = json.object().value("ocs").toObject().value("data").toArray(); auto activities = json.object().value("ocs").toObject().value("data").toArray();
@@ -368,55 +328,17 @@ void ActivityListModel::activitiesReceived(const QJsonDocument &json, int status
auto json = activ.toObject(); auto json = activ.toObject();
Activity a; Activity a;
const auto activityUser = json.value(QStringLiteral("user")).toString();
a._type = Activity::ActivityType; a._type = Activity::ActivityType;
a._objectType = json.value(QStringLiteral("object_type")).toString(); a._objectType = json.value("object_type").toString();
a._accName = ast->account()->displayName(); a._accName = ast->account()->displayName();
a._id = json.value(QStringLiteral("activity_id")).toInt(); a._id = json.value("activity_id").toInt();
a._fileAction = json.value(QStringLiteral("type")).toString(); a._fileAction = json.value("type").toString();
a._subject = json.value(QStringLiteral("subject")).toString(); a._subject = json.value("subject").toString();
a._message = json.value(QStringLiteral("message")).toString(); a._message = json.value("message").toString();
a._file = json.value(QStringLiteral("object_name")).toString(); a._file = json.value("object_name").toString();
a._link = QUrl(json.value(QStringLiteral("link")).toString()); a._link = QUrl(json.value("link").toString());
a._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate); a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
a._icon = json.value(QStringLiteral("icon")).toString(); a._icon = json.value("icon").toString();
a._isCurrentUserFileActivity = a._objectType == QStringLiteral("files") && activityUser == ast->account()->davUser();
auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
if(richSubjectData.size() > 1) {
a._subjectRich = richSubjectData[0].toString();
auto parameters = richSubjectData[1].toObject();
const QRegularExpression subjectRichParameterRe(QStringLiteral("({[a-zA-Z0-9]*})"));
const QRegularExpression subjectRichParameterBracesRe(QStringLiteral("[{}]"));
for (auto i = parameters.begin(); i != parameters.end(); ++i) {
const auto parameterJsonObject = i.value().toObject();
const Activity::RichSubjectParameter parameter = {
parameterJsonObject.value(QStringLiteral("type")).toString(),
parameterJsonObject.value(QStringLiteral("id")).toString(),
parameterJsonObject.value(QStringLiteral("name")).toString(),
parameterJsonObject.contains(QStringLiteral("path")) ? parameterJsonObject.value(QStringLiteral("path")).toString() : QString(),
parameterJsonObject.contains(QStringLiteral("link")) ? QUrl(parameterJsonObject.value(QStringLiteral("link")).toString()) : QUrl(),
};
a._subjectRichParameters[i.key()] = parameter;
}
auto displayString = a._subjectRich;
auto i = subjectRichParameterRe.globalMatch(displayString);
while (i.hasNext()) {
const auto match = i.next();
auto word = match.captured(1);
word.remove(subjectRichParameterBracesRe);
Q_ASSERT(a._subjectRichParameters.contains(word));
displayString = displayString.replace(match.captured(1), a._subjectRichParameters[word].name);
}
a._subjectDisplay = displayString;
}
list.append(a); list.append(a);
_currentItem = list.last()._id; _currentItem = list.last()._id;
@@ -432,9 +354,9 @@ void ActivityListModel::activitiesReceived(const QJsonDocument &json, int status
_activityLists.append(list); _activityLists.append(list);
combineActivityLists();
emit activityJobStatusCode(statusCode); emit activityJobStatusCode(statusCode);
combineActivityLists();
} }
void ActivityListModel::addErrorToActivityList(Activity activity) void ActivityListModel::addErrorToActivityList(Activity activity)
@@ -523,7 +445,7 @@ void ActivityListModel::removeActivityFromActivityList(Activity activity)
} }
} }
void ActivityListModel::slotTriggerDefaultAction(const int activityIndex) void ActivityListModel::triggerDefaultAction(int activityIndex)
{ {
if (activityIndex < 0 || activityIndex >= _finalList.size()) { if (activityIndex < 0 || activityIndex >= _finalList.size()) {
qCWarning(lcActivity) << "Couldn't trigger default action at index" << activityIndex << "/ final list size:" << _finalList.size(); qCWarning(lcActivity) << "Couldn't trigger default action at index" << activityIndex << "/ final list size:" << _finalList.size();
@@ -531,7 +453,7 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
} }
const auto modelIndex = index(activityIndex); const auto modelIndex = index(activityIndex);
const auto path = data(modelIndex, PathRole).toString(); const auto path = data(modelIndex, PathRole).toUrl();
const auto activity = _finalList.at(activityIndex); const auto activity = _finalList.at(activityIndex);
if (activity._status == SyncFileItem::Conflict) { if (activity._status == SyncFileItem::Conflict) {
@@ -581,15 +503,15 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
return; return;
} }
if (!path.isEmpty()) { if (path.isValid()) {
QDesktopServices::openUrl(QUrl::fromLocalFile(path)); QDesktopServices::openUrl(path);
} else { } else {
const auto link = data(modelIndex, LinkRole).toUrl(); const auto link = data(modelIndex, LinkRole).toUrl();
Utility::openBrowser(link); Utility::openBrowser(link);
} }
} }
void ActivityListModel::slotTriggerAction(const int activityIndex, const int actionIndex) void ActivityListModel::triggerAction(int activityIndex, int actionIndex)
{ {
if (activityIndex < 0 || activityIndex >= _finalList.size()) { if (activityIndex < 0 || activityIndex >= _finalList.size()) {
qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size(); qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size();
@@ -613,112 +535,11 @@ void ActivityListModel::slotTriggerAction(const int activityIndex, const int act
emit sendNotificationRequest(activity._accName, action._link, action._verb, activityIndex); emit sendNotificationRequest(activity._accName, action._link, action._verb, activityIndex);
} }
void ActivityListModel::slotTriggerDismiss(const int activityIndex)
{
if (activityIndex < 0 || activityIndex >= _finalList.size()) {
qCWarning(lcActivity) << "Couldn't trigger action on activity at index" << activityIndex << "/ final list size:" << _finalList.size();
return;
}
const auto activityLinks = _finalList[activityIndex]._links;
const auto foundActivityLinkIt = std::find_if(std::cbegin(activityLinks), std::cend(activityLinks), [](const ActivityLink &link) {
return link._verb == QStringLiteral("DELETE");
});
if (foundActivityLinkIt == std::cend(activityLinks)) {
qCWarning(lcActivity) << "Couldn't find dismiss action in activity at index" << activityIndex
<< " links.size() " << activityLinks.size();
return;
}
const auto actionIndex = static_cast<int>(std::distance(activityLinks.begin(), foundActivityLinkIt));
if (actionIndex < 0 || actionIndex > activityLinks.size()) {
qCWarning(lcActivity) << "Couldn't find dismiss action in activity at index" << activityIndex
<< " actionIndex found " << actionIndex;
return;
}
slotTriggerAction(activityIndex, actionIndex);
}
AccountState *ActivityListModel::accountState() const AccountState *ActivityListModel::accountState() const
{ {
return _accountState; return _accountState;
} }
QVariantList ActivityListModel::convertLinksToActionButtons(const Activity &activity)
{
QVariantList customList;
if (activity._links.size() == 1) {
return customList;
}
if (static_cast<quint32>(activity._links.size()) > maxActionButtons()) {
customList << ActivityListModel::convertLinkToActionButton(activity, activity._links.first());
return customList;
}
for (const auto &activityLink : activity._links) {
if (activityLink._verb == QStringLiteral("DELETE")
|| (activity._objectType == QStringLiteral("chat") || activity._objectType == QStringLiteral("call")
|| activity._objectType == QStringLiteral("room"))) {
customList << ActivityListModel::convertLinkToActionButton(activity, activityLink);
}
}
return customList;
}
QVariant ActivityListModel::convertLinkToActionButton(const OCC::Activity &activity, const OCC::ActivityLink &activityLink)
{
auto activityLinkCopy = activityLink;
const auto isReplyIconApplicable = activityLink._verb == QStringLiteral("WEB")
&& (activity._objectType == QStringLiteral("chat") || activity._objectType == QStringLiteral("call")
|| activity._objectType == QStringLiteral("room"));
const QString replyButtonPath = QStringLiteral("image://svgimage-custom-color/reply.svg");
if (isReplyIconApplicable) {
activityLinkCopy._imageSource =
QString(replyButtonPath + "/" + OCC::Theme::instance()->wizardHeaderBackgroundColor().name());
activityLinkCopy._imageSourceHovered =
QString(replyButtonPath + "/" + OCC::Theme::instance()->wizardHeaderTitleColor().name());
}
const auto isReplyLabelApplicable = activityLink._verb == QStringLiteral("WEB")
&& (activity._objectType == QStringLiteral("chat")
|| (activity._objectType != QStringLiteral("room") && activity._objectType != QStringLiteral("call")));
if (activityLink._verb == QStringLiteral("DELETE")) {
activityLinkCopy._label = QObject::tr("Mark as read");
} else if (isReplyLabelApplicable) {
activityLinkCopy._label = QObject::tr("Reply");
}
return QVariant::fromValue(activityLinkCopy);
}
QVariantList ActivityListModel::convertLinksToMenuEntries(const Activity &activity)
{
QVariantList customList;
if (static_cast<quint32>(activity._links.size()) > maxActionButtons()) {
for (int i = 0; i < activity._links.size(); ++i) {
const auto &activityLink = activity._links[i];
if (!activityLink._primary) {
customList << QVariantMap{
{QStringLiteral("actionIndex"), i}, {QStringLiteral("label"), activityLink._label}};
}
}
}
return customList;
}
void ActivityListModel::combineActivityLists() void ActivityListModel::combineActivityLists()
{ {
ActivityList resultList; ActivityList resultList;
@@ -762,8 +583,14 @@ void ActivityListModel::combineActivityLists()
} }
beginResetModel(); beginResetModel();
_finalList = resultList; _finalList.clear();
endResetModel(); endResetModel();
if (resultList.count() > 0) {
beginInsertRows(QModelIndex(), 0, resultList.count() - 1);
_finalList = resultList;
endInsertRows();
}
} }
bool ActivityListModel::canFetchActivities() const bool ActivityListModel::canFetchActivities() const
@@ -773,8 +600,11 @@ bool ActivityListModel::canFetchActivities() const
void ActivityListModel::fetchMore(const QModelIndex &) void ActivityListModel::fetchMore(const QModelIndex &)
{ {
if (canFetchActivities() && !_currentlyFetching) { if (canFetchActivities()) {
startFetchJob(); startFetchJob();
} else {
_doneFetching = true;
combineActivityLists();
} }
} }
@@ -804,6 +634,4 @@ void ActivityListModel::slotRemoveAccount()
_totalActivitiesFetched = 0; _totalActivitiesFetched = 0;
_showMoreActivitiesAvailableEntry = false; _showMoreActivitiesAvailableEntry = false;
} }
} }

View File

@@ -40,17 +40,14 @@ class ActivityListModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(quint32 maxActionButtons READ maxActionButtons CONSTANT)
Q_PROPERTY(AccountState *accountState READ accountState CONSTANT) Q_PROPERTY(AccountState *accountState READ accountState CONSTANT)
public: public:
enum DataRole { enum DataRole {
ActionIconRole = Qt::UserRole + 1, ActionIconRole = Qt::UserRole + 1,
UserIconRole,
AccountRole, AccountRole,
ObjectTypeRole, ObjectTypeRole,
ActionsLinksRole, ActionsLinksRole,
ActionsLinksContextMenuRole,
ActionsLinksForActionButtonsRole,
ActionTextRole, ActionTextRole,
ActionTextColorRole, ActionTextColorRole,
ActionRole, ActionRole,
@@ -58,13 +55,11 @@ public:
DisplayPathRole, DisplayPathRole,
PathRole, PathRole,
AbsolutePathRole, AbsolutePathRole,
DisplayLocationRole, // Provides the display path to a file's parent folder, relative to Nextcloud root
LinkRole, LinkRole,
PointInTimeRole, PointInTimeRole,
AccountConnectedRole, AccountConnectedRole,
SyncFileStatusRole,
DisplayActions, DisplayActions,
ShareableRole,
IsCurrentUserFileActivityRole,
}; };
Q_ENUM(DataRole) Q_ENUM(DataRole)
@@ -89,22 +84,14 @@ public:
void removeActivityFromActivityList(int row); void removeActivityFromActivityList(int row);
void removeActivityFromActivityList(Activity activity); void removeActivityFromActivityList(Activity activity);
Q_INVOKABLE void triggerDefaultAction(int activityIndex);
Q_INVOKABLE void triggerAction(int activityIndex, int actionIndex);
AccountState *accountState() const; AccountState *accountState() const;
void setAccountState(AccountState *state);
static constexpr quint32 maxActionButtons()
{
return MaxActionButtons;
}
void setCurrentItem(const int currentItem);
public slots: public slots:
void slotRefreshActivity(); void slotRefreshActivity();
void slotRemoveAccount(); void slotRemoveAccount();
void slotTriggerDefaultAction(const int activityIndex);
void slotTriggerAction(const int activityIndex, const int actionIndex);
void slotTriggerDismiss(const int activityIndex);
signals: signals:
void activityJobStatusCode(int statusCode); void activityJobStatusCode(int statusCode);
@@ -114,6 +101,7 @@ protected:
void activitiesReceived(const QJsonDocument &json, int statusCode); void activitiesReceived(const QJsonDocument &json, int statusCode);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
void setAccountState(AccountState *state);
void setCurrentlyFetching(bool value); void setCurrentlyFetching(bool value);
bool currentlyFetching() const; bool currentlyFetching() const;
void setDoneFetching(bool value); void setDoneFetching(bool value);
@@ -122,16 +110,7 @@ protected:
virtual void startFetchJob(); virtual void startFetchJob();
// added these for unit tests
void setFinalList(const ActivityList &finalList);
const ActivityList &finalList() const;
int currentItem() const;
//
private: private:
static QVariantList convertLinksToMenuEntries(const Activity &activity);
static QVariantList convertLinksToActionButtons(const Activity &activity);
static QVariant convertLinkToActionButton(const Activity &activity, const ActivityLink &activityLink);
void combineActivityLists(); void combineActivityLists();
bool canFetchActivities() const; bool canFetchActivities() const;
@@ -158,8 +137,6 @@ private:
bool _currentlyFetching = false; bool _currentlyFetching = false;
bool _doneFetching = false; bool _doneFetching = false;
bool _hideOldActivities = true; bool _hideOldActivities = true;
static constexpr quint32 MaxActionButtons = 2;
}; };
} }

View File

@@ -121,7 +121,14 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
auto actions = json.value("actions").toArray(); auto actions = json.value("actions").toArray();
foreach (auto action, actions) { foreach (auto action, actions) {
a._links.append(ActivityLink::createFomJsonObject(action.toObject())); auto actionJson = action.toObject();
ActivityLink al;
al._label = QUrl::fromPercentEncoding(actionJson.value("label").toString().toUtf8());
al._link = actionJson.value("link").toString();
al._verb = actionJson.value("type").toString().toUtf8();
al._primary = actionJson.value("primary").toBool();
a._links.append(al);
} }
// Add another action to dismiss notification on server // Add another action to dismiss notification on server

View File

@@ -506,15 +506,12 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
activity._folder = folder->alias(); activity._folder = folder->alias();
activity._fileAction = ""; activity._fileAction = "";
const auto fileName = QFileInfo(item->_originalFile).fileName();
if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
activity._fileAction = "file_deleted"; activity._fileAction = "file_deleted";
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW) { } else if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
activity._fileAction = "file_created"; activity._fileAction = "file_created";
} else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) { } else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) {
activity._fileAction = "file_renamed"; activity._fileAction = "file_renamed";
activity._renamedFile = item->_renameTarget;
} else { } else {
activity._fileAction = "file_changed"; activity._fileAction = "file_changed";
} }
@@ -523,15 +520,15 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
if (item->_direction != SyncFileItem::Up) { if (item->_direction != SyncFileItem::Up) {
activity._message = tr("Synced %1").arg(fileName); activity._message = tr("Synced %1").arg(item->_originalFile);
} else if (activity._fileAction == "file_renamed") { } else if (activity._fileAction == "file_renamed") {
activity._message = tr("You renamed %1").arg(fileName); activity._message = tr("You renamed %1").arg(item->_originalFile);
} else if (activity._fileAction == "file_deleted") { } else if (activity._fileAction == "file_deleted") {
activity._message = tr("You deleted %1").arg(fileName); activity._message = tr("You deleted %1").arg(item->_originalFile);
} else if (activity._fileAction == "file_created") { } else if (activity._fileAction == "file_created") {
activity._message = tr("You created %1").arg(fileName); activity._message = tr("You created %1").arg(item->_originalFile);
} else { } else {
activity._message = tr("You changed %1").arg(fileName); activity._message = tr("You changed %1").arg(item->_originalFile);
} }
_activityModel->addSyncFileItemToActivityList(activity); _activityModel->addSyncFileItemToActivityList(activity);

View File

@@ -57,8 +57,7 @@ using namespace QKeychain;
namespace { namespace {
constexpr int pushNotificationsReconnectInterval = 1000 * 60 * 2; constexpr int pushNotificationsReconnectInterval = 1000 * 60 * 2;
constexpr int usernamePrefillServerVersionMinSupportedMajor = 24; constexpr int usernamePrefillServerVersinMinSupportedMajor = 24;
constexpr int checksumRecalculateRequestServerVersionMinSupportedMajor = 24;
} }
namespace OCC { namespace OCC {
@@ -633,17 +632,7 @@ bool Account::serverVersionUnsupported() const
bool Account::isUsernamePrefillSupported() const bool Account::isUsernamePrefillSupported() const
{ {
return serverVersionInt() >= makeServerVersion(usernamePrefillServerVersionMinSupportedMajor, 0, 0); return serverVersionInt() >= makeServerVersion(usernamePrefillServerVersinMinSupportedMajor, 0, 0);
}
bool Account::isChecksumRecalculateRequestSupported() const
{
return serverVersionInt() >= makeServerVersion(checksumRecalculateRequestServerVersionMinSupportedMajor, 0, 0);
}
int Account::checksumRecalculateServerVersionMinSupportedMajor() const
{
return checksumRecalculateRequestServerVersionMinSupportedMajor;
} }
void Account::setServerVersion(const QString &version) void Account::setServerVersion(const QString &version)

View File

@@ -232,10 +232,6 @@ public:
bool isUsernamePrefillSupported() const; bool isUsernamePrefillSupported() const;
bool isChecksumRecalculateRequestSupported() const;
int checksumRecalculateServerVersionMinSupportedMajor() const;
/** True when the server connection is using HTTP2 */ /** True when the server connection is using HTTP2 */
bool isHttp2Supported() { return _http2Supported; } bool isHttp2Supported() { return _http2Supported; }
void setHttp2Supported(bool value) { _http2Supported = value; } void setHttp2Supported(bool value) { _http2Supported = value; }

View File

@@ -290,10 +290,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
const QString originalFilePath = propagator()->fullLocalPath(item->_file); const QString originalFilePath = propagator()->fullLocalPath(item->_file);
if (!FileSystem::fileExists(fullFilePath)) { if (!FileSystem::fileExists(fullFilePath)) {
_pendingChecksumFiles.remove(item->_file); return slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath));
slotOnErrorStartFolderUnlock(item, SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath));
checkPropagationIsDone();
return;
} }
const time_t prevModtime = item->_modtime; // the _item value was set in PropagateUploadFile::start() const time_t prevModtime = item->_modtime; // the _item value was set in PropagateUploadFile::start()
// but a potential checksum calculation could have taken some time during which the file could // but a potential checksum calculation could have taken some time during which the file could
@@ -302,7 +299,7 @@ void BulkPropagatorJob::slotStartUpload(SyncFileItemPtr item,
item->_modtime = FileSystem::getModTime(originalFilePath); item->_modtime = FileSystem::getModTime(originalFilePath);
if (item->_modtime <= 0) { if (item->_modtime <= 0) {
_pendingChecksumFiles.remove(item->_file); _pendingChecksumFiles.remove(item->_file);
slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file))); slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)));
checkPropagationIsDone(); checkPropagationIsDone();
return; return;
} }

View File

@@ -21,12 +21,12 @@ namespace OCC {
Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg) Q_LOGGING_CATEGORY(lcDeleteJob, "nextcloud.sync.networkjob.delete", QtInfoMsg)
DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent) DeleteJob::DeleteJob(AccountPtr account, const QString &path, QObject *parent)
: SimpleFileJob(account, path, parent) : AbstractNetworkJob(account, path, parent)
{ {
} }
DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent) DeleteJob::DeleteJob(AccountPtr account, const QUrl &url, QObject *parent)
: SimpleFileJob(account, QString(), parent) : AbstractNetworkJob(account, QString(), parent)
, _url(url) , _url(url)
{ {
} }
@@ -39,10 +39,24 @@ void DeleteJob::start()
} }
if (_url.isValid()) { if (_url.isValid()) {
startRequest("DELETE", _url, req); sendRequest("DELETE", _url, req);
} else { } else {
startRequest("DELETE", req); sendRequest("DELETE", makeDavUrl(path()), req);
} }
if (reply()->error() != QNetworkReply::NoError) {
qCWarning(lcDeleteJob) << " Network error: " << reply()->errorString();
}
AbstractNetworkJob::start();
}
bool DeleteJob::finished()
{
qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS"
<< replyStatusString();
emit finishedSignal();
return true;
} }
QByteArray DeleteJob::folderToken() const QByteArray DeleteJob::folderToken() const

View File

@@ -23,7 +23,7 @@ namespace OCC {
* @brief The DeleteJob class * @brief The DeleteJob class
* @ingroup libsync * @ingroup libsync
*/ */
class DeleteJob : public SimpleFileJob class DeleteJob : public AbstractNetworkJob
{ {
Q_OBJECT Q_OBJECT
public: public:
@@ -31,10 +31,14 @@ public:
explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr); explicit DeleteJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr);
void start() override; void start() override;
bool finished() override;
QByteArray folderToken() const; QByteArray folderToken() const;
void setFolderToken(const QByteArray &folderToken); void setFolderToken(const QByteArray &folderToken);
signals:
void finishedSignal();
private: private:
QUrl _url; // Only used if the constructor taking a url is taken. QUrl _url; // Only used if the constructor taking a url is taken.
QByteArray _folderToken; QByteArray _folderToken;

View File

@@ -1060,8 +1060,9 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
if (!isFilePlaceHolder && !isOnlineOnlyFolder) { if (!isFilePlaceHolder && !isOnlineOnlyFolder) {
if (localEntry.isDirectory && folderPlaceHolderAvailability.isValid() && !isOnlineOnlyFolder) { if (localEntry.isDirectory && folderPlaceHolderAvailability.isValid() && !isOnlineOnlyFolder) {
// a VFS folder but is not online-only (has some files hydrated) // a VFS folder but is not online0only (has some files hydrated)
qCInfo(lcDisco) << "Virtual directory without db entry for" << path._local << "but it contains hydrated file(s), so let's keep it and reupload."; qCInfo(lcDisco) << "Virtual directory without db entry for" << path._local << "but it contains hydrated file(s), so let's keep it and reupload.";
emit _discoveryData->addErrorToGui(SyncFileItem::SoftError, tr("Conflict when uploading some files to a folder. Those, conflicted, are going to get cleared!"), path._local);
return; return;
} }
qCWarning(lcDisco) << "Virtual file without db entry for" << path._local qCWarning(lcDisco) << "Virtual file without db entry for" << path._local

View File

@@ -186,8 +186,8 @@ QPair<bool, QByteArray> DiscoveryPhase::findAndCancelDeletedJob(const QString &o
qCWarning(lcDiscovery) << "(*it)->_type" << (*it)->_type; qCWarning(lcDiscovery) << "(*it)->_type" << (*it)->_type;
qCWarning(lcDiscovery) << "(*it)->_isRestoration " << (*it)->_isRestoration; qCWarning(lcDiscovery) << "(*it)->_isRestoration " << (*it)->_isRestoration;
Q_ASSERT(false); Q_ASSERT(false);
addErrorToGui(SyncFileItem::Status::FatalError, tr("Error while canceling deletion of a file"), originalPath); addErrorToGui(SyncFileItem::Status::FatalError, tr("Error while canceling delete of a file"), originalPath);
emit fatalError(tr("Error while canceling deletion of %1").arg(originalPath)); emit fatalError(tr("Error while canceling delete of %1").arg(originalPath));
} }
(*it)->_instruction = CSYNC_INSTRUCTION_NONE; (*it)->_instruction = CSYNC_INSTRUCTION_NONE;
result = true; result = true;

View File

@@ -89,30 +89,6 @@ bool Logger::isLoggingToFile() const
void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString &message) void Logger::doLog(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{ {
const QString msg = qFormatLogMessage(type, ctx, message); const QString msg = qFormatLogMessage(type, ctx, message);
#if defined(Q_OS_WIN) && defined(QT_DEBUG)
// write logs to Output window of Visual Studio
{
QString prefix;
switch (type) {
case QtDebugMsg:
break;
case QtInfoMsg:
break;
case QtWarningMsg:
prefix = QStringLiteral("[WARNING] ");
break;
case QtCriticalMsg:
prefix = QStringLiteral("[CRITICAL ERROR] ");
break;
case QtFatalMsg:
prefix = QStringLiteral("[FATAL ERROR] ");
break;
}
auto msgW = QString(prefix + message).toStdWString();
msgW.append(L"\n");
OutputDebugString(msgW.c_str());
}
#endif
{ {
QMutexLocker lock(&_mutex); QMutexLocker lock(&_mutex);
_crashLogIndex = (_crashLogIndex + 1) % CrashLogSize; _crashLogIndex = (_crashLogIndex + 1) % CrashLogSize;

View File

@@ -55,7 +55,6 @@ Q_LOGGING_CATEGORY(lcMkColJob, "nextcloud.sync.networkjob.mkcol", QtInfoMsg)
Q_LOGGING_CATEGORY(lcProppatchJob, "nextcloud.sync.networkjob.proppatch", QtInfoMsg) Q_LOGGING_CATEGORY(lcProppatchJob, "nextcloud.sync.networkjob.proppatch", QtInfoMsg)
Q_LOGGING_CATEGORY(lcJsonApiJob, "nextcloud.sync.networkjob.jsonapi", QtInfoMsg) Q_LOGGING_CATEGORY(lcJsonApiJob, "nextcloud.sync.networkjob.jsonapi", QtInfoMsg)
Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "nextcloud.sync.networkjob.determineauthtype", QtInfoMsg) Q_LOGGING_CATEGORY(lcDetermineAuthTypeJob, "nextcloud.sync.networkjob.determineauthtype", QtInfoMsg)
Q_LOGGING_CATEGORY(lcSimpleFileJob, "nextcloud.sync.networkjob.simplefilejob", QtInfoMsg)
const int notModifiedStatusCode = 304; const int notModifiedStatusCode = 304;
QByteArray parseEtag(const char *header) QByteArray parseEtag(const char *header)
@@ -1085,39 +1084,9 @@ bool SimpleNetworkJob::finished()
return true; return true;
} }
SimpleFileJob::SimpleFileJob(AccountPtr account, const QString &filePath, QObject *parent)
: AbstractNetworkJob(account, filePath, parent)
{
}
QNetworkReply *SimpleFileJob::startRequest(
const QByteArray &verb, const QNetworkRequest req, QIODevice *requestBody)
{
return startRequest(verb, makeDavUrl(path()), req, requestBody);
}
QNetworkReply *SimpleFileJob::startRequest(
const QByteArray &verb, const QUrl &url, const QNetworkRequest req, QIODevice *requestBody)
{
_verb = verb;
const auto reply = sendRequest(verb, url, req, requestBody);
if (reply->error() != QNetworkReply::NoError) {
qCWarning(lcSimpleFileJob) << verb << " Network error: " << reply->errorString();
}
AbstractNetworkJob::start();
return reply;
}
bool SimpleFileJob::finished()
{
qCInfo(lcSimpleFileJob) << _verb << "for" << reply()->request().url() << "FINISHED WITH STATUS" << replyStatusString();
emit finishedSignal(reply());
return true;
}
DeleteApiJob::DeleteApiJob(AccountPtr account, const QString &path, QObject *parent) DeleteApiJob::DeleteApiJob(AccountPtr account, const QString &path, QObject *parent)
: SimpleFileJob(account, path, parent) : AbstractNetworkJob(account, path, parent)
{ {
} }
@@ -1126,13 +1095,14 @@ void DeleteApiJob::start()
{ {
QNetworkRequest req; QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true"); req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
startRequest("DELETE", req); sendRequest("DELETE", url, req);
AbstractNetworkJob::start();
} }
bool DeleteApiJob::finished() bool DeleteApiJob::finished()
{ {
qCInfo(lcJsonApiJob) << "DeleteApiJob of" << reply()->request().url() << "FINISHED WITH STATUS" qCInfo(lcJsonApiJob) << "JsonApiJob of" << reply()->request().url() << "FINISHED WITH STATUS"
<< reply()->error() << reply()->error()
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString()); << (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
@@ -1148,7 +1118,7 @@ bool DeleteApiJob::finished()
const auto replyData = QString::fromUtf8(reply()->readAll()); const auto replyData = QString::fromUtf8(reply()->readAll());
qCInfo(lcJsonApiJob()) << "TMX Delete Job" << replyData; qCInfo(lcJsonApiJob()) << "TMX Delete Job" << replyData;
emit result(httpStatus); emit result(httpStatus);
return SimpleFileJob::finished(); return true;
} }
void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath, void fetchPrivateLinkUrl(AccountPtr account, const QString &remotePath,

View File

@@ -60,31 +60,6 @@ private slots:
bool finished() override; bool finished() override;
}; };
/**
* @brief A basic file manipulation job
* @ingroup libsync
*/
class OWNCLOUDSYNC_EXPORT SimpleFileJob : public AbstractNetworkJob
{
Q_OBJECT
public:
explicit SimpleFileJob(AccountPtr account, const QString &filePath, QObject *parent = nullptr);
QNetworkReply *startRequest(
const QByteArray &verb, const QNetworkRequest req = QNetworkRequest(), QIODevice *requestBody = nullptr);
QNetworkReply *startRequest(const QByteArray &verb, const QUrl &url, const QNetworkRequest req = QNetworkRequest(),
QIODevice *requestBody = nullptr);
signals:
void finishedSignal(QNetworkReply *reply);
protected slots:
bool finished() override;
private:
QByteArray _verb;
};
/** /**
* @brief sends a DELETE http request to a url. * @brief sends a DELETE http request to a url.
* *
@@ -92,7 +67,7 @@ private:
* *
* This does *not* delete files, it does a http request. * This does *not* delete files, it does a http request.
*/ */
class OWNCLOUDSYNC_EXPORT DeleteApiJob : public SimpleFileJob class OWNCLOUDSYNC_EXPORT DeleteApiJob : public AbstractNetworkJob
{ {
Q_OBJECT Q_OBJECT
public: public:

View File

@@ -773,7 +773,7 @@ bool OwncloudPropagator::createConflict(const SyncFileItemPtr &item,
QString renameError; QString renameError;
auto conflictModTime = FileSystem::getModTime(fn); auto conflictModTime = FileSystem::getModTime(fn);
if (conflictModTime <= 0) { if (conflictModTime <= 0) {
*error = tr("Impossible to get modification time for file in conflict %1").arg(fn); *error = tr("Impossible to get modification time for file in conflict %1)").arg(fn);
return false; return false;
} }
QString conflictUserName; QString conflictUserName;
@@ -1133,7 +1133,7 @@ void PropagateDirectory::slotSubJobsFinished(SyncFileItem::Status status)
if (_item->_modtime <= 0) { if (_item->_modtime <= 0) {
status = _item->_status = SyncFileItem::NormalError; status = _item->_status = SyncFileItem::NormalError;
_item->_errorString = tr("Error updating metadata due to invalid modification time"); _item->_errorString = tr("Error updating metadata due to invalid modified time");
qCWarning(lcDirectory) << "Error writing to the database for file" << _item->_file; qCWarning(lcDirectory) << "Error writing to the database for file" << _item->_file;
} }

View File

@@ -22,6 +22,7 @@
#include "common/utility.h" #include "common/utility.h"
#include "filesystem.h" #include "filesystem.h"
#include "propagatorjobs.h" #include "propagatorjobs.h"
#include <common/checksums.h>
#include <common/asserts.h> #include <common/asserts.h>
#include <common/constants.h> #include <common/constants.h>
#include "clientsideencryptionjobs.h" #include "clientsideencryptionjobs.h"
@@ -922,53 +923,8 @@ void PropagateDownloadFile::slotGetFinished()
validator->start(_tmpFile.fileName(), checksumHeader); validator->start(_tmpFile.fileName(), checksumHeader);
} }
void PropagateDownloadFile::slotChecksumFail(const QString &errMsg, void PropagateDownloadFile::slotChecksumFail(const QString &errMsg)
const QByteArray &calculatedChecksumType, const QByteArray &calculatedChecksum, const ValidateChecksumHeader::FailureReason reason) {
{
if (reason == ValidateChecksumHeader::FailureReason::ChecksumMismatch && propagator()->account()->isChecksumRecalculateRequestSupported()) {
const QByteArray calculatedChecksumHeader(calculatedChecksumType + ':' + calculatedChecksum);
const QString fullRemotePathForFile(propagator()->fullRemotePath(_isEncrypted ? _item->_encryptedFileName : _item->_file));
auto *job = new SimpleFileJob(propagator()->account(), fullRemotePathForFile);
QObject::connect(job, &SimpleFileJob::finishedSignal, this,
[this, calculatedChecksumHeader, errMsg](const QNetworkReply *reply) { processChecksumRecalculate(reply, calculatedChecksumHeader, errMsg);
});
qCWarning(lcPropagateDownload) << "Checksum validation has failed for file:" << fullRemotePathForFile
<< " Requesting checksum recalculation on the server...";
QNetworkRequest req;
req.setRawHeader(checksumRecalculateOnServerHeaderC, calculatedChecksumType);
job->startRequest(QByteArrayLiteral("PATCH"), req);
return;
}
checksumValidateFailedAbortDownload(errMsg);
}
void PropagateDownloadFile::processChecksumRecalculate(const QNetworkReply *reply, const QByteArray &originalChecksumHeader, const QString &errorMessage)
{
if (reply->error() != QNetworkReply::NoError) {
qCCritical(lcPropagateDownload) << "Checksum recalculation has failed for file:" << reply->url()
<< " Recalculation request has finished with error:" << reply->errorString();
checksumValidateFailedAbortDownload(errorMessage);
return;
}
const auto newChecksumHeaderFromServer = reply->rawHeader(checkSumHeaderC);
if (newChecksumHeaderFromServer == originalChecksumHeader) {
const auto newChecksumHeaderFromServerSplit = newChecksumHeaderFromServer.split(':');
if (newChecksumHeaderFromServerSplit.size() > 1) {
transmissionChecksumValidated(newChecksumHeaderFromServerSplit.first(), newChecksumHeaderFromServerSplit.last());
return;
}
}
qCCritical(lcPropagateDownload) << "Checksum recalculation has failed for file:" << reply->url() << " "
<< checkSumHeaderC << " received is:" << newChecksumHeaderFromServer;
checksumValidateFailedAbortDownload(errorMessage);
}
void PropagateDownloadFile::checksumValidateFailedAbortDownload(const QString &errMsg)
{
FileSystem::remove(_tmpFile.fileName()); FileSystem::remove(_tmpFile.fileName());
propagator()->_anotherSyncNeeded = true; propagator()->_anotherSyncNeeded = true;
done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded.")); done(SyncFileItem::SoftError, errMsg); // tr("The file downloaded with a broken checksum, will be redownloaded."));

View File

@@ -17,7 +17,6 @@
#include "owncloudpropagator.h" #include "owncloudpropagator.h"
#include "networkjobs.h" #include "networkjobs.h"
#include "clientsideencryption.h" #include "clientsideencryption.h"
#include <common/checksums.h>
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
@@ -236,10 +235,7 @@ private slots:
void abort(PropagatorJob::AbortType abortType) override; void abort(PropagatorJob::AbortType abortType) override;
void slotDownloadProgress(qint64, qint64); void slotDownloadProgress(qint64, qint64);
void slotChecksumFail(const QString &errMsg, const QByteArray &calculatedChecksumType, void slotChecksumFail(const QString &errMsg);
const QByteArray &calculatedChecksum, const ValidateChecksumHeader::FailureReason reason);
void processChecksumRecalculate(const QNetworkReply *reply, const QByteArray &originalChecksumHeader, const QString &errorMessage);
void checksumValidateFailedAbortDownload(const QString &errMsg);
private: private:
void startAfterIsEncryptedIsChecked(); void startAfterIsEncryptedIsChecked();

View File

@@ -319,7 +319,7 @@ void PropagateUploadFileCommon::slotComputeContentChecksum()
// probably temporary one. // probably temporary one.
_item->_modtime = FileSystem::getModTime(filePath); _item->_modtime = FileSystem::getModTime(filePath);
if (_item->_modtime <= 0) { if (_item->_modtime <= 0) {
slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file))); slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
return; return;
} }
@@ -394,7 +394,7 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh
return slotOnErrorStartFolderUnlock(SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath)); return slotOnErrorStartFolderUnlock(SyncFileItem::SoftError, tr("File Removed (start upload) %1").arg(fullFilePath));
} }
if (_item->_modtime <= 0) { if (_item->_modtime <= 0) {
slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file))); slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
return; return;
} }
Q_ASSERT(_item->_modtime > 0); Q_ASSERT(_item->_modtime > 0);
@@ -407,7 +407,7 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray &transmissionCh
_item->_modtime = FileSystem::getModTime(originalFilePath); _item->_modtime = FileSystem::getModTime(originalFilePath);
if (_item->_modtime <= 0) { if (_item->_modtime <= 0) {
slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file))); slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
return; return;
} }
Q_ASSERT(_item->_modtime > 0); Q_ASSERT(_item->_modtime > 0);

View File

@@ -24,9 +24,8 @@ namespace OCC {
* Tags for checksum header. * Tags for checksum header.
* It's here for being shared between Upload- and Download Job * It's here for being shared between Upload- and Download Job
*/ */
constexpr auto checkSumHeaderC = "OC-Checksum"; static const char checkSumHeaderC[] = "OC-Checksum";
constexpr auto contentMd5HeaderC = "Content-MD5"; static const char contentMd5HeaderC[] = "Content-MD5";
constexpr auto checksumRecalculateOnServerHeaderC = "X-Recalculate-Hash";
/** /**
* @brief Declaration of the other propagation jobs * @brief Declaration of the other propagation jobs

View File

@@ -417,15 +417,6 @@ bool Theme::forbidBadSSL() const
#endif #endif
} }
bool Theme::doNotUseProxy() const
{
#ifdef DO_NOT_USE_PROXY
return true;
#else
return false;
#endif
}
QString Theme::forceConfigAuthType() const QString Theme::forceConfigAuthType() const
{ {
return QString(); return QString();

View File

@@ -254,13 +254,6 @@ public:
*/ */
virtual bool forbidBadSSL() const; virtual bool forbidBadSSL() const;
/**
* Forbid use of proxy
*
* When true, the app always connects to the server directly
*/
virtual bool doNotUseProxy() const;
/** /**
* This is only usefull when previous version had a different overrideServerUrl * This is only usefull when previous version had a different overrideServerUrl
* with a different auth type in that case You should then specify "http" or "shibboleth". * with a different auth type in that case You should then specify "http" or "shibboleth".

View File

@@ -650,7 +650,7 @@ OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::se
OCC::Result<void, QString> OCC::CfApiWrapper::createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId) OCC::Result<void, QString> OCC::CfApiWrapper::createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId)
{ {
if (modtime <= 0) { if (modtime <= 0) {
return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(path).arg(modtime)}; return {QString{"Could not update metadata due to invalid modified time for %1: %2"}.arg(path).arg(modtime)};
} }
const auto fileInfo = QFileInfo(path); const auto fileInfo = QFileInfo(path);
@@ -702,7 +702,7 @@ OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::up
Q_ASSERT(handle); Q_ASSERT(handle);
if (modtime <= 0) { if (modtime <= 0) {
return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(pathForHandle(handle)).arg(modtime)}; return {QString{"Could not update metadata due to invalid modified time for %1: %2"}.arg(pathForHandle(handle)).arg(modtime)};
} }
const auto info = replacesPath.isEmpty() ? findPlaceholderInfo(handle) const auto info = replacesPath.isEmpty() ? findPlaceholderInfo(handle)
@@ -740,38 +740,6 @@ OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::up
return OCC::Vfs::ConvertToPlaceholderResult::Ok; return OCC::Vfs::ConvertToPlaceholderResult::Ok;
} }
OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::dehydratePlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId)
{
Q_ASSERT(handle);
if (modtime <= 0) {
return {QString{"Could not update metadata due to invalid modification time for %1: %2"}.arg(pathForHandle(handle)).arg(modtime)};
}
const auto info = findPlaceholderInfo(handle);
if (!info) {
return { "Can't update non existing placeholder info" };
}
const auto fileIdentity = QString::fromUtf8(fileId).toStdWString();
const auto fileIdentitySize = (fileIdentity.length() + 1) * sizeof(wchar_t);
CF_FILE_RANGE dehydrationRange;
dehydrationRange.StartingOffset.QuadPart = 0;
dehydrationRange.Length.QuadPart = size;
const qint64 result = CfUpdatePlaceholder(handle.get(), nullptr,
fileIdentity.data(), sizeToDWORD(fileIdentitySize),
&dehydrationRange, 1, CF_UPDATE_FLAG_MARK_IN_SYNC, nullptr, nullptr);
if (result != S_OK) {
qCWarning(lcCfApiWrapper) << "Couldn't update placeholder info for" << pathForHandle(handle) << ":" << QString::fromWCharArray(_com_error(result).ErrorMessage());
return { "Couldn't update placeholder info" };
}
return OCC::Vfs::ConvertToPlaceholderResult::Ok;
}
OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath) OCC::Result<OCC::Vfs::ConvertToPlaceholderResult, QString> OCC::CfApiWrapper::convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath)
{ {
Q_UNUSED(modtime); Q_UNUSED(modtime);

View File

@@ -94,7 +94,6 @@ NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> set
NEXTCLOUD_CFAPI_EXPORT Result<void, QString> createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId); NEXTCLOUD_CFAPI_EXPORT Result<void, QString> createPlaceholderInfo(const QString &path, time_t modtime, qint64 size, const QByteArray &fileId);
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderInfo(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString()); NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> updatePlaceholderInfo(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath = QString());
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath); NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> convertToPlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId, const QString &replacesPath);
NEXTCLOUD_CFAPI_EXPORT Result<OCC::Vfs::ConvertToPlaceholderResult, QString> dehydratePlaceholder(const FileHandle &handle, time_t modtime, qint64 size, const QByteArray &fileId);
} }

View File

@@ -132,19 +132,26 @@ Result<void, QString> VfsCfApi::createPlaceholder(const SyncFileItem &item)
Result<void, QString> VfsCfApi::dehydratePlaceholder(const SyncFileItem &item) Result<void, QString> VfsCfApi::dehydratePlaceholder(const SyncFileItem &item)
{ {
const auto localPath = QDir::toNativeSeparators(_setupParams.filesystemPath + item._file); const auto previousPin = pinState(item._file);
const auto handle = cfapi::handleForPath(localPath);
if (handle) { if (!FileSystem::remove(_setupParams.filesystemPath + item._file)) {
auto result = cfapi::dehydratePlaceholder(handle, item._modtime, item._size, item._fileId); return QStringLiteral("Couldn't remove %1 to fulfill dehydration").arg(item._file);
if (result) {
return {};
} else {
return result.error();
}
} else {
qCWarning(lcCfApi) << "Couldn't update metadata for non existing file" << localPath;
return {QStringLiteral("Couldn't update metadata")};
} }
const auto r = createPlaceholder(item);
if (!r) {
return r;
}
if (previousPin) {
if (*previousPin == PinState::AlwaysLocal) {
setPinState(item._file, PinState::Unspecified);
} else {
setPinState(item._file, *previousPin);
}
}
return {};
} }
Result<Vfs::ConvertToPlaceholderResult, QString> VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile) Result<Vfs::ConvertToPlaceholderResult, QString> VfsCfApi::convertToPlaceholder(const QString &filename, const SyncFileItem &item, const QString &replacesFile)

View File

@@ -69,7 +69,7 @@ bool VfsSuffix::isHydrating() const
Result<void, QString> VfsSuffix::updateMetadata(const QString &filePath, time_t modtime, qint64, const QByteArray &) Result<void, QString> VfsSuffix::updateMetadata(const QString &filePath, time_t modtime, qint64, const QByteArray &)
{ {
if (modtime <= 0) { if (modtime <= 0) {
return {tr("Error updating metadata due to invalid modification time")}; return {tr("Error updating metadata due to invalid modified time")};
} }
FileSystem::setModTime(filePath, modtime); FileSystem::setModTime(filePath, modtime);
@@ -79,7 +79,7 @@ Result<void, QString> VfsSuffix::updateMetadata(const QString &filePath, time_t
Result<void, QString> VfsSuffix::createPlaceholder(const SyncFileItem &item) Result<void, QString> VfsSuffix::createPlaceholder(const SyncFileItem &item)
{ {
if (item._modtime <= 0) { if (item._modtime <= 0) {
return {tr("Error updating metadata due to invalid modification time")}; return {tr("Error updating metadata due to invalid modified time")};
} }
// The concrete shape of the placeholder is also used in isDehydratedPlaceholder() below // The concrete shape of the placeholder is also used in isDehydratedPlaceholder() below

View File

@@ -70,7 +70,7 @@ bool VfsXAttr::isHydrating() const
Result<void, QString> VfsXAttr::updateMetadata(const QString &filePath, time_t modtime, qint64, const QByteArray &) Result<void, QString> VfsXAttr::updateMetadata(const QString &filePath, time_t modtime, qint64, const QByteArray &)
{ {
if (modtime <= 0) { if (modtime <= 0) {
return {tr("Error updating metadata due to invalid modification time")}; return {tr("Error updating metadata due to invalid modified time")};
} }
FileSystem::setModTime(filePath, modtime); FileSystem::setModTime(filePath, modtime);
@@ -80,7 +80,7 @@ Result<void, QString> VfsXAttr::updateMetadata(const QString &filePath, time_t m
Result<void, QString> VfsXAttr::createPlaceholder(const SyncFileItem &item) Result<void, QString> VfsXAttr::createPlaceholder(const SyncFileItem &item)
{ {
if (item._modtime <= 0) { if (item._modtime <= 0) {
return {tr("Error updating metadata due to invalid modification time")}; return {tr("Error updating metadata due to invalid modified time")};
} }
const auto path = QString(_setupParams.filesystemPath + item._file); const auto path = QString(_setupParams.filesystemPath + item._file);

View File

@@ -8,7 +8,6 @@
]Icon\r* ]Icon\r*
].DS_Store ].DS_Store
].ds_store ].ds_store
*.textClipping
._* ._*
]Thumbs.db ]Thumbs.db
]photothumb.db ]photothumb.db

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