mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-25 22:46:04 +03:00
commit
b835bdc55b
138 changed files with 11630 additions and 7448 deletions
|
@ -199,7 +199,7 @@ if(BUILD_CLIENT)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
find_package(ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED APPLICATION_ICON_NAME)
|
||||
|
@ -213,6 +213,9 @@ add_definitions(-D_UNICODE)
|
|||
if( WIN32 )
|
||||
add_definitions( -D__USE_MINGW_ANSI_STDIO=1 )
|
||||
add_definitions( -DNOMINMAX )
|
||||
# Get APIs from from Vista onwards.
|
||||
add_definitions( -D_WIN32_WINNT=0x0600)
|
||||
add_definitions( -DWINVER=0x0600)
|
||||
endif( WIN32 )
|
||||
|
||||
include(QtVersionAbstraction)
|
||||
|
|
90
ChangeLog
90
ChangeLog
|
@ -1,75 +1,103 @@
|
|||
ChangeLog
|
||||
=========
|
||||
|
||||
version 2.4.0 (2017-1X-XX)
|
||||
* OAuth2 authentication support by opening external browser
|
||||
* Sync Issues: More functional error view including filters and conflicts (#5516)
|
||||
version 2.4.0 (2017-11-XX)
|
||||
* If you're using 2.4.0 alpha1, please upgrade as the alpha1 had an issue with hidden files!
|
||||
* OAuth2 authentication support by opening external browser (#5668)
|
||||
* Sharing: Add support for multiple public link shares (#5655)
|
||||
* Sharing: Add option to copy/email private links (#5627, #5023)
|
||||
* Sharing: Add option to copy/email private links (#5023, #5627)
|
||||
* Sharing: Add option "show file listing" (#5837)
|
||||
* Sharing: Show warning that links are public
|
||||
* Sharing: Many UI improvements
|
||||
* Sharing: Show warning that links are public (#5747)
|
||||
* Sharing: Sharing dialog redesign: multiple share links support (#5695)
|
||||
* Sharing: Make "can edit" partially checked sometimes (#5642)
|
||||
* Sharing: Trigger a sync for folder affected by a change of sharing (#6098)
|
||||
* Wizard: Never propose an existing folder for syncing (#5597)
|
||||
* Wizard: Don't show last page anymore, go to settings directly (#5726)
|
||||
* Wizard: Handle url-shortener redirects #5954
|
||||
* Gui: Display the user server avatar
|
||||
* Wizard: Handle url-shortener redirects (#5954)
|
||||
* Wizard: Resolve url/ redirects only if url/status.php not found (#5954)
|
||||
* Wizard: Add explanation text when server error is shown (#6157)
|
||||
* Wizard: Update the window size on high dpi screen (#6156)
|
||||
* Wizard: Don't report confusing error message (#6116)
|
||||
* Gui: Display the user server avatar (#5482)
|
||||
* Gui: Use display name of user, not internal name
|
||||
* Server URL: Update configuration in case of permanent redirection (#5972)
|
||||
* Gui: Allow to add multiple sync folder connection of the same folder
|
||||
* Gui: Allow to add multiple sync folder connection of the same folder (#6032)
|
||||
* Tray Menu: More detailed status messages
|
||||
* Tray Menu: Shibboleth: raise the browser when clicking on the tray (#6105)
|
||||
* Activity: Link errors from the account tab, allow filtering by account/folder (#5861)
|
||||
* Activity: Present conflicts more prominently (#5894)
|
||||
* Activity: Allow sorting the columns in issues and protocol tabs (#6093, #6086)
|
||||
* Selective Sync: Open sub folder context menu (#5596)
|
||||
* Selective Sync: Skip excluded folders when reading db
|
||||
* Selective Sync: Skip excluded folders when reading db (#5772)
|
||||
* Selective Sync: Remove local files of unselected folder despite other modified files (#5783)
|
||||
* Excludes: remove .htaccess form list of excluded files
|
||||
* Excludes: Remove .htaccess form list of excluded files (#5701)
|
||||
* Excludes: Hardcode desktop.ini
|
||||
* Excludes: Allow escaping "#" (#6012)
|
||||
* Excludes: Use faster matching via QRegularExpression
|
||||
* Discovery: Increase the MAX_DEPTH and show deep folders as ignored
|
||||
* Excludes: Use faster matching via QRegularExpression (#6063)
|
||||
* Discovery: Increase the MAX_DEPTH and show deep folders as ignored (#1067)
|
||||
* Discovery: General speed improvements
|
||||
* Downloads: Remove empty temporary if disk space full (#5746)
|
||||
* Downloads: Re-trigger folder discovery on 404
|
||||
* Quota: PropagateUpload: Model of remote quota, avoid some uploads (#5537)
|
||||
* When creating explorer favorite use more specific windows functions (#5690)
|
||||
* Downloads: Read Content-MD5 header for object store setups
|
||||
* Checksums: Add global disable environment variable (#5017)
|
||||
* Quota: PropagateUpload: Model of remote quota, avoid some uploads (#5537)
|
||||
* Create favorite also in folder wizard (#455)
|
||||
* Windows: Use the application icon for the Windows 8 sidebar favorite (#2446)
|
||||
* Windows: Use the application icon for the Windows 8 sidebar favorite (#2446, #5690)
|
||||
* macOS: Finder sidebar icon (#296)
|
||||
* Overlay Icons: Consider also the "shared by me" as shared (#4788)
|
||||
* Overlay Icons: Update right after sharing (#6115)
|
||||
* Overlay Icons: Fix different case paths not matching (#5257)
|
||||
* Overlay Icons: Detect changes in the shared flag (#6098)
|
||||
* Windows Overlay Icons: Potential hang fixes
|
||||
* Linux Overlay Icons: fix branded nemo and caja shell integration (#5966)
|
||||
* Http credentials: Fix behavior for bad password (#5989)
|
||||
* Credentials: Use per-account keychain entries (#5830)
|
||||
* Credentials: Fix behavior for bad password (#5989)
|
||||
* Credentials: Don't create empty client cert keychain entries (#5752)
|
||||
* Credentials: Namespace windows cred keys (#6125)
|
||||
* Credentials: Use per-account keychain entries (#5830, #6126)
|
||||
* AccountSettings: Triggering log in re-ask about previously rejected certificates (#5819)
|
||||
* Added owncloudcmd bandwidth limit parameter (#5707)
|
||||
* owncloudcmd: Added bandwidth limit parameter (#5707)
|
||||
* owncloudcmd: Fix timestamps, Fix --logdebug
|
||||
* AccountSettings: Sync with clean discovery on Ctrl-F6 (#5666)
|
||||
* Sync: Dynamic sizing of chunks in chunked uploads for improved big file upload performance
|
||||
* Sync: Dynamic sizing of chunks in chunked uploads for improved big file upload performance (#5852)
|
||||
* Sync: Introduce overall errors that are not tied to a file (#5746)
|
||||
* Sync: Better messaging for 507 Insufficient Storage (#5537)
|
||||
* Sync: Create conflicts by comparing the hash of files with identical mtime/size (#5589)
|
||||
* Sync: Upload conflict files if OWNCLOUD_UPLOAD_CONFLICT_FILES environment variable is set (#6038)
|
||||
* Sync: Blacklist: Don't let errors become warnings (#5516)
|
||||
* Sync: Check etag again after active sync (#4116)
|
||||
* Sync: Rename handling fixes: duplicate file ids (#6096)
|
||||
* Sync: Rename handling fixes: File size must be equal
|
||||
* Sync: Rename handling: Fix duplicate files on abort/resume sync (#5949)
|
||||
* Sync: Add capability for invalid filename regexes (#6092)
|
||||
* SyncJournalDB: Fall back to DELETE journal mode if WAL mode does not seem to work (#5723)
|
||||
* SyncJournalDB: Don't crash if the db file is readonly (#6050)
|
||||
* SyncJournalDB: DB close error is not fatal
|
||||
* Fix at least one memory leak
|
||||
* Documentation improvements
|
||||
* Logging improvements (with Qt logging categories), new --logdebug parameter
|
||||
* Logging improvements (With Qt logging categories) (#5671)
|
||||
* Logging filtering per account (#5672)
|
||||
* Crash fixes
|
||||
* Test improvements
|
||||
* Small UI layout fixes
|
||||
* Maintenance Mode: Detect maintenance mode (#4485)
|
||||
* Maintenance Mode: Add a 1 to 5 min reconnection delay (#5872)
|
||||
* HTTP: Send a unique X-Request-ID with each request (#5853)
|
||||
* HTTP: Support HTTP2 when built and running with Qt 5.9.x (Official packages still on Qt 5.6.x)
|
||||
* HTTP: Support HTTP2 when built and running with Qt 5.9.x (Official packages still on Qt 5.6.x) (#5659)
|
||||
* owncloudcmd: Don't start if connection or auth fails (#5692)
|
||||
* csync: Switch build from C to C++
|
||||
* csync: Switch build from C to C++ (#6033)
|
||||
* csync: Refactor a lot to use common data structures to save memory and memory copying
|
||||
* csync: Switch some data structures to Qt data structures
|
||||
* csync: Switch to using upper layer SyncJournalDB
|
||||
* csync: Switch to using upper layer SyncJournalDB (#6087)
|
||||
* Switch 3rdparty/json usage to Qt5's QJson (#5710)
|
||||
* OpenSSL: Don't require directly, only via Qt
|
||||
* Remove iconv dependency, use Qt for file system locale encoding/decoding (emoji filename support on macOS)
|
||||
* Compilation: Remove Qt 4 code
|
||||
* Harmonize source code style with clang-format
|
||||
* Switch over to Qt 5 function pointer signal/slot syntax
|
||||
* Updater: Rudimentary support for beta channel
|
||||
* OpenSSL: Don't require directly, only via Qt (#5833)
|
||||
* Remove iconv dependency, use Qt for file system locale encoding/decoding (emoji filename support on macOS) (#5875)
|
||||
* Compilation: Remove Qt 4 code (#6025, #5702, #5505)
|
||||
* Harmonize source code style with clang-format (#5732)
|
||||
* Switch over to Qt 5 function pointer signal/slot syntax (#6041)
|
||||
* Updater: Rudimentary support for beta channel (#6048)
|
||||
|
||||
version 2.3.4 (2017-11-02)
|
||||
* Checksums: Use addData function to avoid endless loop CPU load issues with Office files
|
||||
* Packaging: Require ZLIB
|
||||
|
||||
version 2.3.3 (2017-08-29)
|
||||
* Chunking NG: Don't use old chunking on new DAV endpoint (#5855)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
set( MIRALL_VERSION_MAJOR 2 )
|
||||
set( MIRALL_VERSION_MINOR 4 )
|
||||
set( MIRALL_VERSION_MINOR 5 )
|
||||
set( MIRALL_VERSION_PATCH 0 )
|
||||
set( MIRALL_VERSION_YEAR 2017 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
|
|
@ -3,7 +3,7 @@ StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Vis versjonsmerknader"
|
|||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "Fant ${APPLICATION_EXECUTABLE}-prosess(er) som må stoppes.$\nVil du at installasjonsprogrammet skal stoppe dem for deg?"
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Terminerer ${APPLICATION_EXECUTABLE}-prosesser."
|
||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "Fant ikke prosess som skulle termineres!"
|
||||
StrCpy $PageReinstall_NEW_Field_1 "En eldre versjon av ${APPLICATION_NAME} er installert på systemet ditt. Det anbefales at du avnistallerer den versjonen før installering av ny versjon. Velg hva du vil gjøre og klikk Neste for å fortsette."
|
||||
StrCpy $PageReinstall_NEW_Field_1 "En eldre versjon av ${APPLICATION_NAME} er installert på systemet ditt. Det anbefales at du avinstallerer den versjonen før installering av ny versjon. Velg hva du vil gjøre og klikk Neste for å fortsette."
|
||||
StrCpy $PageReinstall_NEW_Field_2 "Avinstaller før installering"
|
||||
StrCpy $PageReinstall_NEW_Field_3 "Ikke avinstaller"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Allerede installert"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# Auto-generated - do not modify
|
||||
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Mostrar las notas de la versión"
|
||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "El/los proceso/s ${APPLICATION_EXECUTABLE} debe/n ser detenidos.$\n¿Quiere que el instalador lo haga por usted?"
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Deteniendo el/los proceso/s ${APPLICATION_EXECUTABLE}."
|
||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "El/los proceso(s) ${APPLICATION_EXECUTABLE} debe(n) ser detenido(s).$\n¿Quiere que el instalador lo haga por usted?"
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Deteniendo el/los proceso(s) ${APPLICATION_EXECUTABLE}."
|
||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "¡Proceso a finalizar no encontrado!"
|
||||
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} se encuentra instalada en el sistema. Se recomienda de instalar la versión actual antes de instalar la nueva. Seleccione la operacion deseada y haga click en Siguiente para continuar."
|
||||
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} se encuentra instalada en el sistema. Se recomienda desinstalar la versión actual antes de instalar la nueva. Seleccione la operacion deseada y haga click en Siguiente para continuar."
|
||||
StrCpy $PageReinstall_NEW_Field_2 "Desinstalar antes de instalar"
|
||||
StrCpy $PageReinstall_NEW_Field_3 "No desinstalar"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Ya está instalado"
|
||||
|
@ -17,13 +17,13 @@ StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Elija la opcion de mantenim
|
|||
StrCpy $SEC_APPLICATION_DETAILS "Instalando ${APPLICATION_NAME} esenciales."
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integración para Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Instalando la integración para Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Acceso directo al programa Menú de Inicio"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Acceso directo al programa en Menú de Inicio"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "Añadiendo accesos directos para ${APPLICATION_NAME} en el Menú de Inicio."
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Acceso directo de Escritorio"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Creando accesos directos de escritorio"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atajo de accceso rápido"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atajo de acceso rápido"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Creando un Acceso Directo al Lanzador Rápido"
|
||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} esencial."
|
||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} esenciales."
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Acceso Directo de ${APPLICATION_NAME}"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Acceso Directo de Escritorio para ${APPLICATION_NAME}"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Lanzador Rápido de Accesos Director para ${APPLICATION_NAME}."
|
||||
|
|
|
@ -603,6 +603,35 @@ Section Uninstall
|
|||
Abort $UNINSTALL_ABORT
|
||||
owncloud_installed:
|
||||
|
||||
; Delete Navigation Pane entries added for Windows 10.
|
||||
; On 64bit Windows, the client will be writing to the 64bit registry.
|
||||
${If} ${RunningX64}
|
||||
SetRegView 64
|
||||
${EndIf}
|
||||
StrCpy $0 0
|
||||
loop:
|
||||
; Look at every registered explorer namespace for HKCU and check if it was added by our application
|
||||
; (we write to a custom "ApplicationName" value there).
|
||||
EnumRegKey $1 HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace" $0
|
||||
StrCmp $1 "" done
|
||||
|
||||
ReadRegStr $R0 HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$1" "ApplicationName"
|
||||
StrCmp $R0 "${APPLICATION_NAME}" deleteClsid
|
||||
; Increment the index when not deleting the enumerated key.
|
||||
IntOp $0 $0 + 1
|
||||
goto loop
|
||||
|
||||
deleteClsid:
|
||||
DetailPrint "Removing Navigation Pane CLSID $1"
|
||||
; Should match FolderMan::updateCloudStorageRegistry
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$1"
|
||||
DeleteRegKey HKCU "Software\Classes\CLSID\$1"
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" $1
|
||||
goto loop
|
||||
done:
|
||||
; Go back to the 32bit registry.
|
||||
SetRegView lastused
|
||||
|
||||
;Delete registry keys.
|
||||
DeleteRegValue HKLM "Software\${APPLICATION_VENDOR}\${APPLICATION_NAME}" "VersionBuild"
|
||||
DeleteRegValue HKLM "Software\${APPLICATION_VENDOR}\${APPLICATION_NAME}" "VersionMajor"
|
||||
|
|
|
@ -15,7 +15,7 @@ Configuration File
|
|||
.. include:: conffile.rst
|
||||
|
||||
Environment Variables
|
||||
------------------
|
||||
---------------------
|
||||
.. index:: env vars
|
||||
.. include:: envvars.rst
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ Some interesting values that can be set on the configuration file are:
|
|||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
| ``forceSyncInterval`` | ``7200000`` | The duration of no activity after which a synchronization run shall be triggered automatically. |
|
||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
| ``fullLocalDiscoveryInterval`` | ``3600000`` | The interval after which the next synchronization will perform a full local discovery. |
|
||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
| ``notificationRefreshInterval`` | ``300000`` | Specifies the default interval of checking for new server notifications in milliseconds. |
|
||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
|
||||
|
@ -62,4 +64,4 @@ Some interesting values that can be set on the configuration file are:
|
|||
| | | ``2`` for No Proxy. |
|
||||
+ + +--------------------------------------------------------------------------------------------------------+
|
||||
| | | ``3`` for HTTP(S) Proxy. |
|
||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||
|
|
24
doc/faq.rst
24
doc/faq.rst
|
@ -1,7 +1,7 @@
|
|||
FAQ
|
||||
===
|
||||
|
||||
Some files are continuously uploaded to the server, even when they are not modified.
|
||||
Some Files Are Continuously Uploaded to the Server, Even When They Are Not Modified.
|
||||
------------------------------------------------------------------------------------
|
||||
|
||||
It is possible that another program is changing the modification date of the file.
|
||||
|
@ -11,15 +11,17 @@ continually changes all files, unless you remove
|
|||
from the windows registry.
|
||||
See http://petersteier.wordpress.com/2011/10/22/windows-indexer-changes-modification-dates-of-eml-files/ for more information.
|
||||
|
||||
Syncing breaks when attempting to sync deeper than 50 sub-directories, but the sync client does not report an error (RC=0)
|
||||
--------------------------------------------------------------------------------------------------------------------------
|
||||
Syncing Stops When Attempting To Sync Deeper Than 100 Sub-directories.
|
||||
----------------------------------------------------------------------
|
||||
|
||||
The sync client has been intentionally limited to sync no deeper than
|
||||
fifty sub-directories, to help prevent memory problems.
|
||||
Unfortunately, it, *currently*, does not report an error when this occurs.
|
||||
However, a UI notification is planned for a future release of ownCloud.
|
||||
The sync client has been intentionally limited to sync no deeper than 100
|
||||
sub-directories. The hard limit exists to guard against bugs with cycles
|
||||
like symbolic link loops.
|
||||
When a deeply nested directory is excluded from synchronization it will be
|
||||
listed with other ignored files and directories in the "Not synced" tab of
|
||||
the "Activity" pane.
|
||||
|
||||
I want to move my local sync folder
|
||||
I Want To Move My Local Sync Folder
|
||||
-----------------------------------
|
||||
|
||||
The ownCloud desktop client does not provide a way to change the local sync directory.
|
||||
|
@ -29,20 +31,20 @@ Specifically, you have to:
|
|||
1. Remove the existing connection which syncs to the wrong directory
|
||||
2. Add a new connection which syncs to the desired directory
|
||||
|
||||
.. image:: images/setup/ownCloud-remove_existing_connection.png
|
||||
.. figure:: images/setup/ownCloud-remove_existing_connection.png
|
||||
:alt: Remove an existing connection
|
||||
|
||||
To do so, in the client UI, which you can see above, click the "**Account**" drop-down menu and then click "Remove".
|
||||
This will display a "**Confirm Account Removal**" dialog window.
|
||||
|
||||
.. image:: images/setup/ownCloud-remove_existing_connection_confirmation_dialog.png
|
||||
.. figure:: images/setup/ownCloud-remove_existing_connection_confirmation_dialog.png
|
||||
:alt: Remove existing connection confirmation dialog
|
||||
|
||||
If you're sure, click "**Remove connection**".
|
||||
|
||||
Then, click the Account drop-down menu again, and this time click "**Add new**".
|
||||
|
||||
.. image:: images/setup/ownCloud-replacement_connection_wizard.png
|
||||
.. figure:: images/setup/ownCloud-replacement_connection_wizard.png
|
||||
:alt: Replacement connection wizard
|
||||
|
||||
This opens the ownCloud Connection Wizard, which you can see above, *but* with an extra option.
|
||||
|
|
|
@ -3,7 +3,7 @@ Installing the Desktop Synchronization Client
|
|||
=============================================
|
||||
|
||||
You can download the latest version of the ownCloud Desktop Synchronization
|
||||
Client from the `ownCloud download page <https://owncloud.org/install/>`_.
|
||||
Client from the `ownCloud download page`_.
|
||||
There are clients for Linux, Mac OS X, and Microsoft Windows.
|
||||
|
||||
Installation on Mac OS X and Windows is the same as for any software
|
||||
|
@ -24,6 +24,20 @@ KWallet, so that the sync client can login automatically.
|
|||
You will also find links to source code archives and older versions on the
|
||||
download page.
|
||||
|
||||
System Requirements
|
||||
----------------------------------
|
||||
|
||||
- Windows 7+
|
||||
- Mac OS X 10.7+ (**64-bit only**)
|
||||
- CentOS 6 & 7 (64-bit only)
|
||||
- Debian 7.0 & 8.0 & 9.0
|
||||
- Fedora 24 & 25 & 26
|
||||
- Ubuntu 16.04 & 16.10 & 17.04
|
||||
- openSUSE Leap 42.1 & 42.2 & 42.3
|
||||
|
||||
.. note::
|
||||
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous `LTS`_.
|
||||
|
||||
Installation Wizard
|
||||
-------------------
|
||||
|
||||
|
@ -58,3 +72,7 @@ synchronizing your files.
|
|||
Web GUI, and one to open your local ownCloud folder
|
||||
|
||||
Click the Finish button, and you're all done.
|
||||
|
||||
.. Links
|
||||
|
||||
.. _ownCloud download page: https://owncloud.com/download/#desktop-clients
|
||||
|
|
|
@ -219,6 +219,111 @@ X-GNOME-Autostart-Delay=3
|
|||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
Comment[oc]=@APPLICATION_NAME@ sincronizacion del client
|
||||
GenericName[oc]=Dorsièr de Sincronizacion
|
||||
|
|
6094
src/3rdparty/sqlite3/sqlite3.c
vendored
6094
src/3rdparty/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load diff
189
src/3rdparty/sqlite3/sqlite3.h
vendored
189
src/3rdparty/sqlite3/sqlite3.h
vendored
|
@ -115,15 +115,17 @@ extern "C" {
|
|||
** a string which identifies a particular check-in of SQLite
|
||||
** within its configuration management system. ^The SQLITE_SOURCE_ID
|
||||
** string contains the date and time of the check-in (UTC) and a SHA1
|
||||
** or SHA3-256 hash of the entire source tree.
|
||||
** or SHA3-256 hash of the entire source tree. If the source code has
|
||||
** been edited in any way since it was last checked in, then the last
|
||||
** four hexadecimal digits of the hash may be modified.
|
||||
**
|
||||
** See also: [sqlite3_libversion()],
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.20.1"
|
||||
#define SQLITE_VERSION_NUMBER 3020001
|
||||
#define SQLITE_SOURCE_ID "2017-08-24 16:21:36 8d3a7ea6c5690d6b7c3767558f4f01b511c55463e3f9e64506801fe9b74dce34"
|
||||
#define SQLITE_VERSION "3.21.0"
|
||||
#define SQLITE_VERSION_NUMBER 3021000
|
||||
#define SQLITE_SOURCE_ID "2017-10-24 18:55:49 1a584e499906b5c87ec7d43d4abce641fdf017c42125b083109bc77c4de48827"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
@ -139,7 +141,7 @@ extern "C" {
|
|||
**
|
||||
** <blockquote><pre>
|
||||
** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
|
||||
** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
|
||||
** assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
|
||||
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
|
||||
** </pre></blockquote>)^
|
||||
**
|
||||
|
@ -149,9 +151,11 @@ extern "C" {
|
|||
** function is provided for use in DLLs since DLL users usually do not have
|
||||
** direct access to string constants within the DLL. ^The
|
||||
** sqlite3_libversion_number() function returns an integer equal to
|
||||
** [SQLITE_VERSION_NUMBER]. ^The sqlite3_sourceid() function returns
|
||||
** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns
|
||||
** a pointer to a string constant whose value is the same as the
|
||||
** [SQLITE_SOURCE_ID] C preprocessor macro.
|
||||
** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built
|
||||
** using an edited copy of [the amalgamation], then the last four characters
|
||||
** of the hash might be different from [SQLITE_SOURCE_ID].)^
|
||||
**
|
||||
** See also: [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
|
@ -432,7 +436,7 @@ SQLITE_API int sqlite3_exec(
|
|||
#define SQLITE_FULL 13 /* Insertion failed because database is full */
|
||||
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
|
||||
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
|
||||
#define SQLITE_EMPTY 16 /* Not used */
|
||||
#define SQLITE_EMPTY 16 /* Internal use only */
|
||||
#define SQLITE_SCHEMA 17 /* The database schema changed */
|
||||
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
|
||||
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
|
||||
|
@ -494,6 +498,9 @@ SQLITE_API int sqlite3_exec(
|
|||
#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8))
|
||||
#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8))
|
||||
#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8))
|
||||
#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8))
|
||||
#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
|
||||
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
|
||||
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
|
||||
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
|
||||
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
|
||||
|
@ -580,6 +587,11 @@ SQLITE_API int sqlite3_exec(
|
|||
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
|
||||
** read-only media and cannot be changed even by processes with
|
||||
** elevated privileges.
|
||||
**
|
||||
** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
|
||||
** filesystem supports doing multiple write operations atomically when those
|
||||
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
|
||||
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
|
||||
*/
|
||||
#define SQLITE_IOCAP_ATOMIC 0x00000001
|
||||
#define SQLITE_IOCAP_ATOMIC512 0x00000002
|
||||
|
@ -595,6 +607,7 @@ SQLITE_API int sqlite3_exec(
|
|||
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
|
||||
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
|
||||
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
|
||||
#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
|
||||
|
||||
/*
|
||||
** CAPI3REF: File Locking Levels
|
||||
|
@ -729,6 +742,7 @@ struct sqlite3_file {
|
|||
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
|
||||
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
|
||||
** <li> [SQLITE_IOCAP_IMMUTABLE]
|
||||
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
|
||||
** </ul>
|
||||
**
|
||||
** The SQLITE_IOCAP_ATOMIC property means that all writes of
|
||||
|
@ -1012,6 +1026,40 @@ struct sqlite3_io_methods {
|
|||
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
|
||||
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
|
||||
** this opcode.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]]
|
||||
** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then
|
||||
** the file descriptor is placed in "batch write mode", which
|
||||
** means all subsequent write operations will be deferred and done
|
||||
** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems
|
||||
** that do not support batch atomic writes will return SQLITE_NOTFOUND.
|
||||
** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to
|
||||
** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or
|
||||
** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make
|
||||
** no VFS interface calls on the same [sqlite3_file] file descriptor
|
||||
** except for calls to the xWrite method and the xFileControl method
|
||||
** with [SQLITE_FCNTL_SIZE_HINT].
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]]
|
||||
** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write
|
||||
** operations since the previous successful call to
|
||||
** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically.
|
||||
** This file control returns [SQLITE_OK] if and only if the writes were
|
||||
** all performed successfully and have been committed to persistent storage.
|
||||
** ^Regardless of whether or not it is successful, this file control takes
|
||||
** the file descriptor out of batch write mode so that all subsequent
|
||||
** write operations are independent.
|
||||
** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without
|
||||
** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]]
|
||||
** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write
|
||||
** operations since the previous successful call to
|
||||
** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back.
|
||||
** ^This file control takes the file descriptor out of batch write mode
|
||||
** so that all subsequent write operations are independent.
|
||||
** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without
|
||||
** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
|
||||
** </ul>
|
||||
*/
|
||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||
|
@ -1043,6 +1091,9 @@ struct sqlite3_io_methods {
|
|||
#define SQLITE_FCNTL_JOURNAL_POINTER 28
|
||||
#define SQLITE_FCNTL_WIN32_GET_HANDLE 29
|
||||
#define SQLITE_FCNTL_PDB 30
|
||||
#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31
|
||||
#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
|
||||
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
|
||||
|
||||
/* deprecated names */
|
||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||
|
@ -1613,6 +1664,16 @@ struct sqlite3_mem_methods {
|
|||
** routines with a wrapper that simulations memory allocation failure or
|
||||
** tracks memory usage, for example. </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
|
||||
** type int, interpreted as a boolean, which if true provides a hint to
|
||||
** SQLite that it should avoid large memory allocations if possible.
|
||||
** SQLite will run faster if it is free to make large memory allocations,
|
||||
** but some application might prefer to run slower in exchange for
|
||||
** guarantees about memory fragmentation that are possible if large
|
||||
** allocations are avoided. This hint is normally off.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
|
||||
** interpreted as a boolean, which enables or disables the collection of
|
||||
|
@ -1630,25 +1691,7 @@ struct sqlite3_mem_methods {
|
|||
** </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
|
||||
** that SQLite can use for scratch memory. ^(There are three arguments
|
||||
** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
|
||||
** aligned memory buffer from which the scratch allocations will be
|
||||
** drawn, the size of each scratch allocation (sz),
|
||||
** and the maximum number of scratch allocations (N).)^
|
||||
** The first argument must be a pointer to an 8-byte aligned buffer
|
||||
** of at least sz*N bytes of memory.
|
||||
** ^SQLite will not use more than one scratch buffers per thread.
|
||||
** ^SQLite will never request a scratch buffer that is more than 6
|
||||
** times the database page size.
|
||||
** ^If SQLite needs needs additional
|
||||
** scratch memory beyond what is provided by this configuration option, then
|
||||
** [sqlite3_malloc()] will be used to obtain the memory needed.<p>
|
||||
** ^When the application provides any amount of scratch memory using
|
||||
** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
|
||||
** [sqlite3_malloc|heap allocations].
|
||||
** This can help [Robson proof|prevent memory allocation failures] due to heap
|
||||
** fragmentation in low-memory embedded systems.
|
||||
** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt>
|
||||
|
@ -1684,8 +1727,7 @@ struct sqlite3_mem_methods {
|
|||
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
|
||||
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
|
||||
** that SQLite will use for all of its dynamic memory allocation needs
|
||||
** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
|
||||
** [SQLITE_CONFIG_PAGECACHE].
|
||||
** beyond those provided for by [SQLITE_CONFIG_PAGECACHE].
|
||||
** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
|
||||
** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
|
||||
** [SQLITE_ERROR] if invoked otherwise.
|
||||
|
@ -1878,7 +1920,7 @@ struct sqlite3_mem_methods {
|
|||
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
|
||||
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
|
||||
#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */
|
||||
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
|
||||
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
|
||||
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
|
||||
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
|
||||
|
@ -1899,6 +1941,7 @@ struct sqlite3_mem_methods {
|
|||
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
|
||||
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
|
||||
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
|
||||
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
|
||||
|
||||
/*
|
||||
** CAPI3REF: Database Connection Configuration Options
|
||||
|
@ -3099,10 +3142,10 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
|
|||
** ^If [URI filename] interpretation is enabled, and the filename argument
|
||||
** begins with "file:", then the filename is interpreted as a URI. ^URI
|
||||
** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is
|
||||
** set in the fourth argument to sqlite3_open_v2(), or if it has
|
||||
** set in the third argument to sqlite3_open_v2(), or if it has
|
||||
** been enabled globally using the [SQLITE_CONFIG_URI] option with the
|
||||
** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
|
||||
** As of SQLite version 3.7.7, URI filename interpretation is turned off
|
||||
** URI filename interpretation is turned off
|
||||
** by default, but future releases of SQLite might enable URI filename
|
||||
** interpretation by default. See "[URI filenames]" for additional
|
||||
** information.
|
||||
|
@ -3776,8 +3819,9 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*);
|
|||
** implementation of [application-defined SQL functions] are protected.
|
||||
** ^The sqlite3_value object returned by
|
||||
** [sqlite3_column_value()] is unprotected.
|
||||
** Unprotected sqlite3_value objects may only be used with
|
||||
** [sqlite3_result_value()] and [sqlite3_bind_value()].
|
||||
** Unprotected sqlite3_value objects may only be used as arguments
|
||||
** to [sqlite3_result_value()], [sqlite3_bind_value()], and
|
||||
** [sqlite3_value_dup()].
|
||||
** The [sqlite3_value_blob | sqlite3_value_type()] family of
|
||||
** interfaces require protected sqlite3_value objects.
|
||||
*/
|
||||
|
@ -4199,7 +4243,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
|
|||
** other than [SQLITE_ROW] before any subsequent invocation of
|
||||
** sqlite3_step(). Failure to reset the prepared statement using
|
||||
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
|
||||
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
|
||||
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
|
||||
** sqlite3_step() began
|
||||
** calling [sqlite3_reset()] automatically in this circumstance rather
|
||||
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
|
||||
|
@ -6203,15 +6247,20 @@ struct sqlite3_index_info {
|
|||
** an operator that is part of a constraint term in the wHERE clause of
|
||||
** a query that uses a [virtual table].
|
||||
*/
|
||||
#define SQLITE_INDEX_CONSTRAINT_EQ 2
|
||||
#define SQLITE_INDEX_CONSTRAINT_GT 4
|
||||
#define SQLITE_INDEX_CONSTRAINT_LE 8
|
||||
#define SQLITE_INDEX_CONSTRAINT_LT 16
|
||||
#define SQLITE_INDEX_CONSTRAINT_GE 32
|
||||
#define SQLITE_INDEX_CONSTRAINT_MATCH 64
|
||||
#define SQLITE_INDEX_CONSTRAINT_LIKE 65
|
||||
#define SQLITE_INDEX_CONSTRAINT_GLOB 66
|
||||
#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
|
||||
#define SQLITE_INDEX_CONSTRAINT_EQ 2
|
||||
#define SQLITE_INDEX_CONSTRAINT_GT 4
|
||||
#define SQLITE_INDEX_CONSTRAINT_LE 8
|
||||
#define SQLITE_INDEX_CONSTRAINT_LT 16
|
||||
#define SQLITE_INDEX_CONSTRAINT_GE 32
|
||||
#define SQLITE_INDEX_CONSTRAINT_MATCH 64
|
||||
#define SQLITE_INDEX_CONSTRAINT_LIKE 65
|
||||
#define SQLITE_INDEX_CONSTRAINT_GLOB 66
|
||||
#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
|
||||
#define SQLITE_INDEX_CONSTRAINT_NE 68
|
||||
#define SQLITE_INDEX_CONSTRAINT_ISNOT 69
|
||||
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
|
||||
#define SQLITE_INDEX_CONSTRAINT_ISNULL 71
|
||||
#define SQLITE_INDEX_CONSTRAINT_IS 72
|
||||
|
||||
/*
|
||||
** CAPI3REF: Register A Virtual Table Implementation
|
||||
|
@ -6963,7 +7012,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||
#define SQLITE_TESTCTRL_RESERVE 14
|
||||
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
||||
#define SQLITE_TESTCTRL_ISKEYWORD 16
|
||||
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17
|
||||
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
|
||||
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
|
||||
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
|
||||
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
|
||||
|
@ -7022,8 +7071,7 @@ SQLITE_API int sqlite3_status64(
|
|||
** <dd>This parameter is the current amount of memory checked out
|
||||
** using [sqlite3_malloc()], either directly or indirectly. The
|
||||
** figure includes calls made to [sqlite3_malloc()] by the application
|
||||
** and internal memory usage by the SQLite library. Scratch memory
|
||||
** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache
|
||||
** and internal memory usage by the SQLite library. Auxiliary page-cache
|
||||
** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in
|
||||
** this parameter. The amount returned is the sum of the allocation
|
||||
** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^
|
||||
|
@ -7061,29 +7109,14 @@ SQLITE_API int sqlite3_status64(
|
|||
** *pHighwater parameter to [sqlite3_status()] is of interest.
|
||||
** The value written into the *pCurrent parameter is undefined.</dd>)^
|
||||
**
|
||||
** [[SQLITE_STATUS_SCRATCH_USED]] ^(<dt>SQLITE_STATUS_SCRATCH_USED</dt>
|
||||
** <dd>This parameter returns the number of allocations used out of the
|
||||
** [scratch memory allocator] configured using
|
||||
** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not
|
||||
** in bytes. Since a single thread may only have one scratch allocation
|
||||
** outstanding at time, this parameter also reports the number of threads
|
||||
** using scratch memory at the same time.</dd>)^
|
||||
** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt>
|
||||
** <dd>No longer used.</dd>
|
||||
**
|
||||
** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt>
|
||||
** <dd>This parameter returns the number of bytes of scratch memory
|
||||
** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH]
|
||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The values
|
||||
** returned include overflows because the requested allocation was too
|
||||
** larger (that is, because the requested allocation was larger than the
|
||||
** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer
|
||||
** slots were available.
|
||||
** </dd>)^
|
||||
** <dd>No longer used.</dd>
|
||||
**
|
||||
** [[SQLITE_STATUS_SCRATCH_SIZE]] ^(<dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
|
||||
** <dd>This parameter records the largest memory allocation request
|
||||
** handed to [scratch memory allocator]. Only the value returned in the
|
||||
** *pHighwater parameter to [sqlite3_status()] is of interest.
|
||||
** The value written into the *pCurrent parameter is undefined.</dd>)^
|
||||
** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
|
||||
** <dd>No longer used.</dd>
|
||||
**
|
||||
** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt>
|
||||
** <dd>The *pHighwater parameter records the deepest parser stack.
|
||||
|
@ -7096,12 +7129,12 @@ SQLITE_API int sqlite3_status64(
|
|||
#define SQLITE_STATUS_MEMORY_USED 0
|
||||
#define SQLITE_STATUS_PAGECACHE_USED 1
|
||||
#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2
|
||||
#define SQLITE_STATUS_SCRATCH_USED 3
|
||||
#define SQLITE_STATUS_SCRATCH_OVERFLOW 4
|
||||
#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */
|
||||
#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */
|
||||
#define SQLITE_STATUS_MALLOC_SIZE 5
|
||||
#define SQLITE_STATUS_PARSER_STACK 6
|
||||
#define SQLITE_STATUS_PAGECACHE_SIZE 7
|
||||
#define SQLITE_STATUS_SCRATCH_SIZE 8
|
||||
#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */
|
||||
#define SQLITE_STATUS_MALLOC_COUNT 9
|
||||
|
||||
/*
|
||||
|
@ -9198,8 +9231,8 @@ SQLITE_API int sqlite3session_diff(
|
|||
*/
|
||||
SQLITE_API int sqlite3session_patchset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
|
||||
void **ppPatchset /* OUT: Buffer containing changeset */
|
||||
int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */
|
||||
void **ppPatchset /* OUT: Buffer containing patchset */
|
||||
);
|
||||
|
||||
/*
|
||||
|
@ -9966,12 +9999,12 @@ SQLITE_API int sqlite3changeset_apply(
|
|||
**
|
||||
** <table border=1 style="margin-left:8ex;margin-right:8ex">
|
||||
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
|
||||
** <tr><td>sqlite3changeset_apply_str<td>[sqlite3changeset_apply]
|
||||
** <tr><td>sqlite3changeset_concat_str<td>[sqlite3changeset_concat]
|
||||
** <tr><td>sqlite3changeset_invert_str<td>[sqlite3changeset_invert]
|
||||
** <tr><td>sqlite3changeset_start_str<td>[sqlite3changeset_start]
|
||||
** <tr><td>sqlite3session_changeset_str<td>[sqlite3session_changeset]
|
||||
** <tr><td>sqlite3session_patchset_str<td>[sqlite3session_patchset]
|
||||
** <tr><td>sqlite3changeset_apply_strm<td>[sqlite3changeset_apply]
|
||||
** <tr><td>sqlite3changeset_concat_strm<td>[sqlite3changeset_concat]
|
||||
** <tr><td>sqlite3changeset_invert_strm<td>[sqlite3changeset_invert]
|
||||
** <tr><td>sqlite3changeset_start_strm<td>[sqlite3changeset_start]
|
||||
** <tr><td>sqlite3session_changeset_strm<td>[sqlite3session_changeset]
|
||||
** <tr><td>sqlite3session_patchset_strm<td>[sqlite3session_patchset]
|
||||
** </table>
|
||||
**
|
||||
** Non-streaming functions that accept changesets (or patchsets) as input
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
|
||||
#include "theme.h"
|
||||
#include "netrcparser.h"
|
||||
#include "libsync/logger.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
@ -189,7 +190,7 @@ void help()
|
|||
std::cout << " --downlimit [n] Limit the download speed of files to n KB/s" << std::endl;
|
||||
std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
|
||||
std::cout << " --version, -v Display version and exit" << std::endl;
|
||||
std::cout << " --debug More verbose logging" << std::endl;
|
||||
std::cout << " --logdebug More verbose logging" << std::endl;
|
||||
std::cout << "" << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
@ -267,6 +268,9 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
|||
options->uplimit = it.next().toInt() * 1000;
|
||||
} else if (option == "--downlimit" && !it.peekNext().startsWith("-")) {
|
||||
options->downlimit = it.next().toInt() * 1000;
|
||||
} else if (option == "--logdebug") {
|
||||
Logger::instance()->setLogFile("-");
|
||||
Logger::instance()->setLogDebug(true);
|
||||
} else {
|
||||
help();
|
||||
}
|
||||
|
@ -330,6 +334,8 @@ int main(int argc, char **argv)
|
|||
csync_set_log_level(options.silent ? 1 : 11);
|
||||
if (options.silent) {
|
||||
qInstallMsgHandler(nullMessageHandler);
|
||||
} else {
|
||||
qSetMessagePattern("%{time MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}");
|
||||
}
|
||||
|
||||
AccountPtr account = Account::create();
|
||||
|
|
|
@ -90,6 +90,21 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
|
|||
return header;
|
||||
}
|
||||
|
||||
QByteArray findBestChecksum(const QByteArray &checksums)
|
||||
{
|
||||
int i = 0;
|
||||
// The order of the searches here defines the preference ordering.
|
||||
if (-1 != (i = checksums.indexOf("SHA1:"))
|
||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
||||
// Now i is the start of the best checksum
|
||||
// Grab it until the next space or end of string.
|
||||
auto checksum = checksums.mid(i);
|
||||
return checksum.mid(0, checksum.indexOf(" "));
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum)
|
||||
{
|
||||
if (header.isEmpty()) {
|
||||
|
@ -120,7 +135,7 @@ QByteArray parseChecksumHeaderType(const QByteArray &header)
|
|||
|
||||
bool uploadChecksumEnabled()
|
||||
{
|
||||
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD").isEmpty();
|
||||
static bool enabled = qEnvironmentVariableIsEmpty("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
@ -133,6 +148,12 @@ QByteArray contentChecksumType()
|
|||
return type;
|
||||
}
|
||||
|
||||
static bool checksumComputationEnabled()
|
||||
{
|
||||
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_COMPUTATIONS").isEmpty();
|
||||
return enabled;
|
||||
}
|
||||
|
||||
ComputeChecksum::ComputeChecksum(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
|
@ -150,6 +171,8 @@ QByteArray ComputeChecksum::checksumType() const
|
|||
|
||||
void ComputeChecksum::start(const QString &filePath)
|
||||
{
|
||||
qCInfo(lcChecksums) << "Computing" << checksumType() << "checksum of" << filePath << "in a thread";
|
||||
|
||||
// Calculate the checksum in a different thread first.
|
||||
connect(&_watcher, &QFutureWatcherBase::finished,
|
||||
this, &ComputeChecksum::slotCalculationDone,
|
||||
|
@ -159,6 +182,11 @@ void ComputeChecksum::start(const QString &filePath)
|
|||
|
||||
QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray &checksumType)
|
||||
{
|
||||
if (!checksumComputationEnabled()) {
|
||||
qCWarning(lcChecksums) << "Checksum computation disabled by environment variable";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (checksumType == checkSumMD5C) {
|
||||
return FileSystem::calcMd5(filePath);
|
||||
} else if (checksumType == checkSumSHA1C) {
|
||||
|
@ -237,6 +265,7 @@ QByteArray CSyncChecksumHook::hook(const QByteArray &path, const QByteArray &oth
|
|||
if (type.isEmpty())
|
||||
return NULL;
|
||||
|
||||
qCInfo(lcChecksums) << "Computing" << type << "checksum of" << path << "in the csync hook";
|
||||
QByteArray checksum = ComputeChecksum::computeNow(QString::fromUtf8(path), type);
|
||||
if (checksum.isNull()) {
|
||||
qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path;
|
||||
|
|
|
@ -36,6 +36,16 @@ static const char checkSumAdlerC[] = "Adler32";
|
|||
|
||||
class SyncJournalDb;
|
||||
|
||||
/**
|
||||
* Returns the highest-quality checksum in a 'checksums'
|
||||
* property retrieved from the server.
|
||||
*
|
||||
* Example: "ADLER32:1231 SHA1:ab124124 MD5:2131affa21"
|
||||
* -> "SHA1:ab124124"
|
||||
*/
|
||||
OCSYNC_EXPORT QByteArray findBestChecksum(const QByteArray &checksums);
|
||||
|
||||
|
||||
/// Creates a checksum header from type and value.
|
||||
OCSYNC_EXPORT QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
|
||||
|
|
|
@ -358,26 +358,19 @@ QString FileSystem::fileSystemForPath(const QString &path)
|
|||
|
||||
#define BUFSIZE qint64(500 * 1024) // 500 KiB
|
||||
|
||||
static QByteArray readToCrypto(const QString &filename, QCryptographicHash::Algorithm algo)
|
||||
{
|
||||
QFile file(filename);
|
||||
const qint64 bufSize = qMin(BUFSIZE, file.size() + 1);
|
||||
QByteArray buf(bufSize, Qt::Uninitialized);
|
||||
QByteArray arr;
|
||||
QCryptographicHash crypto(algo);
|
||||
static QByteArray readToCrypto( const QString& filename, QCryptographicHash::Algorithm algo )
|
||||
{
|
||||
QFile file(filename);
|
||||
QByteArray arr;
|
||||
QCryptographicHash crypto( algo );
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
qint64 size;
|
||||
while (!file.atEnd()) {
|
||||
size = file.read(buf.data(), bufSize);
|
||||
if (size > 0) {
|
||||
crypto.addData(buf.data(), size);
|
||||
}
|
||||
}
|
||||
arr = crypto.result().toHex();
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
if (crypto.addData(&file)) {
|
||||
arr = crypto.result().toHex();
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
QByteArray FileSystem::calcMd5(const QString &filename)
|
||||
{
|
||||
|
|
|
@ -85,24 +85,32 @@ bool SqlDatabase::openHelper(const QString &filename, int sqliteFlags)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SqlDatabase::checkDb()
|
||||
SqlDatabase::CheckDbResult SqlDatabase::checkDb()
|
||||
{
|
||||
// quick_check can fail with a disk IO error when diskspace is low
|
||||
SqlQuery quick_check(*this);
|
||||
quick_check.prepare("PRAGMA quick_check;", /*allow_failure=*/true);
|
||||
|
||||
if (quick_check.prepare("PRAGMA quick_check;", /*allow_failure=*/true) != SQLITE_OK) {
|
||||
qCWarning(lcSql) << "Error preparing quick_check on database";
|
||||
_errId = quick_check.errorId();
|
||||
_error = quick_check.error();
|
||||
return CheckDbResult::CantPrepare;
|
||||
}
|
||||
if (!quick_check.exec()) {
|
||||
qCWarning(lcSql) << "Error running quick_check on database";
|
||||
return false;
|
||||
_errId = quick_check.errorId();
|
||||
_error = quick_check.error();
|
||||
return CheckDbResult::CantExec;
|
||||
}
|
||||
|
||||
quick_check.next();
|
||||
QString result = quick_check.stringValue(0);
|
||||
if (result != "ok") {
|
||||
qCWarning(lcSql) << "quick_check returned failure:" << result;
|
||||
return false;
|
||||
return CheckDbResult::NotOk;
|
||||
}
|
||||
|
||||
return true;
|
||||
return CheckDbResult::Ok;
|
||||
}
|
||||
|
||||
bool SqlDatabase::openOrCreateReadWrite(const QString &filename)
|
||||
|
@ -115,13 +123,25 @@ bool SqlDatabase::openOrCreateReadWrite(const QString &filename)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!checkDb()) {
|
||||
// When disk space is low, checking the db may fail even though it's fine.
|
||||
qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath());
|
||||
if (freeSpace != -1 && freeSpace < 1000000) {
|
||||
qCWarning(lcSql) << "Consistency check failed, disk space is low, aborting" << freeSpace;
|
||||
close();
|
||||
return false;
|
||||
auto checkResult = checkDb();
|
||||
if (checkResult != CheckDbResult::Ok) {
|
||||
if (checkResult == CheckDbResult::CantPrepare) {
|
||||
// When disk space is low, preparing may fail even though the db is fine.
|
||||
// Typically CANTOPEN or IOERR.
|
||||
qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath());
|
||||
if (freeSpace != -1 && freeSpace < 1000000) {
|
||||
qCWarning(lcSql) << "Can't prepare consistency check and disk space is low:" << freeSpace;
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Even when there's enough disk space, it might very well be that the
|
||||
// file is on a read-only filesystem and can't be opened because of that.
|
||||
if (_errId == SQLITE_CANTOPEN) {
|
||||
qCWarning(lcSql) << "Can't open db to prepare consistency check, aborting";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qCCritical(lcSql) << "Consistency check failed, removing broken db" << filename;
|
||||
|
@ -144,7 +164,7 @@ bool SqlDatabase::openReadOnly(const QString &filename)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!checkDb()) {
|
||||
if (checkDb() != CheckDbResult::Ok) {
|
||||
qCWarning(lcSql) << "Consistency check failed in readonly mode, giving up" << filename;
|
||||
close();
|
||||
return false;
|
||||
|
@ -164,8 +184,8 @@ void SqlDatabase::close()
|
|||
{
|
||||
if (_db) {
|
||||
SQLITE_DO(sqlite3_close(_db));
|
||||
// Fatal because reopening an unclosed db might be problematic.
|
||||
ENFORCE(_errId == SQLITE_OK, "Error when closing DB");
|
||||
if (_errId != SQLITE_OK)
|
||||
qCWarning(lcSql) << "Closing database failed" << _error;
|
||||
_db = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,8 +48,15 @@ public:
|
|||
sqlite3 *sqliteDb();
|
||||
|
||||
private:
|
||||
enum class CheckDbResult {
|
||||
Ok,
|
||||
CantPrepare,
|
||||
CantExec,
|
||||
NotOk,
|
||||
};
|
||||
|
||||
bool openHelper(const QString &filename, int sqliteFlags);
|
||||
bool checkDb();
|
||||
CheckDbResult checkDb();
|
||||
|
||||
sqlite3 *_db;
|
||||
QString _error; // last error string
|
||||
|
|
|
@ -547,13 +547,28 @@ bool SyncJournalDb::checkConnect()
|
|||
return sqlFail("prepare _getFileRecordQueryByFileId", *_getFileRecordQueryByFileId);
|
||||
}
|
||||
|
||||
// This query is used to skip discovery and fill the tree from the
|
||||
// database instead
|
||||
_getFilesBelowPathQuery.reset(new SqlQuery(_db));
|
||||
if (_getFilesBelowPathQuery->prepare(
|
||||
GET_FILE_RECORD_QUERY
|
||||
" WHERE path > (?1||'/') AND path < (?1||'0') ORDER BY path||'/' ASC")) {
|
||||
" WHERE path > (?1||'/') AND path < (?1||'0')"
|
||||
// We want to ensure that the contents of a directory are sorted
|
||||
// directly behind the directory itself. Without this ORDER BY
|
||||
// an ordering like foo, foo-2, foo/file would be returned.
|
||||
// With the trailing /, we get foo-2, foo, foo/file. This property
|
||||
// is used in fill_tree_from_db().
|
||||
" ORDER BY path||'/' ASC")) {
|
||||
return sqlFail("prepare _getFilesBelowPathQuery", *_getFilesBelowPathQuery);
|
||||
}
|
||||
|
||||
_getAllFilesQuery.reset(new SqlQuery(_db));
|
||||
if (_getAllFilesQuery->prepare(
|
||||
GET_FILE_RECORD_QUERY
|
||||
" ORDER BY path||'/' ASC")) {
|
||||
return sqlFail("prepare _getAllFilesQuery", *_getAllFilesQuery);
|
||||
}
|
||||
|
||||
_setFileRecordQuery.reset(new SqlQuery(_db));
|
||||
if (_setFileRecordQuery->prepare("INSERT OR REPLACE INTO metadata "
|
||||
"(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId) "
|
||||
|
@ -704,6 +719,7 @@ void SyncJournalDb::close()
|
|||
_getFileRecordQueryByInode.reset(0);
|
||||
_getFileRecordQueryByFileId.reset(0);
|
||||
_getFilesBelowPathQuery.reset(0);
|
||||
_getAllFilesQuery.reset(0);
|
||||
_setFileRecordQuery.reset(0);
|
||||
_setFileRecordChecksumQuery.reset(0);
|
||||
_setFileRecordLocalMetadataQuery.reset(0);
|
||||
|
@ -1094,15 +1110,10 @@ bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *r
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SyncJournalDb::getFileRecordByFileId(const QByteArray &fileId, SyncJournalFileRecord *rec)
|
||||
bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std::function<void(const SyncJournalFileRecord &)> &rowCallback)
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
|
||||
// Reset the output var in case the caller is reusing it.
|
||||
Q_ASSERT(rec);
|
||||
rec->_path.clear();
|
||||
Q_ASSERT(!rec->isValid());
|
||||
|
||||
if (fileId.isEmpty() || _metadataTableIsEmpty)
|
||||
return true; // no error, yet nothing found (rec->isValid() == false)
|
||||
|
||||
|
@ -1116,8 +1127,10 @@ bool SyncJournalDb::getFileRecordByFileId(const QByteArray &fileId, SyncJournalF
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_getFileRecordQueryByFileId->next()) {
|
||||
fillFileRecordFromGetQuery(*rec, *_getFileRecordQueryByFileId);
|
||||
while (_getFileRecordQueryByFileId->next()) {
|
||||
SyncJournalFileRecord rec;
|
||||
fillFileRecordFromGetQuery(rec, *_getFileRecordQueryByFileId);
|
||||
rowCallback(rec);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1133,16 +1146,23 @@ bool SyncJournalDb::getFilesBelowPath(const QByteArray &path, const std::functio
|
|||
if (!checkConnect())
|
||||
return false;
|
||||
|
||||
_getFilesBelowPathQuery->reset_and_clear_bindings();
|
||||
_getFilesBelowPathQuery->bindValue(1, path);
|
||||
// Since the path column doesn't store the starting /, the getFilesBelowPathQuery
|
||||
// can't be used for the root path "". It would scan for (path > '/' and path < '0')
|
||||
// and find nothing. So, unfortunately, we have to use a different query for
|
||||
// retrieving the whole tree.
|
||||
auto &query = path.isEmpty() ? _getAllFilesQuery : _getFilesBelowPathQuery;
|
||||
|
||||
if (!_getFilesBelowPathQuery->exec()) {
|
||||
query->reset_and_clear_bindings();
|
||||
if (query == _getFilesBelowPathQuery)
|
||||
query->bindValue(1, path);
|
||||
|
||||
if (!query->exec()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (_getFilesBelowPathQuery->next()) {
|
||||
while (query->next()) {
|
||||
SyncJournalFileRecord rec;
|
||||
fillFileRecordFromGetQuery(rec, *_getFilesBelowPathQuery);
|
||||
fillFileRecordFromGetQuery(rec, *query);
|
||||
rowCallback(rec);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); }
|
||||
bool getFileRecord(const QByteArray &filename, SyncJournalFileRecord *rec);
|
||||
bool getFileRecordByInode(quint64 inode, SyncJournalFileRecord *rec);
|
||||
bool getFileRecordByFileId(const QByteArray &fileId, SyncJournalFileRecord *rec);
|
||||
bool getFileRecordsByFileId(const QByteArray &fileId, const std::function<void(const SyncJournalFileRecord &)> &rowCallback);
|
||||
bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
|
||||
bool setFileRecord(const SyncJournalFileRecord &record);
|
||||
|
||||
|
@ -244,6 +244,7 @@ private:
|
|||
QScopedPointer<SqlQuery> _getFileRecordQueryByInode;
|
||||
QScopedPointer<SqlQuery> _getFileRecordQueryByFileId;
|
||||
QScopedPointer<SqlQuery> _getFilesBelowPathQuery;
|
||||
QScopedPointer<SqlQuery> _getAllFilesQuery;
|
||||
QScopedPointer<SqlQuery> _setFileRecordQuery;
|
||||
QScopedPointer<SqlQuery> _setFileRecordChecksumQuery;
|
||||
QScopedPointer<SqlQuery> _setFileRecordLocalMetadataQuery;
|
||||
|
|
|
@ -579,7 +579,7 @@ bool Utility::isConflictFile(const char *name)
|
|||
|
||||
bool Utility::shouldUploadConflictFiles()
|
||||
{
|
||||
static bool uploadConflictFiles = qgetenv("OWNCLOUD_UPLOAD_CONFLICT_FILES").toInt() != 0;
|
||||
static bool uploadConflictFiles = qEnvironmentVariableIntValue("OWNCLOUD_UPLOAD_CONFLICT_FILES") != 0;
|
||||
return uploadConflictFiles;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,8 +28,13 @@
|
|||
#include <QLoggingCategory>
|
||||
#include <QMap>
|
||||
#include <QUrl>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class QSettings;
|
||||
|
||||
namespace OCC {
|
||||
|
@ -187,6 +192,14 @@ namespace Utility {
|
|||
* Experimental! Real feature planned for 2.5.
|
||||
*/
|
||||
OCSYNC_EXPORT bool shouldUploadConflictFiles();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
|
||||
OCSYNC_EXPORT bool registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value);
|
||||
OCSYNC_EXPORT bool registryDeleteKeyTree(HKEY hRootKey, const QString &subKey);
|
||||
OCSYNC_EXPORT bool registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
|
||||
OCSYNC_EXPORT bool registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback);
|
||||
#endif
|
||||
}
|
||||
/** @} */ // \addtogroup
|
||||
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#define WINVER 0x0600
|
||||
#include "asserts.h"
|
||||
#include <shlobj.h>
|
||||
#include <winbase.h>
|
||||
#include <windows.h>
|
||||
|
@ -93,4 +92,168 @@ static inline bool hasDarkSystray_private()
|
|||
return true;
|
||||
}
|
||||
|
||||
QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
|
||||
{
|
||||
QVariant value;
|
||||
|
||||
HKEY hKey;
|
||||
|
||||
REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
|
||||
LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
|
||||
ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return value;
|
||||
|
||||
DWORD type = 0, sizeInBytes = 0;
|
||||
result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, nullptr, &sizeInBytes);
|
||||
ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
|
||||
if (result == ERROR_SUCCESS) {
|
||||
switch (type) {
|
||||
case REG_DWORD:
|
||||
DWORD dword;
|
||||
Q_ASSERT(sizeInBytes == sizeof(dword));
|
||||
if (RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, reinterpret_cast<LPBYTE>(&dword), &sizeInBytes) == ERROR_SUCCESS) {
|
||||
value = int(dword);
|
||||
}
|
||||
break;
|
||||
case REG_EXPAND_SZ:
|
||||
case REG_SZ: {
|
||||
QString string;
|
||||
string.resize(sizeInBytes / sizeof(QChar));
|
||||
result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, reinterpret_cast<LPBYTE>(string.data()), &sizeInBytes);
|
||||
|
||||
if (result == ERROR_SUCCESS) {
|
||||
int newCharSize = sizeInBytes / sizeof(QChar);
|
||||
// From the doc:
|
||||
// If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with
|
||||
// the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS,
|
||||
// the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer.
|
||||
if (string.at(newCharSize - 1) == QChar('\0'))
|
||||
string.resize(newCharSize - 1);
|
||||
value = string;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
ASSERT(result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool Utility::registrySetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName, DWORD type, const QVariant &value)
|
||||
{
|
||||
HKEY hKey;
|
||||
// KEY_WOW64_64KEY is necessary because CLSIDs are "Redirected and reflected only for CLSIDs that do not specify InprocServer32 or InprocHandler32."
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253%28v=vs.85%29.aspx#redirected__shared__and_reflected_keys_under_wow64
|
||||
// This shouldn't be an issue in our case since we use shell32.dll as InprocServer32, so we could write those registry keys for both 32 and 64bit.
|
||||
// FIXME: Not doing so at the moment means that explorer will show the cloud provider, but 32bit processes' open dialogs (like the ownCloud client itself) won't show it.
|
||||
REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
|
||||
LONG result = RegCreateKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, nullptr, 0, sam, nullptr, &hKey, nullptr);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
result = -1;
|
||||
switch (type) {
|
||||
case REG_DWORD: {
|
||||
DWORD dword = value.toInt();
|
||||
result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, type, reinterpret_cast<const BYTE *>(&dword), sizeof(dword));
|
||||
break;
|
||||
}
|
||||
case REG_EXPAND_SZ:
|
||||
case REG_SZ: {
|
||||
QString string = value.toString();
|
||||
result = RegSetValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, type, reinterpret_cast<const BYTE *>(string.constData()), (string.size() + 1) * sizeof(QChar));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool Utility::registryDeleteKeyTree(HKEY hRootKey, const QString &subKey)
|
||||
{
|
||||
HKEY hKey;
|
||||
REGSAM sam = DELETE | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_WOW64_64KEY;
|
||||
LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
result = RegDeleteTree(hKey, nullptr);
|
||||
RegCloseKey(hKey);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
|
||||
result |= RegDeleteKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), sam, 0);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool Utility::registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
|
||||
{
|
||||
HKEY hKey;
|
||||
REGSAM sam = KEY_WRITE | KEY_WOW64_64KEY;
|
||||
LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
result = RegDeleteValue(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()));
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return result == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
bool Utility::registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback)
|
||||
{
|
||||
HKEY hKey;
|
||||
REGSAM sam = KEY_READ | KEY_WOW64_64KEY;
|
||||
LONG result = RegOpenKeyEx(hRootKey, reinterpret_cast<LPCWSTR>(subKey.utf16()), 0, sam, &hKey);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
if (result != ERROR_SUCCESS)
|
||||
return false;
|
||||
|
||||
DWORD maxSubKeyNameSize;
|
||||
// Get the largest keyname size once instead of relying each call on ERROR_MORE_DATA.
|
||||
result = RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, nullptr, &maxSubKeyNameSize, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
ASSERT(result == ERROR_SUCCESS);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
RegCloseKey(hKey);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString subKeyName;
|
||||
subKeyName.reserve(maxSubKeyNameSize + 1);
|
||||
|
||||
DWORD retCode = ERROR_SUCCESS;
|
||||
for (DWORD i = 0; retCode == ERROR_SUCCESS; ++i) {
|
||||
Q_ASSERT(unsigned(subKeyName.capacity()) > maxSubKeyNameSize);
|
||||
// Make the previously reserved capacity official again.
|
||||
subKeyName.resize(subKeyName.capacity());
|
||||
DWORD subKeyNameSize = subKeyName.size();
|
||||
retCode = RegEnumKeyEx(hKey, i, reinterpret_cast<LPWSTR>(subKeyName.data()), &subKeyNameSize, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
ASSERT(result == ERROR_SUCCESS || retCode == ERROR_NO_MORE_ITEMS);
|
||||
if (retCode == ERROR_SUCCESS) {
|
||||
// subKeyNameSize excludes the trailing \0
|
||||
subKeyName.resize(subKeyNameSize);
|
||||
// Pass only the sub keyname, not the full path.
|
||||
callback(hKey, subKeyName);
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(hKey);
|
||||
return retCode != ERROR_NO_MORE_ITEMS;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -314,6 +314,9 @@ int csync_s::reinitialize() {
|
|||
local.files.clear();
|
||||
remote.files.clear();
|
||||
|
||||
local_discovery_style = LocalDiscoveryStyle::FilesystemOnly;
|
||||
locally_touched_dirs.clear();
|
||||
|
||||
status = CSYNC_STATUS_INIT;
|
||||
SAFE_FREE(error_string);
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <sqlite3.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "common/syncjournaldb.h"
|
||||
#include "config_csync.h"
|
||||
|
@ -70,6 +71,11 @@ enum csync_replica_e {
|
|||
REMOTE_REPLICA
|
||||
};
|
||||
|
||||
enum class LocalDiscoveryStyle {
|
||||
FilesystemOnly, //< read all local data from the filesystem
|
||||
DatabaseAndFilesystem, //< read from the db, except for listed paths
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* This is a structurere similar to QStringRef
|
||||
|
@ -190,6 +196,16 @@ struct OCSYNC_EXPORT csync_s {
|
|||
*/
|
||||
bool read_remote_from_db = false;
|
||||
|
||||
LocalDiscoveryStyle local_discovery_style = LocalDiscoveryStyle::FilesystemOnly;
|
||||
|
||||
/**
|
||||
* List of folder-relative directory paths that should be scanned on the
|
||||
* filesystem if the local_discovery_style suggests it.
|
||||
*
|
||||
* Their parents will be scanned too. The paths don't start with a /.
|
||||
*/
|
||||
std::set<QByteArray> locally_touched_dirs;
|
||||
|
||||
bool ignore_hidden_files = true;
|
||||
|
||||
csync_s(const char *localUri, OCC::SyncJournalDb *statedb);
|
||||
|
|
|
@ -63,18 +63,6 @@ static csync_file_stat_t *_csync_check_ignored(csync_s::FileMap *tree, const Byt
|
|||
}
|
||||
}
|
||||
|
||||
/* Returns true if we're reasonably certain that hash equality
|
||||
* for the header means content equality.
|
||||
*
|
||||
* Cryptographic safety is not required - this is mainly
|
||||
* intended to rule out checksums like Adler32 that are not intended for
|
||||
* hashing and have high likelihood of collision with particular inputs.
|
||||
*/
|
||||
static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
||||
{
|
||||
return strncmp(checksum_header, "SHA1:", 5) == 0
|
||||
|| strncmp(checksum_header, "MD5:", 4) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function in the reconcile pass.
|
||||
|
@ -104,14 +92,17 @@ static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
|||
* source and the destination, have been changed, the newer file wins.
|
||||
*/
|
||||
static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
csync_s::FileMap *our_tree = nullptr;
|
||||
csync_s::FileMap *other_tree = nullptr;
|
||||
|
||||
/* we need the opposite tree! */
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
our_tree = &ctx->local.files;
|
||||
other_tree = &ctx->remote.files;
|
||||
break;
|
||||
case REMOTE_REPLICA:
|
||||
our_tree = &ctx->remote.files;
|
||||
other_tree = &ctx->local.files;
|
||||
break;
|
||||
default:
|
||||
|
@ -152,40 +143,51 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
cur->instruction = CSYNC_INSTRUCTION_REMOVE;
|
||||
break;
|
||||
case CSYNC_INSTRUCTION_EVAL_RENAME: {
|
||||
OCC::SyncJournalFileRecord base;
|
||||
if(ctx->current == LOCAL_REPLICA ) {
|
||||
/* use the old name to find the "other" node */
|
||||
ctx->statedb->getFileRecordByInode(cur->inode, &base);
|
||||
qCDebug(lcReconcile, "Finding opposite temp through inode %" PRIu64 ": %s",
|
||||
cur->inode, base.isValid() ? "true":"false");
|
||||
} else {
|
||||
ASSERT( ctx->current == REMOTE_REPLICA );
|
||||
ctx->statedb->getFileRecordByFileId(cur->file_id, &base);
|
||||
qCDebug(lcReconcile, "Finding opposite temp through file ID %s: %s",
|
||||
cur->file_id.constData(), base.isValid() ? "true":"false");
|
||||
}
|
||||
// By default, the EVAL_RENAME decays into a NEW
|
||||
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
|
||||
bool processedRename = false;
|
||||
auto renameCandidateProcessing = [&](const OCC::SyncJournalFileRecord &base) {
|
||||
if (processedRename)
|
||||
return;
|
||||
if (!base.isValid())
|
||||
return;
|
||||
|
||||
if( base.isValid() ) {
|
||||
/* First, check that the file is NOT in our tree (another file with the same name was added) */
|
||||
csync_s::FileMap *our_tree = ctx->current == REMOTE_REPLICA ? &ctx->remote.files : &ctx->local.files;
|
||||
if (our_tree->findFile(base._path)) {
|
||||
qCDebug(lcReconcile, "Origin found in our tree : %s", base._path.constData());
|
||||
if (our_tree->findFile(base._path)) {
|
||||
qCDebug(lcReconcile, "Origin found in our tree : %s", base._path.constData());
|
||||
} else {
|
||||
/* Find the temporar file in the other tree.
|
||||
/* Find the potential rename source file in the other tree.
|
||||
* If the renamed file could not be found in the opposite tree, that is because it
|
||||
* is not longer existing there, maybe because it was renamed or deleted.
|
||||
* The journal is cleaned up later after propagation.
|
||||
*/
|
||||
other = other_tree->findFile(base._path);
|
||||
qCDebug(lcReconcile, "Temporary opposite (%s) %s",
|
||||
base._path.constData() , other ? "found": "not found" );
|
||||
qCDebug(lcReconcile, "Rename origin in other tree (%s) %s",
|
||||
base._path.constData(), other ? "found" : "not found");
|
||||
}
|
||||
|
||||
if(!other) {
|
||||
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
} else if (other->instruction == CSYNC_INSTRUCTION_NONE
|
||||
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|
||||
|| cur->type == CSYNC_FTW_TYPE_DIR) {
|
||||
// Stick with the NEW
|
||||
return;
|
||||
} else if (other->instruction == CSYNC_INSTRUCTION_RENAME) {
|
||||
// Some other EVAL_RENAME already claimed other.
|
||||
// We do nothing: maybe a different candidate for
|
||||
// other is found as well?
|
||||
qCDebug(lcReconcile, "Other has already been renamed to %s",
|
||||
other->rename_path.constData());
|
||||
} else if (cur->type == CSYNC_FTW_TYPE_DIR
|
||||
// The local replica is reconciled first, so the remote tree would
|
||||
// have either NONE or UPDATE_METADATA if the remote file is safe to
|
||||
// move.
|
||||
// In the remote replica, REMOVE is also valid (local has already
|
||||
// been reconciled). NONE can still happen if the whole parent dir
|
||||
// was set to REMOVE by the local reconcile.
|
||||
|| other->instruction == CSYNC_INSTRUCTION_NONE
|
||||
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|
||||
|| other->instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||
qCDebug(lcReconcile, "Switching %s to RENAME to %s",
|
||||
other->path.constData(), cur->path.constData());
|
||||
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
||||
other->rename_path = cur->path;
|
||||
if( !cur->file_id.isEmpty() ) {
|
||||
|
@ -193,24 +195,44 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
}
|
||||
other->inode = cur->inode;
|
||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
} else if (other->instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
||||
other->rename_path = cur->path;
|
||||
// We have consumed 'other': exit this loop to not consume another one.
|
||||
processedRename = true;
|
||||
} else if (our_tree->findFile(csync_rename_adjust_path(ctx, other->path)) == cur) {
|
||||
// If we're here, that means that the other side's reconcile will be able
|
||||
// to work against cur: The filename itself didn't change, only a parent
|
||||
// directory was renamed! In that case it's safe to ignore the rename
|
||||
// since the parent directory rename will already deal with it.
|
||||
|
||||
if( !cur->file_id.isEmpty() ) {
|
||||
other->file_id = cur->file_id;
|
||||
}
|
||||
other->inode = cur->inode;
|
||||
// Local: The remote reconcile will be able to deal with this.
|
||||
// Remote: The local replica has already dealt with this.
|
||||
// See the EVAL_RENAME case when other was found directly.
|
||||
qCDebug(lcReconcile, "File in a renamed directory, other side's instruction: %d",
|
||||
other->instruction);
|
||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
} else if (other->instruction == CSYNC_INSTRUCTION_NEW) {
|
||||
qCDebug(lcReconcile, "OOOO=> NEW detected in other tree!");
|
||||
cur->instruction = CSYNC_INSTRUCTION_CONFLICT;
|
||||
} else {
|
||||
assert(other->type != CSYNC_FTW_TYPE_DIR);
|
||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
other->instruction = CSYNC_INSTRUCTION_SYNC;
|
||||
// This can, for instance, happen when there was a local change in other
|
||||
// and the instruction in the local tree is NEW while cur has EVAL_RENAME
|
||||
// due to a remote move of the same file. In these scenarios we just
|
||||
// want the instruction to stay NEW.
|
||||
qCDebug(lcReconcile, "Other already has instruction %d",
|
||||
other->instruction);
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx->current == LOCAL_REPLICA) {
|
||||
/* use the old name to find the "other" node */
|
||||
OCC::SyncJournalFileRecord base;
|
||||
qCDebug(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
|
||||
cur->inode);
|
||||
ctx->statedb->getFileRecordByInode(cur->inode, &base);
|
||||
renameCandidateProcessing(base);
|
||||
} else {
|
||||
ASSERT(ctx->current == REMOTE_REPLICA);
|
||||
qCDebug(lcReconcile, "Finding rename origin through file ID %s",
|
||||
cur->file_id.constData());
|
||||
ctx->statedb->getFileRecordsByFileId(cur->file_id, renameCandidateProcessing);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -263,14 +285,14 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
//
|
||||
// In older client versions we always treated these cases as a
|
||||
// non-conflict. This behavior is preserved in case the server
|
||||
// doesn't provide a suitable content hash.
|
||||
// doesn't provide a content checksum.
|
||||
//
|
||||
// When it does have one, however, we do create a job, but the job
|
||||
// will compare hashes and avoid the download if they are equal.
|
||||
const char *remoteChecksumHeader =
|
||||
// will compare hashes and avoid the download if possible.
|
||||
QByteArray remoteChecksumHeader =
|
||||
(ctx->current == REMOTE_REPLICA ? cur->checksumHeader : other->checksumHeader);
|
||||
if (remoteChecksumHeader) {
|
||||
is_conflict |= _csync_is_collision_safe_hash(remoteChecksumHeader);
|
||||
if (!remoteChecksumHeader.isEmpty()) {
|
||||
is_conflict = true;
|
||||
}
|
||||
|
||||
// SO: If there is no checksum, we can have !is_conflict here
|
||||
|
@ -310,10 +332,19 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
break;
|
||||
case CSYNC_INSTRUCTION_IGNORE:
|
||||
cur->instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Ensure we're not leaving discovery-only instructions
|
||||
// in place. This can happen, for instance, when other's
|
||||
// instruction is EVAL_RENAME because the parent dir was renamed.
|
||||
// NEW is safer than EVAL because it will end up with
|
||||
// propagation unless it's changed by something, and EVAL and
|
||||
// NEW are treated equivalently during reconcile.
|
||||
if (cur->instruction == CSYNC_INSTRUCTION_EVAL)
|
||||
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -297,41 +297,60 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
|||
|
||||
} else {
|
||||
/* Remote Replica Rename check */
|
||||
OCC::SyncJournalFileRecord base;
|
||||
if(!ctx->statedb->getFileRecordByFileId(fs->file_id, &base)) {
|
||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
|
||||
bool done = false;
|
||||
auto renameCandidateProcessing = [&](const OCC::SyncJournalFileRecord &base) {
|
||||
if (done)
|
||||
return;
|
||||
if (!base.isValid())
|
||||
return;
|
||||
|
||||
// Some things prohibit rename detection entirely.
|
||||
// Since we don't do the same checks again in reconcile, we can't
|
||||
// just skip the candidate, but have to give up completely.
|
||||
if (base._type != fs->type) {
|
||||
qCWarning(lcUpdate, "file types different, not a rename");
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
if (fs->type != CSYNC_FTW_TYPE_DIR && base._etag != fs->etag) {
|
||||
/* File with different etag, don't do a rename, but download the file again */
|
||||
qCWarning(lcUpdate, "file etag different, not a rename");
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Record directory renames
|
||||
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
||||
// If the same folder was already renamed by a different entry,
|
||||
// skip to the next candidate
|
||||
if (ctx->renames.folder_renamed_to.count(base._path) > 0) {
|
||||
qCWarning(lcUpdate, "folder already has a rename entry, skipping");
|
||||
return;
|
||||
}
|
||||
csync_rename_record(ctx, base._path, fs->path);
|
||||
}
|
||||
|
||||
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||
done = true;
|
||||
};
|
||||
|
||||
if (!ctx->statedb->getFileRecordsByFileId(fs->file_id, renameCandidateProcessing)) {
|
||||
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
|
||||
return -1;
|
||||
}
|
||||
if (base.isValid()) { /* tmp existing at all */
|
||||
if (base._type != fs->type) {
|
||||
qCWarning(lcUpdate, "file types different is not!");
|
||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
goto out;
|
||||
}
|
||||
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
||||
csync_rename_record(ctx, base._path, fs->path);
|
||||
} else {
|
||||
if( base._etag != fs->etag ) {
|
||||
/* CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "ETags are different!"); */
|
||||
/* File with different etag, don't do a rename, but download the file again */
|
||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
}
|
||||
}
|
||||
goto out;
|
||||
|
||||
} else {
|
||||
/* file not found in statedb */
|
||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
|
||||
if (fs->type == CSYNC_FTW_TYPE_DIR && ctx->current == REMOTE_REPLICA && ctx->callbacks.checkSelectiveSyncNewFolderHook) {
|
||||
if (ctx->callbacks.checkSelectiveSyncNewFolderHook(ctx->callbacks.update_callback_userdata, fs->path, fs->remotePerm)) {
|
||||
return 1;
|
||||
}
|
||||
if (fs->instruction == CSYNC_INSTRUCTION_NEW
|
||||
&& fs->type == CSYNC_FTW_TYPE_DIR
|
||||
&& ctx->current == REMOTE_REPLICA
|
||||
&& ctx->callbacks.checkSelectiveSyncNewFolderHook) {
|
||||
if (ctx->callbacks.checkSelectiveSyncNewFolderHook(ctx->callbacks.update_callback_userdata, fs->path, fs->remotePerm)) {
|
||||
return 1;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,28 +454,31 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
|||
{
|
||||
int64_t count = 0;
|
||||
QByteArray skipbase;
|
||||
auto rowCallback = [ctx, &count, &skipbase](const OCC::SyncJournalFileRecord &rec) {
|
||||
/* When selective sync is used, the database may have subtrees with a parent
|
||||
* whose etag (md5) is _invalid_. These are ignored and shall not appear in the
|
||||
* remote tree.
|
||||
* Sometimes folders that are not ignored by selective sync get marked as
|
||||
* _invalid_, but that is not a problem as the next discovery will retrieve
|
||||
* their correct etags again and we don't run into this case.
|
||||
*/
|
||||
if( rec._etag == "_invalid_") {
|
||||
qCDebug(lcUpdate, "%s selective sync excluded", rec._path.constData());
|
||||
skipbase = rec._path;
|
||||
skipbase += '/';
|
||||
return;
|
||||
}
|
||||
auto &files = ctx->current == LOCAL_REPLICA ? ctx->local.files : ctx->remote.files;
|
||||
auto rowCallback = [ctx, &count, &skipbase, &files](const OCC::SyncJournalFileRecord &rec) {
|
||||
if (ctx->current == REMOTE_REPLICA) {
|
||||
/* When selective sync is used, the database may have subtrees with a parent
|
||||
* whose etag is _invalid_. These are ignored and shall not appear in the
|
||||
* remote tree.
|
||||
* Sometimes folders that are not ignored by selective sync get marked as
|
||||
* _invalid_, but that is not a problem as the next discovery will retrieve
|
||||
* their correct etags again and we don't run into this case.
|
||||
*/
|
||||
if (rec._etag == "_invalid_") {
|
||||
qCDebug(lcUpdate, "%s selective sync excluded", rec._path.constData());
|
||||
skipbase = rec._path;
|
||||
skipbase += '/';
|
||||
return;
|
||||
}
|
||||
|
||||
/* Skip over all entries with the same base path. Note that this depends
|
||||
* strongly on the ordering of the retrieved items. */
|
||||
if( !skipbase.isEmpty() && rec._path.startsWith(skipbase) ) {
|
||||
qCDebug(lcUpdate, "%s selective sync excluded because the parent is", rec._path.constData());
|
||||
return;
|
||||
} else {
|
||||
skipbase.clear();
|
||||
/* Skip over all entries with the same base path. Note that this depends
|
||||
* strongly on the ordering of the retrieved items. */
|
||||
if (!skipbase.isEmpty() && rec._path.startsWith(skipbase)) {
|
||||
qCDebug(lcUpdate, "%s selective sync excluded because the parent is", rec._path.constData());
|
||||
return;
|
||||
} else {
|
||||
skipbase.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<csync_file_stat_t> st = csync_file_stat_t::fromSyncJournalFileRecord(rec);
|
||||
|
@ -477,7 +499,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
|||
}
|
||||
|
||||
/* store into result list. */
|
||||
ctx->remote.files[rec._path] = std::move(st);
|
||||
files[rec._path] = std::move(st);
|
||||
++count;
|
||||
};
|
||||
|
||||
|
@ -522,6 +544,26 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||
int rc = 0;
|
||||
|
||||
bool do_read_from_db = (ctx->current == REMOTE_REPLICA && ctx->remote.read_from_db);
|
||||
const char *db_uri = uri;
|
||||
|
||||
if (ctx->current == LOCAL_REPLICA
|
||||
&& ctx->local_discovery_style == LocalDiscoveryStyle::DatabaseAndFilesystem) {
|
||||
const char *local_uri = uri + strlen(ctx->local.uri);
|
||||
if (*local_uri == '/')
|
||||
++local_uri;
|
||||
db_uri = local_uri;
|
||||
do_read_from_db = true;
|
||||
|
||||
// Minor bug: local_uri doesn't have a trailing /. Example: Assume it's "d/foo"
|
||||
// and we want to check whether we should read from the db. Assume "d/foo a" is
|
||||
// in locally_touched_dirs. Then this check will say no, don't read from the db!
|
||||
// (because "d/foo" < "d/foo a" < "d/foo/bar")
|
||||
// C++14: Could skip the conversion to QByteArray here.
|
||||
auto it = ctx->locally_touched_dirs.lower_bound(QByteArray(local_uri));
|
||||
if (it != ctx->locally_touched_dirs.end() && it->startsWith(local_uri)) {
|
||||
do_read_from_db = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!depth) {
|
||||
mark_current_item_ignored(ctx, previous_fs, CSYNC_STATUS_INDIVIDUAL_TOO_DEEP);
|
||||
|
@ -533,7 +575,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||
// if the etag of this dir is still the same, its content is restored from the
|
||||
// database.
|
||||
if( do_read_from_db ) {
|
||||
if( ! fill_tree_from_db(ctx, uri) ) {
|
||||
if( ! fill_tree_from_db(ctx, db_uri) ) {
|
||||
errno = ENOENT;
|
||||
ctx->status_code = CSYNC_STATUS_OPENDIR_ERROR;
|
||||
goto error;
|
||||
|
@ -616,7 +658,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||
* local stat function.
|
||||
*/
|
||||
if( filename[0] == '.' ) {
|
||||
if (filename == ".sys.admin#recall#") { /* recall file shall not be ignored (#4420) */
|
||||
if (filename != ".sys.admin#recall#") { /* recall file shall not be ignored (#4420) */
|
||||
dirent->is_hidden = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -211,3 +211,10 @@ time_t oc_httpdate_parse( const char *date ) {
|
|||
result = timegm(&gmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool csync_is_collision_safe_hash(const QByteArray &checksum_header)
|
||||
{
|
||||
return checksum_header.startsWith("SHA1:")
|
||||
|| checksum_header.startsWith("MD5:");
|
||||
}
|
||||
|
|
|
@ -31,4 +31,14 @@ const char OCSYNC_EXPORT *csync_instruction_str(enum csync_instructions_e instr)
|
|||
void OCSYNC_EXPORT csync_memstat_check(void);
|
||||
|
||||
bool OCSYNC_EXPORT csync_file_locked_or_open( const char *dir, const char *fname);
|
||||
|
||||
/* Returns true if we're reasonably certain that hash equality
|
||||
* for the header means content equality.
|
||||
*
|
||||
* Cryptographic safety is not required - this is mainly
|
||||
* intended to rule out checksums like Adler32 that are not intended for
|
||||
* hashing and have high likelihood of collision with particular inputs.
|
||||
*/
|
||||
bool OCSYNC_EXPORT csync_is_collision_safe_hash(const QByteArray &checksum_header);
|
||||
|
||||
#endif /* _CSYNC_UTIL_H */
|
||||
|
|
|
@ -57,6 +57,7 @@ set(client_SRCS
|
|||
ignorelisteditor.cpp
|
||||
lockwatcher.cpp
|
||||
logbrowser.cpp
|
||||
navigationpanehelper.cpp
|
||||
networksettings.cpp
|
||||
ocsjob.cpp
|
||||
ocssharejob.cpp
|
||||
|
|
|
@ -401,6 +401,9 @@ void AccountSettings::slotFolderWizardAccepted()
|
|||
*/
|
||||
definition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
|
||||
|
||||
if (folderMan->navigationPaneHelper().showInExplorerNavigationPane())
|
||||
definition.navigationPaneClsid = QUuid::createUuid();
|
||||
|
||||
auto selectiveSyncBlackList = folderWizard->property("selectiveSyncBlackList").toStringList();
|
||||
|
||||
folderMan->setSyncEnabled(true);
|
||||
|
|
|
@ -30,19 +30,21 @@ using namespace QKeychain;
|
|||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "sync.credentials.http.gui", QtInfoMsg)
|
||||
|
||||
void HttpCredentialsGui::askFromUser()
|
||||
{
|
||||
// Unfortunately there's a bug that doesn't allow us to send the "is this
|
||||
// OAuth2 or Basic auth?" GET request directly. Scheduling it for the event
|
||||
// loop works though. See #5989.
|
||||
QMetaObject::invokeMethod(this, "askFromUserAsync", Qt::QueuedConnection);
|
||||
// This function can be called from AccountState::slotInvalidCredentials,
|
||||
// which (indirectly, through HttpCredentials::invalidateToken) schedules
|
||||
// a cache wipe of the qnam. We can only execute a network job again once
|
||||
// the cache has been cleared, otherwise we'd interfere with the job.
|
||||
QTimer::singleShot(100, this, &HttpCredentialsGui::askFromUserAsync);
|
||||
}
|
||||
|
||||
void HttpCredentialsGui::askFromUserAsync()
|
||||
{
|
||||
// First, we will check what kind of auth we need.
|
||||
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||
job->setTimeout(30 * 1000);
|
||||
QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
|
||||
if (type == DetermineAuthTypeJob::OAuth) {
|
||||
_asyncAuth.reset(new OAuth(_account, this));
|
||||
|
@ -58,7 +60,8 @@ void HttpCredentialsGui::askFromUserAsync()
|
|||
// We will re-enter the event loop, so better wait the next iteration
|
||||
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
|
||||
} else {
|
||||
// Network error? Unsupported auth type?
|
||||
// Shibboleth?
|
||||
qCWarning(lcHttpCredentialsGui) << "Bad http auth type:" << type;
|
||||
emit asked();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -60,12 +60,12 @@ public:
|
|||
private slots:
|
||||
void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken);
|
||||
void showDialog();
|
||||
void askFromUserAsync();
|
||||
|
||||
signals:
|
||||
void authorisationLinkChanged();
|
||||
|
||||
private:
|
||||
Q_INVOKABLE void askFromUserAsync();
|
||||
|
||||
QScopedPointer<OAuth, QScopedPointerObjectDeleteLater<OAuth>> _asyncAuth;
|
||||
};
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
UserAgentWebPage(QObject *parent)
|
||||
: QWebPage(parent)
|
||||
{
|
||||
if (!qgetenv("OWNCLOUD_SHIBBOLETH_DEBUG").isEmpty()) {
|
||||
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_SHIBBOLETH_DEBUG")) {
|
||||
settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ ShibbolethWebView::ShibbolethWebView(AccountPtr account, QWidget *parent)
|
|||
setWindowTitle(tr("%1 - Authenticate").arg(Theme::instance()->appNameGUI()));
|
||||
|
||||
// Debug view to display the cipher suite
|
||||
if (!qgetenv("OWNCLOUD_SHIBBOLETH_DEBUG").isEmpty()) {
|
||||
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_SHIBBOLETH_DEBUG")) {
|
||||
// open an additional window to display some cipher debug info
|
||||
QWebPage *debugPage = new UserAgentWebPage(this);
|
||||
debugPage->mainFrame()->load(QUrl("https://cc.dcsec.uni-hannover.de/"));
|
||||
|
|
|
@ -448,12 +448,26 @@ int Folder::slotWipeErrorBlacklist()
|
|||
|
||||
void Folder::slotWatchedPathChanged(const QString &path)
|
||||
{
|
||||
if (!path.startsWith(this->path())) {
|
||||
qCDebug(lcFolder) << "Changed path is not contained in folder, ignoring:" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
auto relativePath = path.midRef(this->path().size());
|
||||
|
||||
// Add to list of locally modified paths
|
||||
//
|
||||
// We do this before checking for our own sync-related changes to make
|
||||
// extra sure to not miss relevant changes.
|
||||
auto relativePathBytes = relativePath.toUtf8();
|
||||
_localDiscoveryPaths.insert(relativePathBytes);
|
||||
qCDebug(lcFolder) << "local discovery: inserted" << relativePath << "due to file watcher";
|
||||
|
||||
// The folder watcher fires a lot of bogus notifications during
|
||||
// a sync operation, both for actual user files and the database
|
||||
// and log. Therefore we check notifications against operations
|
||||
// the sync is doing to filter out our own changes.
|
||||
#ifdef Q_OS_MAC
|
||||
Q_UNUSED(path)
|
||||
// On OSX the folder watcher does not report changes done by our
|
||||
// own process. Therefore nothing needs to be done here!
|
||||
#else
|
||||
|
@ -465,15 +479,12 @@ void Folder::slotWatchedPathChanged(const QString &path)
|
|||
#endif
|
||||
|
||||
// Check that the mtime actually changed.
|
||||
if (path.startsWith(this->path())) {
|
||||
auto relativePath = path.mid(this->path().size());
|
||||
SyncJournalFileRecord record;
|
||||
if (_journal.getFileRecord(relativePath, &record)
|
||||
&& record.isValid()
|
||||
&& !FileSystem::fileChanged(path, record._fileSize, record._modtime)) {
|
||||
qCInfo(lcFolder) << "Ignoring spurious notification for file" << relativePath;
|
||||
return; // probably a spurious notification
|
||||
}
|
||||
SyncJournalFileRecord record;
|
||||
if (_journal.getFileRecord(relativePathBytes, &record)
|
||||
&& record.isValid()
|
||||
&& !FileSystem::fileChanged(path, record._fileSize, record._modtime)) {
|
||||
qCInfo(lcFolder) << "Ignoring spurious notification for file" << relativePath;
|
||||
return; // probably a spurious notification
|
||||
}
|
||||
|
||||
emit watchedFileChangedExternally(path);
|
||||
|
@ -645,6 +656,36 @@ void Folder::startSync(const QStringList &pathList)
|
|||
setDirtyNetworkLimits();
|
||||
setSyncOptions();
|
||||
|
||||
static qint64 fullLocalDiscoveryInterval = []() {
|
||||
auto interval = ConfigFile().fullLocalDiscoveryInterval();
|
||||
QByteArray env = qgetenv("OWNCLOUD_FULL_LOCAL_DISCOVERY_INTERVAL");
|
||||
if (!env.isEmpty()) {
|
||||
interval = env.toLongLong();
|
||||
}
|
||||
return interval;
|
||||
}();
|
||||
if (_folderWatcher && _folderWatcher->isReliable()
|
||||
&& _timeSinceLastFullLocalDiscovery.isValid()
|
||||
&& (fullLocalDiscoveryInterval < 0
|
||||
|| _timeSinceLastFullLocalDiscovery.elapsed() < fullLocalDiscoveryInterval)) {
|
||||
qCInfo(lcFolder) << "Allowing local discovery to read from the database";
|
||||
_engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, _localDiscoveryPaths);
|
||||
|
||||
if (lcFolder().isDebugEnabled()) {
|
||||
QByteArrayList paths;
|
||||
for (auto &path : _localDiscoveryPaths)
|
||||
paths.append(path);
|
||||
qCDebug(lcFolder) << "local discovery paths: " << paths;
|
||||
}
|
||||
|
||||
_previousLocalDiscoveryPaths = std::move(_localDiscoveryPaths);
|
||||
} else {
|
||||
qCInfo(lcFolder) << "Forbidding local discovery to read from the database";
|
||||
_engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
|
||||
_previousLocalDiscoveryPaths.clear();
|
||||
}
|
||||
_localDiscoveryPaths.clear();
|
||||
|
||||
_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
|
||||
|
||||
QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
|
||||
|
@ -783,6 +824,24 @@ void Folder::slotSyncFinished(bool success)
|
|||
journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList, QStringList());
|
||||
}
|
||||
|
||||
// bug: This function uses many different criteria for "sync was successful" - investigate!
|
||||
if ((_syncResult.status() == SyncResult::Success
|
||||
|| _syncResult.status() == SyncResult::Problem)
|
||||
&& success) {
|
||||
if (_engine->lastLocalDiscoveryStyle() == LocalDiscoveryStyle::FilesystemOnly) {
|
||||
_timeSinceLastFullLocalDiscovery.start();
|
||||
}
|
||||
qCDebug(lcFolder) << "Sync success, forgetting last sync's local discovery path list";
|
||||
} else {
|
||||
// On overall-failure we can't forget about last sync's local discovery
|
||||
// paths yet, reuse them for the next sync again.
|
||||
// C++17: Could use std::set::merge().
|
||||
_localDiscoveryPaths.insert(
|
||||
_previousLocalDiscoveryPaths.begin(), _previousLocalDiscoveryPaths.end());
|
||||
qCDebug(lcFolder) << "Sync failed, keeping last sync's local discovery path list";
|
||||
}
|
||||
_previousLocalDiscoveryPaths.clear();
|
||||
|
||||
emit syncStateChange();
|
||||
|
||||
// The syncFinished result that is to be triggered here makes the folderman
|
||||
|
@ -841,12 +900,38 @@ void Folder::slotTransmissionProgress(const ProgressInfo &pi)
|
|||
// a item is completed: count the errors and forward to the ProgressDispatcher
|
||||
void Folder::slotItemCompleted(const SyncFileItemPtr &item)
|
||||
{
|
||||
if (item->_instruction == CSYNC_INSTRUCTION_NONE || item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {
|
||||
// We only care about the updates that deserve to be shown in the UI
|
||||
return;
|
||||
}
|
||||
|
||||
// add new directories or remove gone away dirs to the watcher
|
||||
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_NEW) {
|
||||
FolderMan::instance()->addMonitorPath(alias(), path() + item->_file);
|
||||
if (_folderWatcher)
|
||||
_folderWatcher->addPath(path() + item->_file);
|
||||
}
|
||||
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||
FolderMan::instance()->removeMonitorPath(alias(), path() + item->_file);
|
||||
if (_folderWatcher)
|
||||
_folderWatcher->removePath(path() + item->_file);
|
||||
}
|
||||
|
||||
// Success and failure of sync items adjust what the next sync is
|
||||
// supposed to do.
|
||||
//
|
||||
// For successes, we want to wipe the file from the list to ensure we don't
|
||||
// rediscover it even if this overall sync fails.
|
||||
//
|
||||
// For failures, we want to add the file to the list so the next sync
|
||||
// will be able to retry it.
|
||||
if (item->_status == SyncFileItem::Success
|
||||
|| item->_status == SyncFileItem::FileIgnored
|
||||
|| item->_status == SyncFileItem::Restoration
|
||||
|| item->_status == SyncFileItem::Conflict) {
|
||||
if (_previousLocalDiscoveryPaths.erase(item->_file.toUtf8()))
|
||||
qCDebug(lcFolder) << "local discovery: wiped" << item->_file;
|
||||
} else {
|
||||
_localDiscoveryPaths.insert(item->_file.toUtf8());
|
||||
qCDebug(lcFolder) << "local discovery: inserted" << item->_file << "due to sync failure";
|
||||
}
|
||||
|
||||
_syncResult.processCompletedItem(item);
|
||||
|
@ -901,6 +986,11 @@ void Folder::slotScheduleThisFolder()
|
|||
FolderMan::instance()->scheduleFolder(this);
|
||||
}
|
||||
|
||||
void Folder::slotNextSyncFullLocalDiscovery()
|
||||
{
|
||||
_timeSinceLastFullLocalDiscovery.invalidate();
|
||||
}
|
||||
|
||||
void Folder::scheduleThisFolderSoon()
|
||||
{
|
||||
if (!_scheduleSelfTimer.isActive()) {
|
||||
|
@ -913,6 +1003,20 @@ void Folder::setSaveBackwardsCompatible(bool save)
|
|||
_saveBackwardsCompatible = save;
|
||||
}
|
||||
|
||||
void Folder::registerFolderWatcher()
|
||||
{
|
||||
if (_folderWatcher)
|
||||
return;
|
||||
if (!QDir(path()).exists())
|
||||
return;
|
||||
|
||||
_folderWatcher.reset(new FolderWatcher(path(), this));
|
||||
connect(_folderWatcher.data(), &FolderWatcher::pathChanged,
|
||||
this, &Folder::slotWatchedPathChanged);
|
||||
connect(_folderWatcher.data(), &FolderWatcher::lostChanges,
|
||||
this, &Folder::slotNextSyncFullLocalDiscovery);
|
||||
}
|
||||
|
||||
void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, bool *cancel)
|
||||
{
|
||||
ConfigFile cfgFile;
|
||||
|
@ -976,6 +1080,12 @@ void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder)
|
|||
settings.setValue(QLatin1String("targetPath"), folder.targetPath);
|
||||
settings.setValue(QLatin1String("paused"), folder.paused);
|
||||
settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
|
||||
|
||||
// Happens only on Windows when the explorer integration is enabled.
|
||||
if (!folder.navigationPaneClsid.isNull())
|
||||
settings.setValue(QLatin1String("navigationPaneClsid"), folder.navigationPaneClsid);
|
||||
else
|
||||
settings.remove(QLatin1String("navigationPaneClsid"));
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
|
@ -989,6 +1099,7 @@ bool FolderDefinition::load(QSettings &settings, const QString &alias,
|
|||
folder->targetPath = settings.value(QLatin1String("targetPath")).toString();
|
||||
folder->paused = settings.value(QLatin1String("paused")).toBool();
|
||||
folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
|
||||
folder->navigationPaneClsid = settings.value(QLatin1String("navigationPaneClsid")).toUuid();
|
||||
settings.endGroup();
|
||||
|
||||
// Old settings can contain paths with native separators. In the rest of the
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QUuid>
|
||||
#include <set>
|
||||
|
||||
class QThread;
|
||||
class QSettings;
|
||||
|
@ -36,6 +38,7 @@ namespace OCC {
|
|||
class SyncEngine;
|
||||
class AccountState;
|
||||
class SyncRunFileLog;
|
||||
class FolderWatcher;
|
||||
|
||||
/**
|
||||
* @brief The FolderDefinition class
|
||||
|
@ -62,6 +65,8 @@ public:
|
|||
bool paused;
|
||||
/// whether the folder syncs hidden files
|
||||
bool ignoreHiddenFiles;
|
||||
/// The CLSID where this folder appears in registry for the Explorer navigation pane entry.
|
||||
QUuid navigationPaneClsid;
|
||||
|
||||
/// Saves the folder definition, creating a new settings group.
|
||||
static void save(QSettings &settings, const FolderDefinition &folder);
|
||||
|
@ -133,6 +138,9 @@ public:
|
|||
*/
|
||||
QString remotePath() const;
|
||||
|
||||
void setNavigationPaneClsid(const QUuid &clsid) { _definition.navigationPaneClsid = clsid; }
|
||||
QUuid navigationPaneClsid() const { return _definition.navigationPaneClsid; }
|
||||
|
||||
/**
|
||||
* remote folder path with server url
|
||||
*/
|
||||
|
@ -227,6 +235,13 @@ public:
|
|||
*/
|
||||
void setSaveBackwardsCompatible(bool save);
|
||||
|
||||
/**
|
||||
* Sets up this folder's folderWatcher if possible.
|
||||
*
|
||||
* May be called several times.
|
||||
*/
|
||||
void registerFolderWatcher();
|
||||
|
||||
signals:
|
||||
void syncStateChange();
|
||||
void syncStarted();
|
||||
|
@ -304,6 +319,9 @@ private slots:
|
|||
*/
|
||||
void slotScheduleThisFolder();
|
||||
|
||||
/** Ensures that the next sync performs a full local discovery. */
|
||||
void slotNextSyncFullLocalDiscovery();
|
||||
|
||||
private:
|
||||
bool setIgnoredFiles();
|
||||
|
||||
|
@ -338,6 +356,7 @@ private:
|
|||
QString _lastEtag;
|
||||
QElapsedTimer _timeSinceLastSyncDone;
|
||||
QElapsedTimer _timeSinceLastSyncStart;
|
||||
QElapsedTimer _timeSinceLastFullLocalDiscovery;
|
||||
qint64 _lastSyncDuration;
|
||||
|
||||
/// The number of syncs that failed in a row.
|
||||
|
@ -365,6 +384,29 @@ private:
|
|||
* path.
|
||||
*/
|
||||
bool _saveBackwardsCompatible;
|
||||
|
||||
/**
|
||||
* Watches this folder's local directory for changes.
|
||||
*
|
||||
* Created by registerFolderWatcher(), triggers slotWatchedPathChanged()
|
||||
*/
|
||||
QScopedPointer<FolderWatcher> _folderWatcher;
|
||||
|
||||
/**
|
||||
* The paths that should be checked by the next local discovery.
|
||||
*
|
||||
* Mostly a collection of files the filewatchers have reported as touched.
|
||||
* Also includes files that have had errors in the last sync run.
|
||||
*/
|
||||
std::set<QByteArray> _localDiscoveryPaths;
|
||||
|
||||
/**
|
||||
* The paths that the current sync run used for local discovery.
|
||||
*
|
||||
* For failing syncs, this list will be merged into _localDiscoveryPaths
|
||||
* again when the sync is done to make sure everything is retried.
|
||||
*/
|
||||
std::set<QByteArray> _previousLocalDiscoveryPaths;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ FolderMan::FolderMan(QObject *parent)
|
|||
, _currentSyncFolder(0)
|
||||
, _syncEnabled(true)
|
||||
, _lockWatcher(new LockWatcher)
|
||||
, _navigationPaneHelper(this)
|
||||
, _appRestartRequired(false)
|
||||
{
|
||||
ASSERT(!_instance);
|
||||
|
@ -104,9 +105,6 @@ void FolderMan::unloadFolder(Folder *f)
|
|||
|
||||
_socketApi->slotUnregisterPath(f->alias());
|
||||
|
||||
if (_folderWatchers.contains(f->alias())) {
|
||||
_folderWatchers.remove(f->alias());
|
||||
}
|
||||
_folderMap.remove(f->alias());
|
||||
|
||||
disconnect(f, &Folder::syncStarted,
|
||||
|
@ -147,54 +145,18 @@ int FolderMan::unloadAndDeleteAllFolders()
|
|||
return cnt;
|
||||
}
|
||||
|
||||
// add a monitor to the local file system. If there is a change in the
|
||||
// file system, the method slotFolderMonitorFired is triggered through
|
||||
// the SignalMapper
|
||||
void FolderMan::registerFolderMonitor(Folder *folder)
|
||||
void FolderMan::registerFolderWithSocketApi(Folder *folder)
|
||||
{
|
||||
if (!folder)
|
||||
return;
|
||||
if (!QDir(folder->path()).exists())
|
||||
return;
|
||||
|
||||
if (!_folderWatchers.contains(folder->alias())) {
|
||||
FolderWatcher *fw = new FolderWatcher(folder->path(), folder);
|
||||
|
||||
// Connect the pathChanged signal, which comes with the changed path,
|
||||
// to the signal mapper which maps to the folder alias. The changed path
|
||||
// is lost this way, but we do not need it for the current implementation.
|
||||
connect(fw, &FolderWatcher::pathChanged, folder, &Folder::slotWatchedPathChanged);
|
||||
|
||||
_folderWatchers.insert(folder->alias(), fw);
|
||||
}
|
||||
|
||||
// register the folder with the socket API
|
||||
if (folder->canSync())
|
||||
_socketApi->slotRegisterPath(folder->alias());
|
||||
}
|
||||
|
||||
void FolderMan::addMonitorPath(const QString &alias, const QString &path)
|
||||
{
|
||||
if (!alias.isEmpty() && _folderWatchers.contains(alias)) {
|
||||
FolderWatcher *fw = _folderWatchers[alias];
|
||||
|
||||
if (fw) {
|
||||
fw->addPath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMan::removeMonitorPath(const QString &alias, const QString &path)
|
||||
{
|
||||
if (!alias.isEmpty() && _folderWatchers.contains(alias)) {
|
||||
FolderWatcher *fw = _folderWatchers[alias];
|
||||
|
||||
if (fw) {
|
||||
fw->removePath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FolderMan::setupFolders()
|
||||
{
|
||||
unloadAndDeleteAllFolders();
|
||||
|
@ -750,7 +712,8 @@ void FolderMan::slotStartScheduledFolderSync()
|
|||
if (folder) {
|
||||
// Safe to call several times, and necessary to try again if
|
||||
// the folder path didn't exist previously.
|
||||
registerFolderMonitor(folder);
|
||||
folder->registerFolderWatcher();
|
||||
registerFolderWithSocketApi(folder);
|
||||
|
||||
_currentSyncFolder = folder;
|
||||
folder->startSync(QStringList());
|
||||
|
@ -932,6 +895,9 @@ Folder *FolderMan::addFolder(AccountState *accountState, const FolderDefinition
|
|||
emit folderSyncStateChange(folder);
|
||||
emit folderListChanged(_folderMap);
|
||||
}
|
||||
|
||||
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
@ -964,7 +930,8 @@ Folder *FolderMan::addFolderInternal(FolderDefinition folderDefinition,
|
|||
connect(folder, &Folder::watchedFileChangedExternally,
|
||||
&folder->syncEngine().syncFileStatusTracker(), &SyncFileStatusTracker::slotPathTouched);
|
||||
|
||||
registerFolderMonitor(folder);
|
||||
folder->registerFolderWatcher();
|
||||
registerFolderWithSocketApi(folder);
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
@ -1040,6 +1007,8 @@ void FolderMan::removeFolder(Folder *f)
|
|||
delete f;
|
||||
}
|
||||
|
||||
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
|
||||
|
||||
emit folderListChanged(_folderMap);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "folder.h"
|
||||
#include "folderwatcher.h"
|
||||
#include "navigationpanehelper.h"
|
||||
#include "syncfileitem.h"
|
||||
|
||||
class TestFolderMan;
|
||||
|
@ -109,15 +110,13 @@ public:
|
|||
|
||||
static SyncResult accountStatus(const QList<Folder *> &folders);
|
||||
|
||||
void removeMonitorPath(const QString &alias, const QString &path);
|
||||
void addMonitorPath(const QString &alias, const QString &path);
|
||||
|
||||
// Escaping of the alias which is used in QSettings AND the file
|
||||
// system, thus need to be escaped.
|
||||
static QString escapeAlias(const QString &);
|
||||
static QString unescapeAlias(const QString &);
|
||||
|
||||
SocketApi *socketApi();
|
||||
NavigationPaneHelper &navigationPaneHelper() { return _navigationPaneHelper; }
|
||||
|
||||
/**
|
||||
* Check if @a path is a valid path for a new folder considering the already sync'ed items.
|
||||
|
@ -284,7 +283,9 @@ private:
|
|||
// finds all folder configuration files
|
||||
// and create the folders
|
||||
QString getBackupName(QString fullPathName) const;
|
||||
void registerFolderMonitor(Folder *folder);
|
||||
|
||||
// makes the folder known to the socket api
|
||||
void registerFolderWithSocketApi(Folder *folder);
|
||||
|
||||
// restarts the application (Linux only)
|
||||
void restartApplication();
|
||||
|
@ -298,9 +299,6 @@ private:
|
|||
QPointer<Folder> _lastSyncFolder;
|
||||
bool _syncEnabled;
|
||||
|
||||
/// Watching for file changes in folders
|
||||
QMap<QString, FolderWatcher *> _folderWatchers;
|
||||
|
||||
/// Starts regular etag query jobs
|
||||
QTimer _etagPollTimer;
|
||||
/// The currently running etag query
|
||||
|
@ -319,6 +317,7 @@ private:
|
|||
QTimer _startScheduledSyncTimer;
|
||||
|
||||
QScopedPointer<SocketApi> _socketApi;
|
||||
NavigationPaneHelper _navigationPaneHelper;
|
||||
|
||||
bool _appRestartRequired;
|
||||
|
||||
|
|
|
@ -68,6 +68,11 @@ bool FolderWatcher::pathIsIgnored(const QString &path)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool FolderWatcher::isReliable() const
|
||||
{
|
||||
return _isReliable;
|
||||
}
|
||||
|
||||
void FolderWatcher::changeDetected(const QString &path)
|
||||
{
|
||||
QStringList paths(path);
|
||||
|
|
|
@ -72,13 +72,29 @@ public:
|
|||
/* Check if the path is ignored. */
|
||||
bool pathIsIgnored(const QString &path);
|
||||
|
||||
/**
|
||||
* Returns false if the folder watcher can't be trusted to capture all
|
||||
* notifications.
|
||||
*
|
||||
* For example, this can happen on linux if the inotify user limit from
|
||||
* /proc/sys/fs/inotify/max_user_watches is exceeded.
|
||||
*/
|
||||
bool isReliable() const;
|
||||
|
||||
signals:
|
||||
/** Emitted when one of the watched directories or one
|
||||
* of the contained files is changed. */
|
||||
void pathChanged(const QString &path);
|
||||
|
||||
/** Emitted if an error occurs */
|
||||
void error(const QString &error);
|
||||
/**
|
||||
* Emitted if some notifications were lost.
|
||||
*
|
||||
* Would happen, for example, if the number of pending notifications
|
||||
* exceeded the allocated buffer size on Windows. Note that the folder
|
||||
* watcher could still be able to capture all future notifications -
|
||||
* i.e. isReliable() is orthogonal to losing changes occasionally.
|
||||
*/
|
||||
void lostChanges();
|
||||
|
||||
protected slots:
|
||||
// called from the implementations to indicate a change in path
|
||||
|
@ -93,6 +109,7 @@ private:
|
|||
QTime _timer;
|
||||
QSet<QString> _lastPaths;
|
||||
Folder *_folder;
|
||||
bool _isReliable = true;
|
||||
|
||||
friend class FolderWatcherPrivate;
|
||||
};
|
||||
|
|
|
@ -78,6 +78,12 @@ void FolderWatcherPrivate::inotifyRegisterPath(const QString &path)
|
|||
IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVE | IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF | IN_UNMOUNT | IN_ONLYDIR);
|
||||
if (wd > -1) {
|
||||
_watches.insert(wd, path);
|
||||
} else {
|
||||
// If we're running out of memory or inotify watches, become
|
||||
// unreliable.
|
||||
if (errno == ENOMEM || errno == ENOSPC) {
|
||||
_parent->_isReliable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ void WatcherThread::watchChanges(size_t fileNotifyBufferSize,
|
|||
DWORD errorCode = GetLastError();
|
||||
if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
|
||||
qCDebug(lcFolderWatcher) << "The buffer for changes overflowed! Triggering a generic change and resizing";
|
||||
emit lostChanges();
|
||||
emit changed(_path);
|
||||
*increaseBufferSize = true;
|
||||
} else {
|
||||
|
@ -199,6 +200,8 @@ FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path
|
|||
_thread = new WatcherThread(path);
|
||||
connect(_thread, SIGNAL(changed(const QString &)),
|
||||
_parent, SLOT(changeDetected(const QString &)));
|
||||
connect(_thread, SIGNAL(lostChanges()),
|
||||
_parent, SIGNAL(lostChanges()));
|
||||
_thread->start();
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ protected:
|
|||
|
||||
signals:
|
||||
void changed(const QString &path);
|
||||
void lostChanges();
|
||||
|
||||
private:
|
||||
QString _path;
|
||||
|
|
|
@ -44,6 +44,7 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
|||
|
||||
connect(_ui->desktopNotificationsCheckBox, &QAbstractButton::toggled,
|
||||
this, &GeneralSettings::slotToggleOptionalDesktopNotifications);
|
||||
connect(_ui->showInExplorerNavigationPaneCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotShowInExplorerNavigationPane);
|
||||
|
||||
_ui->autostartCheckBox->setChecked(Utility::hasLaunchOnStartup(Theme::instance()->appName()));
|
||||
connect(_ui->autostartCheckBox, &QAbstractButton::toggled, this, &GeneralSettings::slotToggleLaunchOnStartup);
|
||||
|
@ -73,6 +74,13 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
|||
_ui->crashreporterCheckBox->setVisible(false);
|
||||
#endif
|
||||
|
||||
// Hide on non-Windows, or WindowsVersion < 10.
|
||||
// The condition should match the default value of ConfigFile::showInExplorerNavigationPane.
|
||||
#ifdef Q_OS_WIN
|
||||
if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS10)
|
||||
#endif
|
||||
_ui->showInExplorerNavigationPaneCheckBox->setVisible(false);
|
||||
|
||||
/* Set the left contents margin of the layout to zero to make the checkboxes
|
||||
* align properly vertically , fixes bug #3758
|
||||
*/
|
||||
|
@ -107,6 +115,7 @@ void GeneralSettings::loadMiscSettings()
|
|||
ConfigFile cfgFile;
|
||||
_ui->monoIconsCheckBox->setChecked(cfgFile.monoIcons());
|
||||
_ui->desktopNotificationsCheckBox->setChecked(cfgFile.optionalDesktopNotifications());
|
||||
_ui->showInExplorerNavigationPaneCheckBox->setChecked(cfgFile.showInExplorerNavigationPane());
|
||||
_ui->crashreporterCheckBox->setChecked(cfgFile.crashReporter());
|
||||
auto newFolderLimit = cfgFile.newBigFolderSizeLimit();
|
||||
_ui->newFolderLimitCheckBox->setChecked(newFolderLimit.first);
|
||||
|
@ -162,6 +171,14 @@ void GeneralSettings::slotToggleOptionalDesktopNotifications(bool enable)
|
|||
cfgFile.setOptionalDesktopNotifications(enable);
|
||||
}
|
||||
|
||||
void GeneralSettings::slotShowInExplorerNavigationPane(bool checked)
|
||||
{
|
||||
ConfigFile cfgFile;
|
||||
cfgFile.setShowInExplorerNavigationPane(checked);
|
||||
// Now update the registry with the change.
|
||||
FolderMan::instance()->navigationPaneHelper().setShowInExplorerNavigationPane(checked);
|
||||
}
|
||||
|
||||
void GeneralSettings::slotIgnoreFilesEditor()
|
||||
{
|
||||
if (_ignoreEditor.isNull()) {
|
||||
|
|
|
@ -43,6 +43,7 @@ private slots:
|
|||
void saveMiscSettings();
|
||||
void slotToggleLaunchOnStartup(bool);
|
||||
void slotToggleOptionalDesktopNotifications(bool);
|
||||
void slotShowInExplorerNavigationPane(bool);
|
||||
void slotUpdateInfo();
|
||||
void slotIgnoreFilesEditor();
|
||||
void loadMiscSettings();
|
||||
|
|
|
@ -218,6 +218,17 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showInExplorerNavigationPaneCheckBox">
|
||||
<property name="text">
|
||||
<string>Show sync folders in &Explorer's Navigation Pane</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
|
|
|
@ -107,6 +107,16 @@ void IssuesWidget::showEvent(QShowEvent *ev)
|
|||
{
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||
|
||||
// Sorting by section was newly enabled. But if we restore the header
|
||||
// from a state where sorting was disabled, both of these flags will be
|
||||
// false and sorting will be impossible!
|
||||
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||
|
||||
// Switch back to "first important, then by time" ordering
|
||||
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||
|
||||
QWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
|
@ -119,6 +129,8 @@ void IssuesWidget::hideEvent(QHideEvent *ev)
|
|||
|
||||
void IssuesWidget::cleanItems(const QString &folder)
|
||||
{
|
||||
_ui->_treeWidget->setSortingEnabled(false);
|
||||
|
||||
// The issue list is a state, clear it and let the next sync fill it
|
||||
// with ignored files and propagation errors.
|
||||
int itemCnt = _ui->_treeWidget->topLevelItemCount();
|
||||
|
@ -129,6 +141,9 @@ void IssuesWidget::cleanItems(const QString &folder)
|
|||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
_ui->_treeWidget->setSortingEnabled(true);
|
||||
|
||||
// update the tabtext
|
||||
emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount()));
|
||||
}
|
||||
|
@ -240,7 +255,7 @@ bool IssuesWidget::shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAc
|
|||
const QString &filterFolderAlias) const
|
||||
{
|
||||
bool visible = true;
|
||||
auto status = item->data(0, Qt::UserRole);
|
||||
auto status = item->data(3, Qt::UserRole);
|
||||
visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored);
|
||||
visible &= (_ui->showWarnings->isChecked()
|
||||
|| (status != SyncFileItem::SoftError
|
||||
|
@ -368,13 +383,14 @@ void IssuesWidget::addError(const QString &folderAlias, const QString &message,
|
|||
|
||||
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error);
|
||||
|
||||
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
|
||||
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
|
||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||
twitem->setData(0, Qt::UserRole, timestamp);
|
||||
twitem->setIcon(0, icon);
|
||||
twitem->setToolTip(0, longTimeStr);
|
||||
twitem->setToolTip(3, message);
|
||||
twitem->setData(0, Qt::UserRole, SyncFileItem::NormalError);
|
||||
twitem->setData(2, Qt::UserRole, folderAlias);
|
||||
twitem->setToolTip(3, message);
|
||||
twitem->setData(3, Qt::UserRole, SyncFileItem::NormalError);
|
||||
|
||||
addItem(twitem);
|
||||
addErrorWidget(twitem, message, category);
|
||||
|
|
|
@ -99,6 +99,9 @@
|
|||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
|
|
|
@ -88,7 +88,7 @@ int main(int argc, char **argv)
|
|||
|
||||
// check a environment variable for core dumps
|
||||
#ifdef Q_OS_UNIX
|
||||
if (!qgetenv("OWNCLOUD_CORE_DUMP").isEmpty()) {
|
||||
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_CORE_DUMP")) {
|
||||
struct rlimit core_limit;
|
||||
core_limit.rlim_cur = RLIM_INFINITY;
|
||||
core_limit.rlim_max = RLIM_INFINITY;
|
||||
|
|
151
src/gui/navigationpanehelper.cpp
Normal file
151
src/gui/navigationpanehelper.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#include "navigationpanehelper.h"
|
||||
#include "accountmanager.h"
|
||||
#include "configfile.h"
|
||||
#include "folderman.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcNavPane, "gui.folder.navigationpane", QtInfoMsg)
|
||||
|
||||
NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
|
||||
: _folderMan(folderMan)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
_showInExplorerNavigationPane = cfg.showInExplorerNavigationPane();
|
||||
|
||||
_updateCloudStorageRegistryTimer.setSingleShot(true);
|
||||
connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
|
||||
}
|
||||
|
||||
void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show)
|
||||
{
|
||||
if (_showInExplorerNavigationPane == show)
|
||||
return;
|
||||
|
||||
_showInExplorerNavigationPane = show;
|
||||
// Re-generate a new CLSID when enabling, possibly throwing away the old one.
|
||||
// updateCloudStorageRegistry will take care of removing any unknown CLSID our application owns from the registry.
|
||||
foreach (Folder *folder, _folderMan->map())
|
||||
folder->setNavigationPaneClsid(show ? QUuid::createUuid() : QUuid());
|
||||
|
||||
scheduleUpdateCloudStorageRegistry();
|
||||
}
|
||||
|
||||
void NavigationPaneHelper::scheduleUpdateCloudStorageRegistry()
|
||||
{
|
||||
// Schedule the update to happen a bit later to avoid doing the update multiple times in a row.
|
||||
if (!_updateCloudStorageRegistryTimer.isActive())
|
||||
_updateCloudStorageRegistryTimer.start(500);
|
||||
}
|
||||
|
||||
void NavigationPaneHelper::updateCloudStorageRegistry()
|
||||
{
|
||||
// Start by looking at every registered namespace extension for the sidebar, and look for an "ApplicationName" value
|
||||
// that matches ours when we saved.
|
||||
QVector<QUuid> entriesToRemove;
|
||||
#ifdef Q_OS_WIN
|
||||
Utility::registryWalkSubKeys(
|
||||
HKEY_CURRENT_USER,
|
||||
QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace"),
|
||||
[&entriesToRemove](HKEY key, const QString &subKey) {
|
||||
QVariant appName = Utility::registryGetKeyValue(key, subKey, QStringLiteral("ApplicationName"));
|
||||
if (appName.toString() == QLatin1String(APPLICATION_NAME)) {
|
||||
QUuid clsid{ subKey };
|
||||
Q_ASSERT(!clsid.isNull());
|
||||
entriesToRemove.append(clsid);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
// Then re-save every folder that has a valid navigationPaneClsid to the registry.
|
||||
// We currently don't distinguish between new and existing CLSIDs, if it's there we just
|
||||
// save over it. We at least need to update the tile in case we are suddently using multiple accounts.
|
||||
foreach (Folder *folder, _folderMan->map()) {
|
||||
if (!folder->navigationPaneClsid().isNull()) {
|
||||
// If it already exists, unmark it for removal, this is a valid sync root.
|
||||
entriesToRemove.removeOne(folder->navigationPaneClsid());
|
||||
|
||||
QString clsidStr = folder->navigationPaneClsid().toString();
|
||||
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
|
||||
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
|
||||
|
||||
QString title = folder->shortGuiRemotePathOrAppName();
|
||||
// Write the account name in the sidebar only when using more than one account.
|
||||
if (AccountManager::instance()->accounts().size() > 1)
|
||||
title = title % " - " % folder->accountState()->account()->displayName();
|
||||
QString iconPath = QDir::toNativeSeparators(qApp->applicationFilePath());
|
||||
QString targetFolderPath = QDir::toNativeSeparators(folder->cleanPath());
|
||||
|
||||
qCInfo(lcNavPane) << "Explorer Cloud storage provider: saving path" << targetFolderPath << "to CLSID" << clsidStr;
|
||||
#ifdef Q_OS_WIN
|
||||
// Steps taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/dn889934%28v=vs.85%29.aspx
|
||||
// Step 1: Add your CLSID and name your extension
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QString(), REG_SZ, title);
|
||||
// Step 2: Set the image for your icon
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\DefaultIcon"), QString(), REG_SZ, iconPath);
|
||||
// Step 3: Add your extension to the Navigation Pane and make it visible
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("System.IsPinnedToNameSpaceTree"), REG_DWORD, 0x1);
|
||||
// Step 4: Set the location for your extension in the Navigation Pane
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath, QStringLiteral("SortOrderIndex"), REG_DWORD, 0x41);
|
||||
// Step 5: Provide the dll that hosts your extension.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\InProcServer32"), QString(), REG_EXPAND_SZ, QStringLiteral("%systemroot%\\system32\\shell32.dll"));
|
||||
// Step 6: Define the instance object
|
||||
// Indicate that your namespace extension should function like other file folder structures in File Explorer.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance"), QStringLiteral("CLSID"), REG_SZ, QStringLiteral("{0E5AAE11-A475-4c5b-AB00-C66DE400274E}"));
|
||||
// Step 7: Provide the file system attributes of the target folder
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("Attributes"), REG_DWORD, 0x11);
|
||||
// Step 8: Set the path for the sync root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\Instance\\InitPropertyBag"), QStringLiteral("TargetFolderPath"), REG_SZ, targetFolderPath);
|
||||
// Step 9: Set appropriate shell flags
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("FolderValueFlags"), REG_DWORD, 0x28);
|
||||
// Step 10: Set the appropriate flags to control your shell behavior
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, clsidPath + QStringLiteral("\\ShellFolder"), QStringLiteral("Attributes"), REG_DWORD, 0xF080004D);
|
||||
// Step 11: Register your extension in the namespace root
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QString(), REG_SZ, title);
|
||||
// Step 12: Hide your extension from the Desktop
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr, REG_DWORD, 0x1);
|
||||
|
||||
// For us, to later be able to iterate and find our own namespace entries and associated CLSID.
|
||||
// Use the macro instead of the theme to make sure it matches with the uninstaller.
|
||||
Utility::registrySetKeyValue(HKEY_CURRENT_USER, namespacePath, QStringLiteral("ApplicationName"), REG_SZ, QLatin1String(APPLICATION_NAME));
|
||||
#else
|
||||
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
|
||||
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
|
||||
Q_ASSERT(false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Then remove anything that isn't in our folder list anymore.
|
||||
foreach (auto &clsid, entriesToRemove) {
|
||||
QString clsidStr = clsid.toString();
|
||||
QString clsidPath = QString() % "Software\\Classes\\CLSID\\" % clsidStr;
|
||||
QString namespacePath = QString() % "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Desktop\\NameSpace\\" % clsidStr;
|
||||
|
||||
qCInfo(lcNavPane) << "Explorer Cloud storage provider: now unused, removing own CLSID" << clsidStr;
|
||||
#ifdef Q_OS_WIN
|
||||
Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, clsidPath);
|
||||
Utility::registryDeleteKeyTree(HKEY_CURRENT_USER, namespacePath);
|
||||
Utility::registryDeleteKeyValue(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel"), clsidStr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OCC
|
46
src/gui/navigationpanehelper.h
Normal file
46
src/gui/navigationpanehelper.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (C) by Jocelyn Turcotte <jturcotte@woboq.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NAVIGATIONPANEHELPER_H
|
||||
#define NAVIGATIONPANEHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class FolderMan;
|
||||
|
||||
class NavigationPaneHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NavigationPaneHelper(FolderMan *folderMan);
|
||||
|
||||
bool showInExplorerNavigationPane() const { return _showInExplorerNavigationPane; }
|
||||
void setShowInExplorerNavigationPane(bool show);
|
||||
|
||||
void scheduleUpdateCloudStorageRegistry();
|
||||
|
||||
private:
|
||||
void updateCloudStorageRegistry();
|
||||
|
||||
FolderMan *_folderMan;
|
||||
bool _showInExplorerNavigationPane;
|
||||
QTimer _updateCloudStorageRegistryTimer;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
#endif // NAVIGATIONPANEHELPER_H
|
|
@ -349,7 +349,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
|||
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
|
||||
}
|
||||
|
||||
QAction *action = new QAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()), menu);
|
||||
QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()));
|
||||
auto alias = folder->alias();
|
||||
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QProcess>
|
||||
#include <QMessageBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QApplication>
|
||||
|
||||
#include "wizard/owncloudwizardcommon.h"
|
||||
#include "wizard/owncloudwizard.h"
|
||||
|
@ -45,7 +46,7 @@ OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent)
|
|||
, _remoteFolder()
|
||||
{
|
||||
connect(_ocWizard, &OwncloudWizard::determineAuthType,
|
||||
this, &OwncloudSetupWizard::slotDetermineAuthType);
|
||||
this, &OwncloudSetupWizard::slotCheckServer);
|
||||
connect(_ocWizard, &OwncloudWizard::connectToOCUrl,
|
||||
this, &OwncloudSetupWizard::slotConnectToOCUrl);
|
||||
connect(_ocWizard, &OwncloudWizard::createLocalAndRemoteFolders,
|
||||
|
@ -69,6 +70,7 @@ static QPointer<OwncloudSetupWizard> wiz = 0;
|
|||
void OwncloudSetupWizard::runWizard(QObject *obj, const char *amember, QWidget *parent)
|
||||
{
|
||||
if (!wiz.isNull()) {
|
||||
bringWizardToFrontIfVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,6 +86,17 @@ bool OwncloudSetupWizard::bringWizardToFrontIfVisible()
|
|||
return false;
|
||||
}
|
||||
|
||||
if (wiz->_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds) {
|
||||
// Try to find if there is a browser open and raise that instead (Issue #6105)
|
||||
const auto allWindow = qApp->topLevelWidgets();
|
||||
auto it = std::find_if(allWindow.cbegin(), allWindow.cend(), [](QWidget *w)
|
||||
{ return QLatin1String(w->metaObject()->className()) == QLatin1String("OCC::ShibbolethWebView"); });
|
||||
if (it != allWindow.cend()) {
|
||||
ownCloudGui::raiseDialog(*it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ownCloudGui::raiseDialog(wiz->_ocWizard);
|
||||
return true;
|
||||
}
|
||||
|
@ -127,7 +140,7 @@ void OwncloudSetupWizard::startWizard()
|
|||
}
|
||||
|
||||
// also checks if an installation is valid and determines auth type in a second step
|
||||
void OwncloudSetupWizard::slotDetermineAuthType(const QString &urlString)
|
||||
void OwncloudSetupWizard::slotCheckServer(const QString &urlString)
|
||||
{
|
||||
QString fixedUrl = urlString;
|
||||
QUrl url = QUrl::fromUserInput(fixedUrl);
|
||||
|
@ -150,7 +163,7 @@ void OwncloudSetupWizard::slotDetermineAuthType(const QString &urlString)
|
|||
// We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings)
|
||||
account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
|
||||
// use a queued invocation so we're as asynchronous as with the other code path
|
||||
QMetaObject::invokeMethod(this, "slotContinueDetermineAuth", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, "slotFindServer", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,20 +177,40 @@ void OwncloudSetupWizard::slotSystemProxyLookupDone(const QNetworkProxy &proxy)
|
|||
AccountPtr account = _ocWizard->account();
|
||||
account->networkAccessManager()->setProxy(proxy);
|
||||
|
||||
slotContinueDetermineAuth();
|
||||
slotFindServer();
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotContinueDetermineAuth()
|
||||
void OwncloudSetupWizard::slotFindServer()
|
||||
{
|
||||
AccountPtr account = _ocWizard->account();
|
||||
|
||||
// Set fake credentials before we check what credential it actually is.
|
||||
account->setCredentials(CredentialsFactory::create("dummy"));
|
||||
|
||||
// Before we check the auth type, resolve any permanent redirect
|
||||
// chain there might be. We cannot do this only on url/status.php
|
||||
// in CheckServerJob, because things like url shorteners don't
|
||||
// redirect subpaths.
|
||||
// Determining the actual server URL can be a multi-stage process
|
||||
// 1. Check url/status.php with CheckServerJob
|
||||
// If that works we're done. In that case we don't check the
|
||||
// url directly for redirects, see #5954.
|
||||
// 2. Check the url for permanent redirects (like url shorteners)
|
||||
// 3. Check redirected-url/status.php with CheckServerJob
|
||||
|
||||
// Step 1: Check url/status.php
|
||||
CheckServerJob *job = new CheckServerJob(account, this);
|
||||
job->setIgnoreCredentialFailure(true);
|
||||
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotFoundServer);
|
||||
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotFindServerBehindRedirect);
|
||||
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoServerFoundTimeout);
|
||||
job->setTimeout((account->url().scheme() == "https") ? 30 * 1000 : 10 * 1000);
|
||||
job->start();
|
||||
|
||||
// Step 2 and 3 are in slotFindServerBehindRedirect()
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotFindServerBehindRedirect()
|
||||
{
|
||||
AccountPtr account = _ocWizard->account();
|
||||
|
||||
// Step 2: Resolve any permanent redirect chains on the base url
|
||||
auto redirectCheckJob = account->sendRequest("GET", account->url());
|
||||
|
||||
// Use a significantly reduced timeout for this redirect check:
|
||||
|
@ -197,20 +230,20 @@ void OwncloudSetupWizard::slotContinueDetermineAuth()
|
|||
}
|
||||
});
|
||||
|
||||
// When done, start checking status.php.
|
||||
// Step 3: When done, start checking status.php.
|
||||
connect(redirectCheckJob, &SimpleNetworkJob::finishedSignal, this,
|
||||
[this, account]() {
|
||||
CheckServerJob *job = new CheckServerJob(account, this);
|
||||
job->setIgnoreCredentialFailure(true);
|
||||
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotOwnCloudFoundAuth);
|
||||
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotNoOwnCloudFoundAuth);
|
||||
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoOwnCloudFoundAuthTimeout);
|
||||
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotFoundServer);
|
||||
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotNoServerFound);
|
||||
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoServerFoundTimeout);
|
||||
job->setTimeout((account->url().scheme() == "https") ? 30 * 1000 : 10 * 1000);
|
||||
job->start();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObject &info)
|
||||
void OwncloudSetupWizard::slotFoundServer(const QUrl &url, const QJsonObject &info)
|
||||
{
|
||||
auto serverVersion = CheckServerJob::version(info);
|
||||
|
||||
|
@ -230,14 +263,10 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObje
|
|||
qCInfo(lcWizard) << " was redirected to" << url.toString();
|
||||
}
|
||||
|
||||
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
|
||||
job->setIgnoreCredentialFailure(true);
|
||||
connect(job, &DetermineAuthTypeJob::authType,
|
||||
_ocWizard, &OwncloudWizard::setAuthType);
|
||||
job->start();
|
||||
slotDetermineAuthType();
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
||||
void OwncloudSetupWizard::slotNoServerFound(QNetworkReply *reply)
|
||||
{
|
||||
auto job = qobject_cast<CheckServerJob *>(sender());
|
||||
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
@ -250,7 +279,7 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||
} else {
|
||||
msg = tr("Failed to connect to %1 at %2:<br/>%3")
|
||||
.arg(Utility::escape(Theme::instance()->appNameGUI()),
|
||||
Utility::escape(reply->url().toString()),
|
||||
Utility::escape(_ocWizard->account()->url().toString()),
|
||||
Utility::escape(job->errorString()));
|
||||
}
|
||||
bool isDowngradeAdvised = checkDowngradeAdvised(reply);
|
||||
|
@ -265,7 +294,8 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||
QString serverError = reply->peek(1024 * 20);
|
||||
qCDebug(lcWizard) << serverError;
|
||||
QMessageBox messageBox(_ocWizard);
|
||||
messageBox.setText(serverError);
|
||||
messageBox.setText(tr("The server reported the following error:"));
|
||||
messageBox.setInformativeText(serverError);
|
||||
messageBox.addButton(QMessageBox::Ok);
|
||||
messageBox.setTextFormat(Qt::RichText);
|
||||
messageBox.exec();
|
||||
|
@ -279,12 +309,20 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||
_ocWizard->account()->resetRejectedCertificates();
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotNoOwnCloudFoundAuthTimeout(const QUrl &url)
|
||||
void OwncloudSetupWizard::slotNoServerFoundTimeout(const QUrl &url)
|
||||
{
|
||||
_ocWizard->displayError(
|
||||
tr("Timeout while trying to connect to %1 at %2.")
|
||||
.arg(Utility::escape(Theme::instance()->appNameGUI()), Utility::escape(url.toString())),
|
||||
false);
|
||||
false);
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotDetermineAuthType()
|
||||
{
|
||||
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
|
||||
connect(job, &DetermineAuthTypeJob::authType,
|
||||
_ocWizard, &OwncloudWizard::setAuthType);
|
||||
job->start();
|
||||
}
|
||||
|
||||
void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
|
||||
|
@ -579,6 +617,8 @@ void OwncloudSetupWizard::slotAssistantFinished(int result)
|
|||
folderDefinition.localPath = localFolder;
|
||||
folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
|
||||
folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
|
||||
if (folderMan->navigationPaneHelper().showInExplorerNavigationPane())
|
||||
folderDefinition.navigationPaneClsid = QUuid::createUuid();
|
||||
|
||||
auto f = folderMan->addFolder(account, folderDefinition);
|
||||
if (f) {
|
||||
|
|
|
@ -49,12 +49,16 @@ signals:
|
|||
void ownCloudWizardDone(int);
|
||||
|
||||
private slots:
|
||||
void slotDetermineAuthType(const QString &);
|
||||
void slotCheckServer(const QString &);
|
||||
void slotSystemProxyLookupDone(const QNetworkProxy &proxy);
|
||||
void slotContinueDetermineAuth();
|
||||
void slotOwnCloudFoundAuth(const QUrl &, const QJsonObject &);
|
||||
void slotNoOwnCloudFoundAuth(QNetworkReply *reply);
|
||||
void slotNoOwnCloudFoundAuthTimeout(const QUrl &url);
|
||||
|
||||
void slotFindServer();
|
||||
void slotFindServerBehindRedirect();
|
||||
void slotFoundServer(const QUrl &, const QJsonObject &);
|
||||
void slotNoServerFound(QNetworkReply *reply);
|
||||
void slotNoServerFoundTimeout(const QUrl &url);
|
||||
|
||||
void slotDetermineAuthType();
|
||||
|
||||
void slotConnectToOCUrl(const QString &);
|
||||
void slotAuthError();
|
||||
|
|
|
@ -32,6 +32,19 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
bool SortedTreeWidgetItem::operator<(const QTreeWidgetItem &other) const
|
||||
{
|
||||
int column = treeWidget()->sortColumn();
|
||||
if (column != 0) {
|
||||
return QTreeWidgetItem::operator<(other);
|
||||
}
|
||||
|
||||
// Items with empty "File" column are larger than others,
|
||||
// otherwise sort by time (this uses lexicographic ordering)
|
||||
return std::forward_as_tuple(text(1).isEmpty(), data(0, Qt::UserRole).toDateTime())
|
||||
< std::forward_as_tuple(other.text(1).isEmpty(), other.data(0, Qt::UserRole).toDateTime());
|
||||
}
|
||||
|
||||
ProtocolWidget::ProtocolWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _ui(new Ui::ProtocolWidget)
|
||||
|
@ -86,6 +99,16 @@ void ProtocolWidget::showEvent(QShowEvent *ev)
|
|||
{
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||
|
||||
// Sorting by section was newly enabled. But if we restore the header
|
||||
// from a state where sorting was disabled, both of these flags will be
|
||||
// false and sorting will be impossible!
|
||||
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||
|
||||
// Switch back to "by time" ordering
|
||||
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||
|
||||
QWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
|
@ -158,14 +181,15 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo
|
|||
columns << Utility::octetsToString(item._size);
|
||||
}
|
||||
|
||||
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
|
||||
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
|
||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||
twitem->setData(0, Qt::UserRole, timestamp);
|
||||
twitem->setIcon(0, icon);
|
||||
twitem->setToolTip(0, longTimeStr);
|
||||
twitem->setToolTip(1, item._file);
|
||||
twitem->setToolTip(3, message);
|
||||
twitem->setData(0, Qt::UserRole, item._status);
|
||||
twitem->setData(2, Qt::UserRole, folder);
|
||||
twitem->setToolTip(3, message);
|
||||
twitem->setData(3, Qt::UserRole, item._status);
|
||||
return twitem;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,21 @@ namespace Ui {
|
|||
}
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* A QTreeWidgetItem with special sorting.
|
||||
*
|
||||
* It allows items for global entries to be moved to the top if the
|
||||
* sorting section is the "Time" column.
|
||||
*/
|
||||
class SortedTreeWidgetItem : public QTreeWidgetItem
|
||||
{
|
||||
public:
|
||||
using QTreeWidgetItem::QTreeWidgetItem;
|
||||
|
||||
private:
|
||||
bool operator<(const QTreeWidgetItem &other) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ProtocolWidget class
|
||||
* @ingroup gui
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
|
|
|
@ -157,7 +157,8 @@ void SettingsDialogMac::accountAdded(AccountState *s)
|
|||
connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction);
|
||||
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialogMac::showIssuesList);
|
||||
|
||||
connect(s->account().data(), SIGNAL(accountChangedAvatar()), this, SLOT(slotAccountAvatarChanged()));
|
||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialogMac::slotAccountAvatarChanged);
|
||||
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialogMac::slotAccountDisplayNameChanged);
|
||||
|
||||
slotRefreshActivity(s);
|
||||
}
|
||||
|
@ -195,4 +196,23 @@ void SettingsDialogMac::slotAccountAvatarChanged()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialogMac::slotAccountDisplayNameChanged()
|
||||
{
|
||||
Account *account = static_cast<Account *>(sender());
|
||||
auto list = findChildren<AccountSettings *>(QString());
|
||||
foreach (auto p, list) {
|
||||
if (p->accountsState()->account() == account) {
|
||||
int idx = indexForPanel(p);
|
||||
QString displayName = account->displayName();
|
||||
if (!displayName.isNull()) {
|
||||
displayName = Theme::instance()->multiAccount()
|
||||
? SettingsDialogCommon::shortDisplayNameForSettings(account, 0)
|
||||
: tr("Account");
|
||||
setPreferencesPanelTitle(idx, displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ private slots:
|
|||
void accountAdded(AccountState *);
|
||||
void accountRemoved(AccountState *);
|
||||
void slotAccountAvatarChanged();
|
||||
void slotAccountDisplayNameChanged();
|
||||
|
||||
private:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
|
|
|
@ -133,6 +133,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
job->setProperties(
|
||||
QList<QByteArray>()
|
||||
<< "http://open-collaboration-services.org/ns:share-permissions"
|
||||
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
|
||||
<< "http://owncloud.org/ns:privatelink");
|
||||
job->setTimeout(10 * 1000);
|
||||
connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
|
||||
|
@ -160,9 +161,13 @@ void ShareDialog::slotPropfindReceived(const QVariantMap &result)
|
|||
qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
|
||||
}
|
||||
auto privateLinkUrl = result["privatelink"].toString();
|
||||
auto numericFileId = result["fileid"].toByteArray();
|
||||
if (!privateLinkUrl.isEmpty()) {
|
||||
qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl;
|
||||
_privateLinkUrl = privateLinkUrl;
|
||||
} else if (!numericFileId.isEmpty()) {
|
||||
qCInfo(lcSharing) << "Received numeric file id for" << _sharePath << numericFileId;
|
||||
_privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
showSharingUi();
|
||||
|
|
|
@ -155,14 +155,15 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||
|
||||
// Prepare sharing menu
|
||||
|
||||
_shareLinkMenu = new QMenu(this);
|
||||
connect(_shareLinkMenu, &QMenu::triggered,
|
||||
this, &ShareLinkWidget::slotShareLinkActionTriggered);
|
||||
_openLinkAction = _shareLinkMenu->addAction(tr("Open link in browser"));
|
||||
_copyLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard"));
|
||||
_copyDirectLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard (direct download)"));
|
||||
_emailLinkAction = _shareLinkMenu->addAction(tr("Send link by email"));
|
||||
_emailDirectLinkAction = _shareLinkMenu->addAction(tr("Send link by email (direct download)"));
|
||||
_linkContextMenu = new QMenu(this);
|
||||
connect(_linkContextMenu, &QMenu::triggered,
|
||||
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
|
||||
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
|
||||
_openLinkAction = _linkContextMenu->addAction(tr("Open link in browser"));
|
||||
_copyLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard"));
|
||||
_copyDirectLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard (direct download)"));
|
||||
_emailLinkAction = _linkContextMenu->addAction(tr("Send link by email"));
|
||||
_emailDirectLinkAction = _linkContextMenu->addAction(tr("Send link by email (direct download)"));
|
||||
|
||||
/*
|
||||
* Create the share manager and connect it properly
|
||||
|
@ -231,28 +232,24 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
|||
table->insertRow(row);
|
||||
|
||||
auto nameItem = new QTableWidgetItem;
|
||||
QString name = linkShare->getName();
|
||||
if (name.isEmpty()) {
|
||||
if (!_namesSupported) {
|
||||
name = tr("Public link");
|
||||
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
|
||||
} else {
|
||||
name = linkShare->getToken();
|
||||
}
|
||||
auto name = shareName(*linkShare);
|
||||
if (!_namesSupported) {
|
||||
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
|
||||
}
|
||||
nameItem->setText(name);
|
||||
nameItem->setData(Qt::UserRole, QVariant::fromValue(linkShare));
|
||||
table->setItem(row, 0, nameItem);
|
||||
|
||||
auto shareButton = new QToolButton;
|
||||
shareButton->setText("...");
|
||||
shareButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
||||
connect(shareButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotShareLinkButtonClicked);
|
||||
table->setCellWidget(row, 1, shareButton);
|
||||
auto dotdotdotButton = new QToolButton;
|
||||
dotdotdotButton->setText("...");
|
||||
dotdotdotButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
||||
connect(dotdotdotButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotContextMenuButtonClicked);
|
||||
table->setCellWidget(row, 1, dotdotdotButton);
|
||||
|
||||
auto deleteButton = new QToolButton;
|
||||
deleteButton->setIcon(deleteIcon);
|
||||
deleteButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
||||
deleteButton->setToolTip(tr("Delete link share"));
|
||||
connect(deleteButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotDeleteShareClicked);
|
||||
table->setCellWidget(row, 2, deleteButton);
|
||||
|
||||
|
@ -514,22 +511,56 @@ void ShareLinkWidget::openShareLink(const QUrl &url)
|
|||
Utility::openBrowser(url, this);
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotShareLinkButtonClicked()
|
||||
void ShareLinkWidget::confirmAndDeleteShare(const QSharedPointer<LinkShare> &share)
|
||||
{
|
||||
auto messageBox = new QMessageBox(
|
||||
QMessageBox::Question,
|
||||
tr("Confirm Link Share Deletion"),
|
||||
tr("<p>Do you really want to delete the public link share <i>%1</i>?</p>"
|
||||
"<p>Note: This action cannot be undone.</p>")
|
||||
.arg(shareName(*share)),
|
||||
QMessageBox::NoButton,
|
||||
this);
|
||||
QPushButton *yesButton =
|
||||
messageBox->addButton(tr("Delete"), QMessageBox::YesRole);
|
||||
messageBox->addButton(tr("Cancel"), QMessageBox::NoRole);
|
||||
|
||||
connect(messageBox, &QMessageBox::finished, this,
|
||||
[messageBox, yesButton, share]() {
|
||||
if (messageBox->clickedButton() == yesButton)
|
||||
share->deleteShare();
|
||||
});
|
||||
messageBox->open();
|
||||
}
|
||||
|
||||
QString ShareLinkWidget::shareName(const LinkShare &share) const
|
||||
{
|
||||
QString name = share.getName();
|
||||
if (!name.isEmpty())
|
||||
return name;
|
||||
if (!_namesSupported)
|
||||
return tr("Public link");
|
||||
return share.getToken();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotContextMenuButtonClicked()
|
||||
{
|
||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||
bool downloadEnabled = share->getShowFileListing();
|
||||
_copyDirectLinkAction->setVisible(downloadEnabled);
|
||||
_emailDirectLinkAction->setVisible(downloadEnabled);
|
||||
|
||||
_shareLinkMenu->setProperty(propertyShareC, QVariant::fromValue(share));
|
||||
_shareLinkMenu->exec(QCursor::pos());
|
||||
_linkContextMenu->setProperty(propertyShareC, QVariant::fromValue(share));
|
||||
_linkContextMenu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotShareLinkActionTriggered(QAction *action)
|
||||
void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
||||
{
|
||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||
|
||||
if (action == _copyLinkAction) {
|
||||
if (action == _deleteLinkAction) {
|
||||
confirmAndDeleteShare(share);
|
||||
} else if (action == _copyLinkAction) {
|
||||
QApplication::clipboard()->setText(share->getLink().toString());
|
||||
} else if (action == _copyDirectLinkAction) {
|
||||
QApplication::clipboard()->setText(share->getDirectDownloadLink().toString());
|
||||
|
@ -545,7 +576,7 @@ void ShareLinkWidget::slotShareLinkActionTriggered(QAction *action)
|
|||
void ShareLinkWidget::slotDeleteShareClicked()
|
||||
{
|
||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||
share->deleteShare();
|
||||
confirmAndDeleteShare(share);
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotPermissionsCheckboxClicked()
|
||||
|
|
|
@ -70,8 +70,8 @@ private slots:
|
|||
void slotPasswordChanged(const QString &newText);
|
||||
void slotNameEdited(QTableWidgetItem *item);
|
||||
|
||||
void slotShareLinkButtonClicked();
|
||||
void slotShareLinkActionTriggered(QAction *action);
|
||||
void slotContextMenuButtonClicked();
|
||||
void slotLinkContextMenuActionTriggered(QAction *action);
|
||||
|
||||
void slotDeleteShareFetched();
|
||||
void slotCreateShareFetched(const QSharedPointer<LinkShare> &share);
|
||||
|
@ -93,6 +93,12 @@ private:
|
|||
void emailShareLink(const QUrl &url);
|
||||
void openShareLink(const QUrl &url);
|
||||
|
||||
/** Confirm with the user and then delete the share */
|
||||
void confirmAndDeleteShare(const QSharedPointer<LinkShare> &share);
|
||||
|
||||
/** Retrieve a share's name, accounting for _namesSupported */
|
||||
QString shareName(const LinkShare &share) const;
|
||||
|
||||
/**
|
||||
* Retrieve the selected share, returning 0 if none.
|
||||
*/
|
||||
|
@ -120,12 +126,13 @@ private:
|
|||
// the next time getShares() finishes. This stores its id.
|
||||
QString _newShareOverrideSelectionId;
|
||||
|
||||
QMenu *_shareLinkMenu;
|
||||
QAction *_openLinkAction;
|
||||
QAction *_copyLinkAction;
|
||||
QAction *_copyDirectLinkAction;
|
||||
QAction *_emailLinkAction;
|
||||
QAction *_emailDirectLinkAction;
|
||||
QMenu *_linkContextMenu = nullptr;
|
||||
QAction *_deleteLinkAction = nullptr;
|
||||
QAction *_openLinkAction = nullptr;
|
||||
QAction *_copyLinkAction = nullptr;
|
||||
QAction *_copyDirectLinkAction = nullptr;
|
||||
QAction *_emailLinkAction = nullptr;
|
||||
QAction *_emailDirectLinkAction = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -15,25 +15,41 @@
|
|||
#include "sharemanager.h"
|
||||
#include "ocssharejob.h"
|
||||
#include "account.h"
|
||||
#include "folderman.h"
|
||||
#include "accountstate.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace {
|
||||
struct CreateShare
|
||||
{
|
||||
QString path;
|
||||
OCC::Share::ShareType shareType;
|
||||
QString shareWith;
|
||||
OCC::Share::Permissions permissions;
|
||||
};
|
||||
} // anonymous namespace
|
||||
Q_DECLARE_METATYPE(CreateShare)
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* When a share is modified, we need to tell the folders so they can adjust overlay icons
|
||||
*/
|
||||
static void updateFolder(const AccountPtr &account, const QString &path)
|
||||
{
|
||||
foreach (Folder *f, FolderMan::instance()->map()) {
|
||||
if (f->accountState()->account() != account)
|
||||
continue;
|
||||
auto folderPath = f->remotePath();
|
||||
if (path.startsWith(folderPath) && (path == folderPath || folderPath.endsWith('/') || path[folderPath.size()] == '/')) {
|
||||
// Workaround the fact that the server does not invalidate the etags of parent directories
|
||||
// when something is shared.
|
||||
auto relative = path.midRef(folderPath.size());
|
||||
if (relative.startsWith('/'))
|
||||
relative = relative.mid(1);
|
||||
f->journalDb()->avoidReadFromDbOnNextSync(relative.toString());
|
||||
|
||||
// Schedule a sync so it can update the remote permission flag and let the socket API
|
||||
// know about the shared icon.
|
||||
f->scheduleThisFolderSoon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Share::Share(AccountPtr account,
|
||||
const QString &id,
|
||||
const QString &path,
|
||||
|
@ -54,6 +70,11 @@ AccountPtr Share::account() const
|
|||
return _account;
|
||||
}
|
||||
|
||||
QString Share::path() const
|
||||
{
|
||||
return _path;
|
||||
}
|
||||
|
||||
QString Share::getId() const
|
||||
{
|
||||
return _id;
|
||||
|
@ -99,6 +120,8 @@ void Share::deleteShare()
|
|||
void Share::slotDeleted()
|
||||
{
|
||||
emit shareDeleted();
|
||||
|
||||
updateFolder(_account, _path);
|
||||
}
|
||||
|
||||
void Share::slotOcsError(int statusCode, const QString &message)
|
||||
|
@ -258,6 +281,8 @@ void ShareManager::slotLinkShareCreated(const QJsonDocument &reply)
|
|||
QSharedPointer<LinkShare> share(parseLinkShare(data));
|
||||
|
||||
emit linkShareCreated(share);
|
||||
|
||||
updateFolder(_account, share->path());
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,51 +292,34 @@ void ShareManager::createShare(const QString &path,
|
|||
const Share::Permissions permissions)
|
||||
{
|
||||
auto job = new OcsShareJob(_account);
|
||||
|
||||
// Store values that we need for creating this share later.
|
||||
CreateShare continuation;
|
||||
continuation.path = path;
|
||||
continuation.shareType = shareType;
|
||||
continuation.shareWith = shareWith;
|
||||
continuation.permissions = permissions;
|
||||
_jobContinuation[job] = QVariant::fromValue(continuation);
|
||||
|
||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotCreateShare);
|
||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||
connect(job, &OcsShareJob::shareJobFinished, this,
|
||||
[=](const QJsonDocument &reply) {
|
||||
// Find existing share permissions (if this was shared with us)
|
||||
Share::Permissions existingPermissions = SharePermissionDefault;
|
||||
foreach (const QJsonValue &element, reply.object()["ocs"].toObject()["data"].toArray()) {
|
||||
auto map = element.toObject();
|
||||
if (map["file_target"] == path)
|
||||
existingPermissions = Share::Permissions(map["permissions"].toInt());
|
||||
}
|
||||
|
||||
// Limit the permissions we request for a share to the ones the item
|
||||
// was shared with initially.
|
||||
auto perm = permissions;
|
||||
if (permissions == SharePermissionDefault) {
|
||||
perm = existingPermissions;
|
||||
} else if (existingPermissions != SharePermissionDefault) {
|
||||
perm &= existingPermissions;
|
||||
}
|
||||
|
||||
OcsShareJob *job = new OcsShareJob(_account);
|
||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotShareCreated);
|
||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||
job->createShare(path, shareType, shareWith, permissions);
|
||||
});
|
||||
job->getSharedWithMe();
|
||||
}
|
||||
|
||||
void ShareManager::slotCreateShare(const QJsonDocument &reply)
|
||||
{
|
||||
if (!_jobContinuation.contains(sender()))
|
||||
return;
|
||||
|
||||
CreateShare cont = _jobContinuation[sender()].value<CreateShare>();
|
||||
if (cont.path.isEmpty())
|
||||
return;
|
||||
_jobContinuation.remove(sender());
|
||||
|
||||
// Find existing share permissions (if this was shared with us)
|
||||
Share::Permissions existingPermissions = SharePermissionDefault;
|
||||
foreach (const QJsonValue &element, reply.object()["ocs"].toObject()["data"].toArray()) {
|
||||
auto map = element.toObject();
|
||||
if (map["file_target"] == cont.path)
|
||||
existingPermissions = Share::Permissions(map["permissions"].toInt());
|
||||
}
|
||||
|
||||
// Limit the permissions we request for a share to the ones the item
|
||||
// was shared with initially.
|
||||
if (cont.permissions == SharePermissionDefault) {
|
||||
cont.permissions = existingPermissions;
|
||||
} else if (existingPermissions != SharePermissionDefault) {
|
||||
cont.permissions &= existingPermissions;
|
||||
}
|
||||
|
||||
OcsShareJob *job = new OcsShareJob(_account);
|
||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotShareCreated);
|
||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||
job->createShare(cont.path, cont.shareType, cont.shareWith, cont.permissions);
|
||||
}
|
||||
|
||||
void ShareManager::slotShareCreated(const QJsonDocument &reply)
|
||||
{
|
||||
|
@ -320,6 +328,8 @@ void ShareManager::slotShareCreated(const QJsonDocument &reply)
|
|||
QSharedPointer<Share> share(parseShare(data));
|
||||
|
||||
emit shareCreated(share);
|
||||
|
||||
updateFolder(_account, share->path());
|
||||
}
|
||||
|
||||
void ShareManager::fetchShares(const QString &path)
|
||||
|
|
|
@ -64,6 +64,8 @@ public:
|
|||
*/
|
||||
AccountPtr account() const;
|
||||
|
||||
QString path() const;
|
||||
|
||||
/*
|
||||
* Get the id
|
||||
*/
|
||||
|
@ -293,13 +295,10 @@ private slots:
|
|||
void slotLinkShareCreated(const QJsonDocument &reply);
|
||||
void slotShareCreated(const QJsonDocument &reply);
|
||||
void slotOcsError(int statusCode, const QString &message);
|
||||
void slotCreateShare(const QJsonDocument &reply);
|
||||
|
||||
private:
|
||||
QSharedPointer<LinkShare> parseLinkShare(const QJsonObject &data);
|
||||
QSharedPointer<Share> parseShare(const QJsonObject &data);
|
||||
|
||||
QMap<QObject *, QVariant> _jobContinuation;
|
||||
AccountPtr _account;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -503,27 +503,29 @@ void fetchPrivateLinkUrl(const QString &localFile, SocketApi *target, void (Sock
|
|||
const QString localFileClean = QDir::cleanPath(localFile);
|
||||
const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
|
||||
|
||||
AccountPtr account = shareFolder->accountState()->account();
|
||||
|
||||
// Generate private link ourselves: used as a fallback
|
||||
SyncJournalFileRecord rec;
|
||||
if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
|
||||
return;
|
||||
const QString oldUrl =
|
||||
shareFolder->accountState()->account()->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded);
|
||||
account->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded);
|
||||
|
||||
// If the server doesn't have the property, use the old url directly.
|
||||
if (!shareFolder->accountState()->account()->capabilities().privateLinkPropertyAvailable()) {
|
||||
(target->*targetFun)(oldUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the new link by PROPFIND
|
||||
PropfindJob *job = new PropfindJob(shareFolder->accountState()->account(), file, target);
|
||||
job->setProperties(QList<QByteArray>() << "http://owncloud.org/ns:privatelink");
|
||||
// Retrieve the new link or numeric file id by PROPFIND
|
||||
PropfindJob *job = new PropfindJob(account, file, target);
|
||||
job->setProperties(
|
||||
QList<QByteArray>()
|
||||
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
|
||||
<< "http://owncloud.org/ns:privatelink");
|
||||
job->setTimeout(10 * 1000);
|
||||
QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) {
|
||||
auto privateLinkUrl = result["privatelink"].toString();
|
||||
auto numericFileId = result["fileid"].toByteArray();
|
||||
if (!privateLinkUrl.isEmpty()) {
|
||||
(target->*targetFun)(privateLinkUrl);
|
||||
} else if (!numericFileId.isEmpty()) {
|
||||
(target->*targetFun)(account->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded));
|
||||
} else {
|
||||
(target->*targetFun)(oldUrl);
|
||||
}
|
||||
|
|
|
@ -201,13 +201,15 @@ bool OwncloudSetupPage::urlHasChanged()
|
|||
|
||||
int OwncloudSetupPage::nextId() const
|
||||
{
|
||||
if (_authType == DetermineAuthTypeJob::Basic) {
|
||||
switch (_authType) {
|
||||
case DetermineAuthTypeJob::Basic:
|
||||
return WizardCommon::Page_HttpCreds;
|
||||
} else if (_authType == DetermineAuthTypeJob::OAuth) {
|
||||
case DetermineAuthTypeJob::OAuth:
|
||||
return WizardCommon::Page_OAuthCreds;
|
||||
} else {
|
||||
case DetermineAuthTypeJob::Shibboleth:
|
||||
return WizardCommon::Page_ShibbolethCreds;
|
||||
}
|
||||
return WizardCommon::Page_HttpCreds;
|
||||
}
|
||||
|
||||
QString OwncloudSetupPage::url() const
|
||||
|
@ -334,7 +336,7 @@ void OwncloudSetupPage::slotCertificateAccepted()
|
|||
addCertDial->reinit(); // FIXME: Why not just have this only created on use?
|
||||
validatePage();
|
||||
} else {
|
||||
addCertDial->showErrorMessage("Could not load certificate");
|
||||
addCertDial->showErrorMessage(tr("Could not load certificate. Maybe wrong password?"));
|
||||
addCertDial->show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,6 +135,7 @@ void AbstractNetworkJob::adoptRequest(QNetworkReply *reply)
|
|||
addTimer(reply);
|
||||
setReply(reply);
|
||||
setupConnections(reply);
|
||||
newReplyHook(reply);
|
||||
}
|
||||
|
||||
QUrl AbstractNetworkJob::makeAccountUrl(const QString &relativePath) const
|
||||
|
@ -172,8 +173,6 @@ void AbstractNetworkJob::slotFinished()
|
|||
QUrl requestedUrl = reply()->request().url();
|
||||
QUrl redirectUrl = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (_followRedirects && !redirectUrl.isEmpty()) {
|
||||
_redirectCount++;
|
||||
|
||||
// Redirects may be relative
|
||||
if (redirectUrl.isRelative())
|
||||
redirectUrl = requestedUrl.resolved(redirectUrl);
|
||||
|
@ -194,27 +193,32 @@ void AbstractNetworkJob::slotFinished()
|
|||
QByteArray verb = requestVerb(*reply());
|
||||
if (requestedUrl.scheme() == QLatin1String("https") && redirectUrl.scheme() == QLatin1String("http")) {
|
||||
qCWarning(lcNetworkJob) << this << "HTTPS->HTTP downgrade detected!";
|
||||
} else if (requestedUrl == redirectUrl || _redirectCount >= maxRedirects()) {
|
||||
} else if (requestedUrl == redirectUrl || _redirectCount + 1 >= maxRedirects()) {
|
||||
qCWarning(lcNetworkJob) << this << "Redirect loop detected!";
|
||||
} else if (_requestBody && _requestBody->isSequential()) {
|
||||
qCWarning(lcNetworkJob) << this << "cannot redirect request with sequential body";
|
||||
} else if (verb.isEmpty()) {
|
||||
qCWarning(lcNetworkJob) << this << "cannot redirect request: could not detect original verb";
|
||||
} else {
|
||||
emit redirected(_reply, redirectUrl, _redirectCount - 1);
|
||||
emit redirected(_reply, redirectUrl, _redirectCount);
|
||||
|
||||
// Create the redirected request and send it
|
||||
qCInfo(lcNetworkJob) << "Redirecting" << verb << requestedUrl << redirectUrl;
|
||||
resetTimeout();
|
||||
if (_requestBody) {
|
||||
_requestBody->seek(0);
|
||||
// The signal emission may have changed this value
|
||||
if (_followRedirects) {
|
||||
_redirectCount++;
|
||||
|
||||
// Create the redirected request and send it
|
||||
qCInfo(lcNetworkJob) << "Redirecting" << verb << requestedUrl << redirectUrl;
|
||||
resetTimeout();
|
||||
if (_requestBody) {
|
||||
_requestBody->seek(0);
|
||||
}
|
||||
sendRequest(
|
||||
verb,
|
||||
redirectUrl,
|
||||
reply()->request(),
|
||||
_requestBody);
|
||||
return;
|
||||
}
|
||||
sendRequest(
|
||||
verb,
|
||||
redirectUrl,
|
||||
reply()->request(),
|
||||
_requestBody);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ public:
|
|||
* requests where custom handling is necessary.
|
||||
*/
|
||||
void setFollowRedirects(bool follow);
|
||||
bool followRedirects() const { return _followRedirects; }
|
||||
|
||||
QByteArray responseTimestamp();
|
||||
|
||||
|
@ -107,8 +108,6 @@ signals:
|
|||
void redirected(QNetworkReply *reply, const QUrl &targetUrl, int redirectCount);
|
||||
|
||||
protected:
|
||||
void setupConnections(QNetworkReply *reply);
|
||||
|
||||
/** Initiate a network request, returning a QNetworkReply.
|
||||
*
|
||||
* Calls setReply() and setupConnections() on it.
|
||||
|
@ -133,6 +132,16 @@ protected:
|
|||
*/
|
||||
void adoptRequest(QNetworkReply *reply);
|
||||
|
||||
void setupConnections(QNetworkReply *reply);
|
||||
|
||||
/** Can be used by derived classes to set up the network reply.
|
||||
*
|
||||
* Particularly useful when the request is redirected and reply()
|
||||
* changes. For things like setting up additional signal connections
|
||||
* on the new reply.
|
||||
*/
|
||||
virtual void newReplyHook(QNetworkReply *) {}
|
||||
|
||||
/// Creates a url for the account from a relative path
|
||||
QUrl makeAccountUrl(const QString &relativePath) const;
|
||||
|
||||
|
|
|
@ -176,7 +176,7 @@ QUrl Account::davUrl() const
|
|||
|
||||
QUrl Account::deprecatedPrivateLinkUrl(const QByteArray &numericFileId) const
|
||||
{
|
||||
return Utility::concatUrlPath(url(),
|
||||
return Utility::concatUrlPath(_userVisibleUrl,
|
||||
QLatin1String("/index.php/f/") + QUrl::toPercentEncoding(QString::fromLatin1(numericFileId)));
|
||||
}
|
||||
|
||||
|
@ -311,6 +311,12 @@ void Account::setSslErrorHandler(AbstractSslErrorHandler *handler)
|
|||
void Account::setUrl(const QUrl &url)
|
||||
{
|
||||
_url = url;
|
||||
_userVisibleUrl = url;
|
||||
}
|
||||
|
||||
void Account::setUserVisibleHost(const QString &host)
|
||||
{
|
||||
_userVisibleUrl.setHost(host);
|
||||
}
|
||||
|
||||
QVariant Account::credentialSetting(const QString &key) const
|
||||
|
|
|
@ -99,6 +99,9 @@ public:
|
|||
void setUrl(const QUrl &url);
|
||||
QUrl url() const { return _url; }
|
||||
|
||||
/// Adjusts _userVisibleUrl once the host to use is discovered.
|
||||
void setUserVisibleHost(const QString &host);
|
||||
|
||||
/**
|
||||
* @brief The possibly themed dav path for the account. It has
|
||||
* a trailing slash.
|
||||
|
@ -266,6 +269,15 @@ private:
|
|||
QImage _avatarImg;
|
||||
QMap<QString, QVariant> _settingsMap;
|
||||
QUrl _url;
|
||||
|
||||
/** If url to use for any user-visible urls.
|
||||
*
|
||||
* If the server configures overwritehost this can be different from
|
||||
* the connection url in _url. We retrieve the visible host through
|
||||
* the ocs/v1.php/config endpoint in ConnectionValidator.
|
||||
*/
|
||||
QUrl _userVisibleUrl;
|
||||
|
||||
QList<QSslCertificate> _approvedCerts;
|
||||
QSslConfiguration _sslConfiguration;
|
||||
Capabilities _capabilities;
|
||||
|
|
|
@ -145,4 +145,9 @@ QList<int> Capabilities::httpErrorCodesThatResetFailingChunkedUploads() const
|
|||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
QString Capabilities::invalidFilenameRegex() const
|
||||
{
|
||||
return _capabilities["dav"].toMap()["invalidFilenameRegex"].toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,6 +105,17 @@ public:
|
|||
*/
|
||||
QList<int> httpErrorCodesThatResetFailingChunkedUploads() const;
|
||||
|
||||
/**
|
||||
* Regex that, if contained in a filename, will result in it not being uploaded.
|
||||
*
|
||||
* For servers older than 8.1.0 it defaults to [\\:?*"<>|]
|
||||
* For servers >= that version, it defaults to the empty regex (the server
|
||||
* will indicate invalid characters through an upload error)
|
||||
*
|
||||
* Note that it just needs to be contained. The regex [ab] is contained in "car".
|
||||
*/
|
||||
QString invalidFilenameRegex() const;
|
||||
|
||||
private:
|
||||
QVariantMap _capabilities;
|
||||
};
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <QNetworkProxy>
|
||||
|
||||
#define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds
|
||||
#define DEFAULT_FULL_LOCAL_DISCOVERY_INTERVAL (60 * 60 * 1000) // 1 hour
|
||||
#define DEFAULT_MAX_LOG_LINES 20000
|
||||
|
||||
namespace OCC {
|
||||
|
@ -45,11 +46,13 @@ Q_LOGGING_CATEGORY(lcConfigFile, "sync.configfile", QtInfoMsg)
|
|||
//static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp
|
||||
static const char remotePollIntervalC[] = "remotePollInterval";
|
||||
static const char forceSyncIntervalC[] = "forceSyncInterval";
|
||||
static const char fullLocalDiscoveryIntervalC[] = "fullLocalDiscoveryInterval";
|
||||
static const char notificationRefreshIntervalC[] = "notificationRefreshInterval";
|
||||
static const char monoIconsC[] = "monoIcons";
|
||||
static const char promptDeleteC[] = "promptDeleteAllFiles";
|
||||
static const char crashReporterC[] = "crashReporter";
|
||||
static const char optionalDesktopNoficationsC[] = "optionalDesktopNotifications";
|
||||
static const char showInExplorerNavigationPaneC[] = "showInExplorerNavigationPane";
|
||||
static const char skipUpdateCheckC[] = "skipUpdateCheck";
|
||||
static const char updateCheckIntervalC[] = "updateCheckInterval";
|
||||
static const char geometryC[] = "geometry";
|
||||
|
@ -122,6 +125,26 @@ bool ConfigFile::optionalDesktopNotifications() const
|
|||
return settings.value(QLatin1String(optionalDesktopNoficationsC), true).toBool();
|
||||
}
|
||||
|
||||
bool ConfigFile::showInExplorerNavigationPane() const
|
||||
{
|
||||
const bool defaultValue =
|
||||
#ifdef Q_OS_WIN
|
||||
QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS10
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
;
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
return settings.value(QLatin1String(showInExplorerNavigationPaneC), defaultValue).toBool();
|
||||
}
|
||||
|
||||
void ConfigFile::setShowInExplorerNavigationPane(bool show)
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.setValue(QLatin1String(showInExplorerNavigationPaneC), show);
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
int ConfigFile::timeout() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
|
@ -406,6 +429,13 @@ quint64 ConfigFile::forceSyncInterval(const QString &connection) const
|
|||
return interval;
|
||||
}
|
||||
|
||||
qint64 ConfigFile::fullLocalDiscoveryInterval() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(defaultConnection());
|
||||
return settings.value(QLatin1String(fullLocalDiscoveryIntervalC), DEFAULT_FULL_LOCAL_DISCOVERY_INTERVAL).toLongLong();
|
||||
}
|
||||
|
||||
quint64 ConfigFile::notificationRefreshInterval(const QString &connection) const
|
||||
{
|
||||
QString con(connection);
|
||||
|
|
|
@ -72,6 +72,13 @@ public:
|
|||
/* Force sync interval, in milliseconds */
|
||||
quint64 forceSyncInterval(const QString &connection = QString()) const;
|
||||
|
||||
/**
|
||||
* Interval in milliseconds within which full local discovery is required
|
||||
*
|
||||
* Use -1 to disable regular full local discoveries.
|
||||
*/
|
||||
qint64 fullLocalDiscoveryInterval() const;
|
||||
|
||||
bool monoIcons() const;
|
||||
void setMonoIcons(bool);
|
||||
|
||||
|
@ -116,6 +123,9 @@ public:
|
|||
bool optionalDesktopNotifications() const;
|
||||
void setOptionalDesktopNotifications(bool show);
|
||||
|
||||
bool showInExplorerNavigationPane() const;
|
||||
void setShowInExplorerNavigationPane(bool show);
|
||||
|
||||
int timeout() const;
|
||||
quint64 chunkSize() const;
|
||||
quint64 maxChunkSize() const;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QNetworkReply>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QPixmap>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "connectionvalidator.h"
|
||||
#include "account.h"
|
||||
|
@ -247,10 +248,22 @@ void ConnectionValidator::slotAuthSuccess()
|
|||
|
||||
void ConnectionValidator::checkServerCapabilities()
|
||||
{
|
||||
// The main flow now needs the capabilities
|
||||
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/capabilities"), this);
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, &ConnectionValidator::slotCapabilitiesRecieved);
|
||||
job->start();
|
||||
|
||||
// And we'll retrieve the ocs config in parallel
|
||||
// note that 'this' might be destroyed before the job finishes, so intentionally not parented
|
||||
auto configJob = new JsonApiJob(_account, QLatin1String("ocs/v1.php/config"));
|
||||
configJob->setTimeout(timeoutToUseMsec);
|
||||
auto account = _account; // capturing account by value will make it live long enough
|
||||
QObject::connect(configJob, &JsonApiJob::jsonReceived, _account.data(),
|
||||
[=](const QJsonDocument &json) {
|
||||
ocsConfigReceived(json, account);
|
||||
});
|
||||
configJob->start();
|
||||
}
|
||||
|
||||
void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
||||
|
@ -268,6 +281,17 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
|||
fetchUser();
|
||||
}
|
||||
|
||||
void ConnectionValidator::ocsConfigReceived(const QJsonDocument &json, AccountPtr account)
|
||||
{
|
||||
QString host = json.object().value("ocs").toObject().value("data").toObject().value("host").toString();
|
||||
if (host.isEmpty()) {
|
||||
qCWarning(lcConnectionValidator) << "Could not extract 'host' from ocs config reply";
|
||||
return;
|
||||
}
|
||||
qCInfo(lcConnectionValidator) << "Determined user-visible host to be" << host;
|
||||
account->setUserVisibleHost(host);
|
||||
}
|
||||
|
||||
void ConnectionValidator::fetchUser()
|
||||
{
|
||||
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
|
||||
|
|
|
@ -58,9 +58,9 @@ namespace OCC {
|
|||
|
|
||||
+---------------------------+
|
||||
|
|
||||
+-> checkServerCapabilities (cloud/capabilities)
|
||||
JsonApiJob
|
||||
|
|
||||
+-> checkServerCapabilities --------------v (in parallel)
|
||||
JsonApiJob (cloud/capabilities) JsonApiJob (ocs/v1.php/config)
|
||||
| +-> ocsConfigReceived
|
||||
+-> slotCapabilitiesRecieved -+
|
||||
|
|
||||
+-----------------------------------+
|
||||
|
@ -129,6 +129,7 @@ private:
|
|||
void reportResult(Status status);
|
||||
void checkServerCapabilities();
|
||||
void fetchUser();
|
||||
static void ocsConfigReceived(const QJsonDocument &json, AccountPtr account);
|
||||
|
||||
/** Sets the account's server version
|
||||
*
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "common/asserts.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
@ -53,6 +54,15 @@ QString AbstractCredentials::keychainKey(const QString &url, const QString &user
|
|||
QString key = user + QLatin1Char(':') + u;
|
||||
if (!accountId.isEmpty()) {
|
||||
key += QLatin1Char(':') + accountId;
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows the credential keys aren't namespaced properly
|
||||
// by qtkeychain. To work around that we manually add namespacing
|
||||
// to the generated keys. See #6125.
|
||||
// It's safe to do that since the key format is changing for 2.4
|
||||
// anyway to include the account ids. That means old keys can be
|
||||
// migrated to new namespaced keys on windows for 2.4.
|
||||
key.prepend(QCoreApplication::applicationName() + "_");
|
||||
#endif
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -442,32 +442,38 @@ void HttpCredentials::persist()
|
|||
_account->setCredentialSetting(QLatin1String(isOAuthC), isUsingOAuth());
|
||||
_account->wantsAccountSaved(_account);
|
||||
|
||||
// write cert
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &HttpCredentials::slotWriteClientCertPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslCertificate.toPem());
|
||||
job->start();
|
||||
// write cert if there is one
|
||||
if (!_clientSslCertificate.isNull()) {
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &HttpCredentials::slotWriteClientCertPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslCertificate.toPem());
|
||||
job->start();
|
||||
} else {
|
||||
slotWriteClientCertPEMJobDone();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpCredentials::slotWriteClientCertPEMJobDone(Job *incomingJob)
|
||||
void HttpCredentials::slotWriteClientCertPEMJobDone()
|
||||
{
|
||||
Q_UNUSED(incomingJob);
|
||||
// write ssl key
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &HttpCredentials::slotWriteClientKeyPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslKey.toPem());
|
||||
job->start();
|
||||
// write ssl key if there is one
|
||||
if (!_clientSslKey.isNull()) {
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
connect(job, &Job::finished, this, &HttpCredentials::slotWriteClientKeyPEMJobDone);
|
||||
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
|
||||
job->setBinaryData(_clientSslKey.toPem());
|
||||
job->start();
|
||||
} else {
|
||||
slotWriteClientKeyPEMJobDone();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpCredentials::slotWriteClientKeyPEMJobDone(Job *incomingJob)
|
||||
void HttpCredentials::slotWriteClientKeyPEMJobDone()
|
||||
{
|
||||
Q_UNUSED(incomingJob);
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
addSettingsToJob(_account, job);
|
||||
job->setInsecureFallback(false);
|
||||
|
|
|
@ -114,8 +114,8 @@ private Q_SLOTS:
|
|||
void slotReadClientKeyPEMJobDone(QKeychain::Job *);
|
||||
void slotReadJobDone(QKeychain::Job *);
|
||||
|
||||
void slotWriteClientCertPEMJobDone(QKeychain::Job *);
|
||||
void slotWriteClientKeyPEMJobDone(QKeychain::Job *);
|
||||
void slotWriteClientCertPEMJobDone();
|
||||
void slotWriteClientKeyPEMJobDone();
|
||||
void slotWriteJobDone(QKeychain::Job *);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "account.h"
|
||||
#include "theme.h"
|
||||
#include "common/asserts.h"
|
||||
#include "common/checksums.h"
|
||||
|
||||
#include <csync_private.h>
|
||||
#include <csync_rename.h>
|
||||
|
@ -300,28 +301,6 @@ void DiscoverySingleDirectoryJob::abort()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest-quality checksum in a 'checksums'
|
||||
* property retrieved from the server.
|
||||
*
|
||||
* Example: "ADLER32:1231 SHA1:ab124124 MD5:2131affa21"
|
||||
* -> "SHA1:ab124124"
|
||||
*/
|
||||
static QByteArray findBestChecksum(const QByteArray &checksums)
|
||||
{
|
||||
int i = 0;
|
||||
// The order of the searches here defines the preference ordering.
|
||||
if (-1 != (i = checksums.indexOf("SHA1:"))
|
||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
||||
// Now i is the start of the best checksum
|
||||
// Grab it until the next space or end of string.
|
||||
auto checksum = checksums.mid(i);
|
||||
return checksum.mid(0, checksum.indexOf(" "));
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
static std::unique_ptr<csync_file_stat_t> propertyMapToFileStat(const QMap<QString, QString> &map)
|
||||
{
|
||||
std::unique_ptr<csync_file_stat_t> file_stat(new csync_file_stat_t);
|
||||
|
|
|
@ -819,71 +819,69 @@ bool JsonApiJob::finished()
|
|||
}
|
||||
|
||||
DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent)
|
||||
: AbstractNetworkJob(account, QString(), parent)
|
||||
, _redirects(0)
|
||||
: QObject(parent)
|
||||
, _account(account)
|
||||
{
|
||||
// This job implements special redirect handling to detect redirections
|
||||
// to pages that are indicative of Shibboleth-using servers. Hence we
|
||||
// disable the standard job redirection handling here.
|
||||
_followRedirects = false;
|
||||
}
|
||||
|
||||
void DetermineAuthTypeJob::start()
|
||||
{
|
||||
send(account()->davUrl());
|
||||
AbstractNetworkJob::start();
|
||||
}
|
||||
qCInfo(lcDetermineAuthTypeJob) << "Determining auth type for" << _account->davUrl();
|
||||
|
||||
bool DetermineAuthTypeJob::finished()
|
||||
{
|
||||
QUrl redirection = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (_redirects >= maxRedirects()) {
|
||||
redirection.clear();
|
||||
}
|
||||
|
||||
auto authChallenge = reply()->rawHeader("WWW-Authenticate").toLower();
|
||||
if (redirection.isEmpty()) {
|
||||
if (authChallenge.contains("bearer ")) {
|
||||
emit authType(OAuth);
|
||||
} else if (!authChallenge.isEmpty()) {
|
||||
emit authType(Basic);
|
||||
} else {
|
||||
// This is also where we end up in case of network error.
|
||||
emit authType(Unknown);
|
||||
}
|
||||
} else if (redirection.toString().endsWith(account()->davPath())) {
|
||||
// do a new run
|
||||
_redirects++;
|
||||
resetTimeout();
|
||||
send(redirection);
|
||||
qCDebug(lcDetermineAuthTypeJob()) << "Redirected to:" << redirection.toString();
|
||||
return false; // don't discard
|
||||
} else {
|
||||
#ifndef NO_SHIBBOLETH
|
||||
QRegExp shibbolethyWords("SAML|wayf");
|
||||
shibbolethyWords.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
if (redirection.toString().contains(shibbolethyWords)) {
|
||||
emit authType(Shibboleth);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// We got redirected to an address that doesn't look like shib
|
||||
// and also doesn't have the davPath. Give up.
|
||||
qCWarning(lcDetermineAuthTypeJob()) << account()->davUrl()
|
||||
<< "was redirected to the incompatible address"
|
||||
<< redirection.toString();
|
||||
emit authType(Unknown);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DetermineAuthTypeJob::send(const QUrl &url)
|
||||
{
|
||||
QNetworkRequest req;
|
||||
// Prevent HttpCredentialsAccessManager from setting an Authorization header.
|
||||
req.setAttribute(HttpCredentials::DontAddCredentialsAttribute, true);
|
||||
sendRequest("GET", url, req);
|
||||
// Don't reuse previous auth credentials
|
||||
req.setAttribute(QNetworkRequest::AuthenticationReuseAttribute, QNetworkRequest::Manual);
|
||||
// Don't send cookies, we can't determine the auth type if we're logged in
|
||||
req.setAttribute(QNetworkRequest::CookieLoadControlAttribute, QNetworkRequest::Manual);
|
||||
|
||||
// Start two parallel requests, one determines whether it's a shib server
|
||||
// and the other checks the HTTP auth method.
|
||||
auto get = _account->sendRequest("GET", _account->davUrl(), req);
|
||||
auto propfind = _account->sendRequest("PROPFIND", _account->davUrl(), req);
|
||||
get->setTimeout(30 * 1000);
|
||||
propfind->setTimeout(30 * 1000);
|
||||
get->setIgnoreCredentialFailure(true);
|
||||
propfind->setIgnoreCredentialFailure(true);
|
||||
|
||||
connect(get, &AbstractNetworkJob::redirected, this, [this, get](QNetworkReply *, const QUrl &target, int) {
|
||||
#ifndef NO_SHIBBOLETH
|
||||
QRegExp shibbolethyWords("SAML|wayf");
|
||||
shibbolethyWords.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
if (target.toString().contains(shibbolethyWords)) {
|
||||
_resultGet = Shibboleth;
|
||||
get->setFollowRedirects(false);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
connect(get, &SimpleNetworkJob::finishedSignal, this, [this]() {
|
||||
_getDone = true;
|
||||
checkBothDone();
|
||||
});
|
||||
connect(propfind, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) {
|
||||
auto authChallenge = reply->rawHeader("WWW-Authenticate").toLower();
|
||||
if (authChallenge.contains("bearer ")) {
|
||||
_resultPropfind = OAuth;
|
||||
} else if (authChallenge.isEmpty()) {
|
||||
qCWarning(lcDetermineAuthTypeJob) << "Did not receive WWW-Authenticate reply to auth-test PROPFIND";
|
||||
}
|
||||
_propfindDone = true;
|
||||
checkBothDone();
|
||||
});
|
||||
}
|
||||
|
||||
void DetermineAuthTypeJob::checkBothDone()
|
||||
{
|
||||
if (!_getDone || !_propfindDone)
|
||||
return;
|
||||
auto result = _resultPropfind;
|
||||
// OAuth > Shib > Basic
|
||||
if (_resultGet == Shibboleth && result != OAuth)
|
||||
result = Shibboleth;
|
||||
qCInfo(lcDetermineAuthTypeJob) << "Auth type for" << _account->davUrl() << "is" << result;
|
||||
emit authType(result);
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
SimpleNetworkJob::SimpleNetworkJob(AccountPtr account, QObject *parent)
|
||||
|
|
|
@ -355,26 +355,29 @@ private:
|
|||
* @brief Checks with auth type to use for a server
|
||||
* @ingroup libsync
|
||||
*/
|
||||
class OWNCLOUDSYNC_EXPORT DetermineAuthTypeJob : public AbstractNetworkJob
|
||||
class OWNCLOUDSYNC_EXPORT DetermineAuthTypeJob : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum AuthType {
|
||||
Unknown,
|
||||
Basic,
|
||||
Basic, // also the catch-all fallback for backwards compatibility reasons
|
||||
OAuth,
|
||||
Shibboleth
|
||||
};
|
||||
|
||||
explicit DetermineAuthTypeJob(AccountPtr account, QObject *parent = 0);
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
void start();
|
||||
signals:
|
||||
void authType(AuthType);
|
||||
private slots:
|
||||
bool finished() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
void send(const QUrl &url);
|
||||
int _redirects;
|
||||
void checkBothDone();
|
||||
|
||||
AccountPtr _account;
|
||||
AuthType _resultGet = Basic;
|
||||
AuthType _resultPropfind = Basic;
|
||||
bool _getDone = false;
|
||||
bool _propfindDone = false;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -114,13 +114,13 @@ PropagateItemJob::~PropagateItemJob()
|
|||
|
||||
static qint64 getMinBlacklistTime()
|
||||
{
|
||||
return qMax(qgetenv("OWNCLOUD_BLACKLIST_TIME_MIN").toInt(),
|
||||
return qMax(qEnvironmentVariableIntValue("OWNCLOUD_BLACKLIST_TIME_MIN"),
|
||||
25); // 25 seconds
|
||||
}
|
||||
|
||||
static qint64 getMaxBlacklistTime()
|
||||
{
|
||||
int v = qgetenv("OWNCLOUD_BLACKLIST_TIME_MAX").toInt();
|
||||
int v = qEnvironmentVariableIntValue("OWNCLOUD_BLACKLIST_TIME_MAX");
|
||||
if (v > 0)
|
||||
return v;
|
||||
return 24 * 60 * 60; // 1 day
|
||||
|
@ -742,6 +742,16 @@ PropagatorJob::JobParallelism PropagatorCompositeJob::parallelism()
|
|||
return FullParallelism;
|
||||
}
|
||||
|
||||
void PropagatorCompositeJob::slotSubJobAbortFinished()
|
||||
{
|
||||
// Count that job has been finished
|
||||
_abortsCount--;
|
||||
|
||||
// Emit abort if last job has been aborted
|
||||
if (_abortsCount == 0) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
bool PropagatorCompositeJob::scheduleSelfOrChild()
|
||||
{
|
||||
|
@ -900,7 +910,8 @@ void PropagateDirectory::slotFirstJobFinished(SyncFileItem::Status status)
|
|||
|
||||
if (status != SyncFileItem::Success && status != SyncFileItem::Restoration) {
|
||||
if (_state != Finished) {
|
||||
abort();
|
||||
// Synchronously abort
|
||||
abort(AbortType::Synchronous);
|
||||
_state = Finished;
|
||||
emit finished(status);
|
||||
}
|
||||
|
|
|
@ -65,6 +65,11 @@ class PropagatorJob : public QObject
|
|||
public:
|
||||
explicit PropagatorJob(OwncloudPropagator *propagator);
|
||||
|
||||
enum AbortType {
|
||||
Synchronous,
|
||||
Asynchronous
|
||||
};
|
||||
|
||||
enum JobState {
|
||||
NotYetStarted,
|
||||
Running,
|
||||
|
@ -98,7 +103,14 @@ public:
|
|||
virtual qint64 committedDiskSpace() const { return 0; }
|
||||
|
||||
public slots:
|
||||
virtual void abort() {}
|
||||
/*
|
||||
* Asynchronous abort requires emit of abortFinished() signal,
|
||||
* while synchronous is expected to abort immedietaly.
|
||||
*/
|
||||
virtual void abort(PropagatorJob::AbortType abortType) {
|
||||
if (abortType == AbortType::Asynchronous)
|
||||
emit abortFinished();
|
||||
}
|
||||
|
||||
/** Starts this job, or a new subjob
|
||||
* returns true if a job was started.
|
||||
|
@ -110,11 +122,14 @@ signals:
|
|||
*/
|
||||
void finished(SyncFileItem::Status);
|
||||
|
||||
/**
|
||||
* Emitted when the abort is fully finished
|
||||
*/
|
||||
void abortFinished(SyncFileItem::Status status = SyncFileItem::NormalError);
|
||||
protected:
|
||||
OwncloudPropagator *propagator() const;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Abstract class to propagate a single item
|
||||
*/
|
||||
|
@ -185,10 +200,11 @@ public:
|
|||
SyncFileItemVector _tasksToDo;
|
||||
QVector<PropagatorJob *> _runningJobs;
|
||||
SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error
|
||||
quint64 _abortsCount;
|
||||
|
||||
explicit PropagatorCompositeJob(OwncloudPropagator *propagator)
|
||||
: PropagatorJob(propagator)
|
||||
, _hasError(SyncFileItem::NoStatus)
|
||||
, _hasError(SyncFileItem::NoStatus), _abortsCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -209,15 +225,32 @@ public:
|
|||
|
||||
virtual bool scheduleSelfOrChild() Q_DECL_OVERRIDE;
|
||||
virtual JobParallelism parallelism() Q_DECL_OVERRIDE;
|
||||
virtual void abort() Q_DECL_OVERRIDE
|
||||
|
||||
/*
|
||||
* Abort synchronously or asynchronously - some jobs
|
||||
* require to be finished without immediete abort (abort on job might
|
||||
* cause conflicts/duplicated files - owncloud/client/issues/5949)
|
||||
*/
|
||||
virtual void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE
|
||||
{
|
||||
foreach (PropagatorJob *j, _runningJobs)
|
||||
j->abort();
|
||||
if (!_runningJobs.empty()) {
|
||||
_abortsCount = _runningJobs.size();
|
||||
foreach (PropagatorJob *j, _runningJobs) {
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
connect(j, &PropagatorJob::abortFinished,
|
||||
this, &PropagatorCompositeJob::slotSubJobAbortFinished);
|
||||
}
|
||||
j->abort(abortType);
|
||||
}
|
||||
} else if (abortType == AbortType::Asynchronous){
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 committedDiskSpace() const Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
void slotSubJobAbortFinished();
|
||||
bool possiblyRunNextJob(PropagatorJob *next)
|
||||
{
|
||||
if (next->_state == NotYetStarted) {
|
||||
|
@ -258,11 +291,17 @@ public:
|
|||
|
||||
virtual bool scheduleSelfOrChild() Q_DECL_OVERRIDE;
|
||||
virtual JobParallelism parallelism() Q_DECL_OVERRIDE;
|
||||
virtual void abort() Q_DECL_OVERRIDE
|
||||
virtual void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE
|
||||
{
|
||||
if (_firstJob)
|
||||
_firstJob->abort();
|
||||
_subJobs.abort();
|
||||
// Force first job to abort synchronously
|
||||
// even if caller allows async abort (asyncAbort)
|
||||
_firstJob->abort(AbortType::Synchronous);
|
||||
|
||||
if (abortType == AbortType::Asynchronous){
|
||||
connect(&_subJobs, &PropagatorCompositeJob::abortFinished, this, &PropagateDirectory::abortFinished);
|
||||
}
|
||||
_subJobs.abort(abortType);
|
||||
}
|
||||
|
||||
void increaseAffectedCount()
|
||||
|
@ -280,6 +319,7 @@ private slots:
|
|||
|
||||
void slotFirstJobFinished(SyncFileItem::Status status);
|
||||
void slotSubJobsFinished(SyncFileItem::Status status);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -324,6 +364,7 @@ public:
|
|||
, _chunkSize(10 * 1000 * 1000) // 10 MB, overridden in setSyncOptions
|
||||
, _account(account)
|
||||
{
|
||||
qRegisterMetaType<PropagatorJob::AbortType>("PropagatorJob::AbortType");
|
||||
}
|
||||
|
||||
~OwncloudPropagator();
|
||||
|
@ -404,13 +445,23 @@ public:
|
|||
|
||||
void abort()
|
||||
{
|
||||
_abortRequested.fetchAndStoreOrdered(true);
|
||||
bool alreadyAborting = _abortRequested.fetchAndStoreOrdered(true);
|
||||
if (alreadyAborting)
|
||||
return;
|
||||
if (_rootJob) {
|
||||
// We're possibly already in an item's finished stack
|
||||
QMetaObject::invokeMethod(_rootJob.data(), "abort", Qt::QueuedConnection);
|
||||
// Connect to abortFinished which signals that abort has been asynchronously finished
|
||||
connect(_rootJob.data(), &PropagateDirectory::abortFinished, this, &OwncloudPropagator::emitFinished);
|
||||
|
||||
// Use Queued Connection because we're possibly already in an item's finished stack
|
||||
QMetaObject::invokeMethod(_rootJob.data(), "abort", Qt::QueuedConnection,
|
||||
Q_ARG(PropagatorJob::AbortType, PropagatorJob::AbortType::Asynchronous));
|
||||
|
||||
// Give asynchronous abort 5000 msec to finish on its own
|
||||
QTimer::singleShot(5000, this, SLOT(abortTimeout()));
|
||||
} else {
|
||||
// No root job, call emitFinished
|
||||
emitFinished(SyncFileItem::NormalError);
|
||||
}
|
||||
// abort() of all jobs will likely have already resulted in finished being emitted, but just in case.
|
||||
QMetaObject::invokeMethod(this, "emitFinished", Qt::QueuedConnection, Q_ARG(SyncFileItem::Status, SyncFileItem::NormalError));
|
||||
}
|
||||
|
||||
// timeout in seconds
|
||||
|
@ -431,6 +482,13 @@ public:
|
|||
|
||||
private slots:
|
||||
|
||||
void abortTimeout()
|
||||
{
|
||||
// Abort synchronously and finish
|
||||
_rootJob.data()->abort(PropagatorJob::AbortType::Synchronous);
|
||||
emitFinished(SyncFileItem::NormalError);
|
||||
}
|
||||
|
||||
/** Emit the finished signal and make sure it is only emitted once */
|
||||
void emitFinished(SyncFileItem::Status status)
|
||||
{
|
||||
|
|
|
@ -127,7 +127,6 @@ void GETFileJob::start()
|
|||
sendRequest("GET", _directDownloadUrl, req);
|
||||
}
|
||||
|
||||
reply()->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth
|
||||
qCDebug(lcGetJob) << _bandwidthManager << _bandwidthChoked << _bandwidthLimited;
|
||||
if (_bandwidthManager) {
|
||||
_bandwidthManager->registerDownloadJob(this);
|
||||
|
@ -137,14 +136,20 @@ void GETFileJob::start()
|
|||
qCWarning(lcGetJob) << " Network error: " << errorString();
|
||||
}
|
||||
|
||||
connect(reply(), &QNetworkReply::metaDataChanged, this, &GETFileJob::slotMetaDataChanged);
|
||||
connect(reply(), &QIODevice::readyRead, this, &GETFileJob::slotReadyRead);
|
||||
connect(reply(), &QNetworkReply::downloadProgress, this, &GETFileJob::downloadProgress);
|
||||
connect(this, &AbstractNetworkJob::networkActivity, account().data(), &Account::propagatorNetworkActivity);
|
||||
|
||||
AbstractNetworkJob::start();
|
||||
}
|
||||
|
||||
void GETFileJob::newReplyHook(QNetworkReply *reply)
|
||||
{
|
||||
reply->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth
|
||||
|
||||
connect(reply, &QNetworkReply::metaDataChanged, this, &GETFileJob::slotMetaDataChanged);
|
||||
connect(reply, &QIODevice::readyRead, this, &GETFileJob::slotReadyRead);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, &GETFileJob::downloadProgress);
|
||||
}
|
||||
|
||||
void GETFileJob::slotMetaDataChanged()
|
||||
{
|
||||
// For some reason setting the read buffer in GETFileJob::start doesn't seem to go
|
||||
|
@ -153,6 +158,10 @@ void GETFileJob::slotMetaDataChanged()
|
|||
|
||||
int httpStatus = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
// Ignore redirects
|
||||
if (httpStatus == 301 || httpStatus == 302 || httpStatus == 303 || httpStatus == 307 || httpStatus == 308)
|
||||
return;
|
||||
|
||||
// If the status code isn't 2xx, don't write the reply body to the file.
|
||||
// For any error: handle it when the job is finished, not here.
|
||||
if (httpStatus / 100 != 2) {
|
||||
|
@ -216,6 +225,8 @@ void GETFileJob::slotMetaDataChanged()
|
|||
if (!lastModified.isNull()) {
|
||||
_lastModified = Utility::qDateTimeToTime_t(lastModified.toDateTime());
|
||||
}
|
||||
|
||||
_saveBodyToFile = true;
|
||||
}
|
||||
|
||||
void GETFileJob::setBandwidthManager(BandwidthManager *bwm)
|
||||
|
@ -281,7 +292,7 @@ void GETFileJob::slotReadyRead()
|
|||
return;
|
||||
}
|
||||
|
||||
if (_device->isOpen()) {
|
||||
if (_device->isOpen() && _saveBodyToFile) {
|
||||
qint64 w = _device->write(buffer.constData(), r);
|
||||
if (w != r) {
|
||||
_errorString = _device->errorString();
|
||||
|
@ -346,13 +357,16 @@ void PropagateDownloadFile::start()
|
|||
}
|
||||
}
|
||||
|
||||
// If we have a conflict where size and mtime are identical,
|
||||
// If we have a conflict where size of the file is unchanged,
|
||||
// compare the remote checksum to the local one.
|
||||
// Maybe it's not a real conflict and no download is necessary!
|
||||
// If the hashes are collision safe and identical, we assume the content is too.
|
||||
// For weak checksums, we only do that if the mtimes are also identical.
|
||||
if (_item->_instruction == CSYNC_INSTRUCTION_CONFLICT
|
||||
&& _item->_size == _item->_previousSize
|
||||
&& _item->_modtime == _item->_previousModtime
|
||||
&& !_item->_checksumHeader.isEmpty()) {
|
||||
&& !_item->_checksumHeader.isEmpty()
|
||||
&& (csync_is_collision_safe_hash(_item->_checksumHeader)
|
||||
|| _item->_modtime == _item->_previousModtime)) {
|
||||
qCDebug(lcPropagateDownload) << _item->_file << "may not need download, computing checksum";
|
||||
auto computeChecksum = new ComputeChecksum(this);
|
||||
computeChecksum->setChecksumType(parseChecksumHeaderType(_item->_checksumHeader));
|
||||
|
@ -368,8 +382,17 @@ void PropagateDownloadFile::start()
|
|||
void PropagateDownloadFile::conflictChecksumComputed(const QByteArray &checksumType, const QByteArray &checksum)
|
||||
{
|
||||
if (makeChecksumHeader(checksumType, checksum) == _item->_checksumHeader) {
|
||||
// No download necessary, just update fs and journal metadata
|
||||
qCDebug(lcPropagateDownload) << _item->_file << "remote and local checksum match";
|
||||
// No download necessary, just update metadata
|
||||
|
||||
// Apply the server mtime locally if necessary, ensuring the journal
|
||||
// and local mtimes end up identical
|
||||
auto fn = propagator()->getFilePath(_item->_file);
|
||||
if (_item->_modtime != _item->_previousModtime) {
|
||||
FileSystem::setModTime(fn, _item->_modtime);
|
||||
emit propagator()->touchedFile(fn);
|
||||
}
|
||||
_item->_modtime = FileSystem::getModTime(fn);
|
||||
updateMetadata(/*isConflict=*/false);
|
||||
return;
|
||||
}
|
||||
|
@ -624,7 +647,10 @@ void PropagateDownloadFile::slotGetFinished()
|
|||
this, &PropagateDownloadFile::transmissionChecksumValidated);
|
||||
connect(validator, &ValidateChecksumHeader::validationFailed,
|
||||
this, &PropagateDownloadFile::slotChecksumFail);
|
||||
auto checksumHeader = job->reply()->rawHeader(checkSumHeaderC);
|
||||
auto checksumHeader = findBestChecksum(job->reply()->rawHeader(checkSumHeaderC));
|
||||
auto contentMd5Header = job->reply()->rawHeader(contentMd5HeaderC);
|
||||
if (checksumHeader.isEmpty() && !contentMd5Header.isEmpty())
|
||||
checksumHeader = "MD5:" + contentMd5Header;
|
||||
validator->start(_tmpFile.fileName(), checksumHeader);
|
||||
}
|
||||
|
||||
|
@ -897,9 +923,13 @@ void PropagateDownloadFile::slotDownloadProgress(qint64 received, qint64)
|
|||
}
|
||||
|
||||
|
||||
void PropagateDownloadFile::abort()
|
||||
void PropagateDownloadFile::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
if (_job && _job->reply())
|
||||
_job->reply()->abort();
|
||||
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,9 @@ class GETFileJob : public AbstractNetworkJob
|
|||
bool _hasEmittedFinishedSignal;
|
||||
time_t _lastModified;
|
||||
|
||||
/// Will be set to true once we've seen a 2xx response header
|
||||
bool _saveBodyToFile = false;
|
||||
|
||||
public:
|
||||
// DOES NOT take ownership of the device.
|
||||
explicit GETFileJob(AccountPtr account, const QString &path, QFile *device,
|
||||
|
@ -76,6 +79,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void newReplyHook(QNetworkReply *reply) override;
|
||||
|
||||
void setBandwidthManager(BandwidthManager *bwm);
|
||||
void setChoked(bool c);
|
||||
void setBandwidthLimited(bool b);
|
||||
|
@ -185,7 +190,7 @@ private slots:
|
|||
/// Called when it's time to update the db metadata
|
||||
void updateMetadata(bool isConflict);
|
||||
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
void slotDownloadProgress(qint64, qint64);
|
||||
void slotChecksumFail(const QString &errMsg);
|
||||
|
||||
|
|
|
@ -75,10 +75,14 @@ void PropagateRemoteDelete::start()
|
|||
_job->start();
|
||||
}
|
||||
|
||||
void PropagateRemoteDelete::abort()
|
||||
void PropagateRemoteDelete::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
if (_job && _job->reply())
|
||||
_job->reply()->abort();
|
||||
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateRemoteDelete::slotDeleteJobFinished()
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
{
|
||||
}
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
|
||||
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return !_item->isDirectory(); }
|
||||
|
||||
|
|
|
@ -60,10 +60,14 @@ void PropagateRemoteMkdir::slotStartMkcolJob()
|
|||
_job->start();
|
||||
}
|
||||
|
||||
void PropagateRemoteMkdir::abort()
|
||||
void PropagateRemoteMkdir::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
if (_job && _job->reply())
|
||||
_job->reply()->abort();
|
||||
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateRemoteMkdir::setDeleteExisting(bool enabled)
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
{
|
||||
}
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
|
||||
// Creating a directory should be fast.
|
||||
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return true; }
|
||||
|
|
|
@ -117,10 +117,14 @@ void PropagateRemoteMove::start()
|
|||
_job->start();
|
||||
}
|
||||
|
||||
void PropagateRemoteMove::abort()
|
||||
void PropagateRemoteMove::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
if (_job && _job->reply())
|
||||
_job->reply()->abort();
|
||||
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateRemoteMove::slotMoveJobFinished()
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
{
|
||||
}
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
JobParallelism parallelism() Q_DECL_OVERRIDE { return _item->isDirectory() ? WaitForFinished : FullParallelism; }
|
||||
|
||||
/**
|
||||
|
|
|
@ -558,20 +558,24 @@ void PropagateUploadFileCommon::slotJobDestroyed(QObject *job)
|
|||
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end());
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::abort()
|
||||
void PropagateUploadFileCommon::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
foreach (auto *job, _jobs) {
|
||||
if (job->reply()) {
|
||||
job->reply()->abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used whenever there is an error occuring and jobs might be in progress
|
||||
void PropagateUploadFileCommon::abortWithError(SyncFileItem::Status status, const QString &error)
|
||||
{
|
||||
_finished = true;
|
||||
abort();
|
||||
abort(AbortType::Synchronous);
|
||||
done(status, error);
|
||||
}
|
||||
|
||||
|
@ -625,4 +629,33 @@ void PropagateUploadFileCommon::finalize()
|
|||
|
||||
done(SyncFileItem::Success);
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::prepareAbort(PropagatorJob::AbortType abortType) {
|
||||
if (!_jobs.empty()) {
|
||||
// Count number of jobs to be aborted asynchronously
|
||||
_abortCount = _jobs.size();
|
||||
|
||||
foreach (AbstractNetworkJob *job, _jobs) {
|
||||
// Check if async abort is requested
|
||||
if (job->reply() && abortType == AbortType::Asynchronous) {
|
||||
// Connect to finished signal of job reply
|
||||
// to asynchonously finish the abort
|
||||
connect(job->reply(), &QNetworkReply::finished, this, &PropagateUploadFileCommon::slotReplyAbortFinished);
|
||||
}
|
||||
}
|
||||
} else if (abortType == AbortType::Asynchronous) {
|
||||
// Empty job list, emit abortFinished immedietaly
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::slotReplyAbortFinished()
|
||||
{
|
||||
_abortCount--;
|
||||
|
||||
if (_abortCount == 0) {
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -129,6 +129,11 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
QIODevice *device()
|
||||
{
|
||||
return _device;
|
||||
}
|
||||
|
||||
QString errorString()
|
||||
{
|
||||
return _errorString.isEmpty() ? AbstractNetworkJob::errorString() : _errorString;
|
||||
|
@ -205,6 +210,7 @@ protected:
|
|||
QVector<AbstractNetworkJob *> _jobs; /// network jobs that are currently in transit
|
||||
bool _finished BITFIELD(1); /// Tells that all the jobs have been finished
|
||||
bool _deleteExisting BITFIELD(1);
|
||||
quint64 _abortCount; /// Keep track of number of aborted items
|
||||
|
||||
// measure the performance of checksum calc and upload
|
||||
#ifdef WITH_TESTING
|
||||
|
@ -218,6 +224,7 @@ public:
|
|||
: PropagateItemJob(propagator, item)
|
||||
, _finished(false)
|
||||
, _deleteExisting(false)
|
||||
, _abortCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -248,13 +255,20 @@ public:
|
|||
void abortWithError(SyncFileItem::Status status, const QString &error);
|
||||
|
||||
public slots:
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
void slotJobDestroyed(QObject *job);
|
||||
|
||||
private slots:
|
||||
void slotReplyAbortFinished();
|
||||
void slotPollFinished();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Prepares the abort e.g. connects proper signals and slots
|
||||
* to the subjobs to abort asynchronously
|
||||
*/
|
||||
void prepareAbort(PropagatorJob::AbortType abortType);
|
||||
|
||||
/**
|
||||
* Checks whether the current error is one that should reset the whole
|
||||
* transfer if it happens too often. If so: Bump UploadInfo::errorCount
|
||||
|
@ -287,15 +301,15 @@ private:
|
|||
* In the non-resuming case it is 0.
|
||||
* If we are resuming, this is the first chunk we need to send
|
||||
*/
|
||||
int _startChunk;
|
||||
int _startChunk = 0;
|
||||
/**
|
||||
* This is the next chunk that we need to send. Starting from 0 even if _startChunk != 0
|
||||
* (In other words, _startChunk + _currentChunk is really the number of the chunk we need to send next)
|
||||
* (In other words, _currentChunk is the number of the chunk that we already sent or started sending)
|
||||
*/
|
||||
int _currentChunk;
|
||||
int _chunkCount; /// Total number of chunks for this file
|
||||
int _transferId; /// transfer id (part of the url)
|
||||
int _currentChunk = 0;
|
||||
int _chunkCount = 0; /// Total number of chunks for this file
|
||||
int _transferId = 0; /// transfer id (part of the url)
|
||||
|
||||
quint64 chunkSize() const {
|
||||
// Old chunking does not use dynamic chunking algorithm, and does not adjusts the chunk size respectively,
|
||||
|
@ -303,7 +317,6 @@ private:
|
|||
return propagator()->syncOptions()._initialChunkSize;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
PropagateUploadFileV1(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
|
||||
: PropagateUploadFileCommon(propagator, item)
|
||||
|
@ -311,7 +324,8 @@ public:
|
|||
}
|
||||
|
||||
void doStartUpload() Q_DECL_OVERRIDE;
|
||||
|
||||
public slots:
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
private slots:
|
||||
void startNextChunk();
|
||||
void slotPutFinished();
|
||||
|
@ -328,11 +342,11 @@ class PropagateUploadFileNG : public PropagateUploadFileCommon
|
|||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
quint64 _sent; /// amount of data (bytes) that was already sent
|
||||
uint _transferId; /// transfer id (part of the url)
|
||||
int _currentChunk; /// Id of the next chunk that will be sent
|
||||
quint64 _currentChunkSize; /// current chunk size
|
||||
bool _removeJobError; /// If not null, there was an error removing the job
|
||||
quint64 _sent = 0; /// amount of data (bytes) that was already sent
|
||||
uint _transferId = 0; /// transfer id (part of the url)
|
||||
int _currentChunk = 0; /// Id of the next chunk that will be sent
|
||||
quint64 _currentChunkSize = 0; /// current chunk size
|
||||
bool _removeJobError = false; /// If not null, there was an error removing the job
|
||||
|
||||
// Map chunk number with its size from the PROPFIND on resume.
|
||||
// (Only used from slotPropfindIterate/slotPropfindFinished because the LsColJob use signals to report data.)
|
||||
|
@ -352,7 +366,6 @@ private:
|
|||
public:
|
||||
PropagateUploadFileNG(OwncloudPropagator *propagator, const SyncFileItemPtr &item)
|
||||
: PropagateUploadFileCommon(propagator, item)
|
||||
, _currentChunkSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -361,6 +374,8 @@ public:
|
|||
private:
|
||||
void startNewUpload();
|
||||
void startNextChunk();
|
||||
public slots:
|
||||
void abort(AbortType abortType) Q_DECL_OVERRIDE;
|
||||
private slots:
|
||||
void slotPropfindFinished();
|
||||
void slotPropfindFinishedWithError();
|
||||
|
|
|
@ -365,7 +365,7 @@ void PropagateUploadFileNG::slotPutFinished()
|
|||
// target duration for each chunk upload.
|
||||
double targetDuration = propagator()->syncOptions()._targetChunkUploadDuration;
|
||||
if (targetDuration > 0) {
|
||||
double uploadTime = job->msSinceStart();
|
||||
double uploadTime = job->msSinceStart() + 1; // add one to avoid div-by-zero
|
||||
|
||||
auto predictedGoodSize = static_cast<quint64>(
|
||||
_currentChunkSize / uploadTime * targetDuration);
|
||||
|
@ -491,4 +491,26 @@ void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 total)
|
|||
}
|
||||
propagator()->reportProgress(*_item, _sent + sent - total);
|
||||
}
|
||||
|
||||
void PropagateUploadFileNG::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
// Prepare abort
|
||||
prepareAbort(abortType);
|
||||
|
||||
// Abort all jobs (if there are any left), except final PUT
|
||||
foreach (AbstractNetworkJob *job, _jobs) {
|
||||
if (job->reply()) {
|
||||
if (abortType == AbortType::Asynchronous && qobject_cast<MoveJob *>(job)){
|
||||
// If it is async abort, dont abort
|
||||
// MoveJob since it might result in conflict,
|
||||
// only PUT and MKDIR jobs can be safely aborted.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Abort the job
|
||||
job->reply()->abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -350,4 +350,31 @@ void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total)
|
|||
}
|
||||
propagator()->reportProgress(*_item, amount);
|
||||
}
|
||||
|
||||
void PropagateUploadFileV1::abort(PropagatorJob::AbortType abortType)
|
||||
{
|
||||
// Prepare abort
|
||||
prepareAbort(abortType);
|
||||
|
||||
// Abort all jobs (if there are any left), except final PUT
|
||||
foreach (AbstractNetworkJob *job, _jobs) {
|
||||
if (job->reply()) {
|
||||
// If asynchronous abort allowed,
|
||||
// dont abort final PUT which uploaded its data,
|
||||
// since this might result in conflicts
|
||||
if (PUTFileJob *putJob = qobject_cast<PUTFileJob *>(job)){
|
||||
if (abortType == AbortType::Asynchronous
|
||||
&& _chunkCount > 0
|
||||
&& (((_currentChunk + _startChunk) % _chunkCount) == 0)
|
||||
&& putJob->device()->atEnd()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Abort the job
|
||||
job->reply()->abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ namespace OCC {
|
|||
* It's here for being shared between Upload- and Download Job
|
||||
*/
|
||||
static const char checkSumHeaderC[] = "OC-Checksum";
|
||||
static const char contentMd5HeaderC[] = "Content-MD5";
|
||||
|
||||
/**
|
||||
* @brief Declaration of the other propagation jobs
|
||||
|
|
|
@ -571,7 +571,6 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
|
|||
dir = SyncFileItem::None;
|
||||
// For directories, metadata-only updates will be done after all their files are propagated.
|
||||
if (!isDirectory) {
|
||||
emit syncItemDiscovered(*item);
|
||||
|
||||
// Update the database now already: New remote fileid or Etag or RemotePerm
|
||||
// Or for files that were detected as "resolved conflict".
|
||||
|
@ -608,6 +607,9 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
|
|||
}
|
||||
|
||||
_journal->setFileRecordMetadata(item->toSyncJournalFileRecordWithInode(filePath));
|
||||
|
||||
// This might have changed the shared flag, so we must notify SyncFileStatusTracker for example
|
||||
emit itemCompleted(item);
|
||||
} else {
|
||||
// The local tree is walked first and doesn't have all the info from the server.
|
||||
// Update only outdated data from the disk.
|
||||
|
@ -683,8 +685,6 @@ int SyncEngine::treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other,
|
|||
}
|
||||
|
||||
_syncItemMap.insert(key, item);
|
||||
|
||||
emit syncItemDiscovered(*item);
|
||||
return re;
|
||||
}
|
||||
|
||||
|
@ -813,6 +813,7 @@ void SyncEngine::startSync()
|
|||
}
|
||||
|
||||
_csync_ctx->read_remote_from_db = true;
|
||||
_lastLocalDiscoveryStyle = _csync_ctx->local_discovery_style;
|
||||
|
||||
bool ok;
|
||||
auto selectiveSyncBlackList = _journal->getSelectiveSyncList(SyncJournalDb::SelectiveSyncBlackList, &ok);
|
||||
|
@ -960,11 +961,20 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
|||
}
|
||||
|
||||
// Check for invalid character in old server version
|
||||
if (_account->serverVersionInt() < Account::makeServerVersion(8, 1, 0)) {
|
||||
// Server version older than 8.1 don't support these character in filename.
|
||||
static const QRegExp invalidCharRx("[\\\\:?*\"<>|]");
|
||||
QString invalidFilenamePattern = _account->capabilities().invalidFilenameRegex();
|
||||
if (invalidFilenamePattern.isNull()
|
||||
&& _account->serverVersionInt() < Account::makeServerVersion(8, 1, 0)) {
|
||||
// Server versions older than 8.1 don't support some characters in filenames.
|
||||
// If the capability is not set, default to a pattern that avoids uploading
|
||||
// files with names that contain these.
|
||||
// It's important to respect the capability also for older servers -- the
|
||||
// version check doesn't make sense for custom servers.
|
||||
invalidFilenamePattern = "[\\\\:?*\"<>|]";
|
||||
}
|
||||
if (!invalidFilenamePattern.isEmpty()) {
|
||||
const QRegExp invalidFilenameRx(invalidFilenamePattern);
|
||||
for (auto it = syncItems.begin(); it != syncItems.end(); ++it) {
|
||||
if ((*it)->_direction == SyncFileItem::Up && (*it)->destination().contains(invalidCharRx)) {
|
||||
if ((*it)->_direction == SyncFileItem::Up && (*it)->destination().contains(invalidFilenameRx)) {
|
||||
(*it)->_errorString = tr("File name contains at least one invalid character");
|
||||
(*it)->_instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
}
|
||||
|
@ -1022,7 +1032,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
|||
_progressInfo->startEstimateUpdates();
|
||||
|
||||
// post update phase script: allow to tweak stuff by a custom script in debug mode.
|
||||
if (!qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT").isEmpty()) {
|
||||
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_POST_UPDATE_SCRIPT")) {
|
||||
#ifndef NDEBUG
|
||||
QString script = qgetenv("OWNCLOUD_POST_UPDATE_SCRIPT");
|
||||
|
||||
|
@ -1455,7 +1465,7 @@ void SyncEngine::checkForPermission(SyncFileItemVector &syncItems)
|
|||
|
||||
RemotePermissions SyncEngine::getPermissions(const QString &file) const
|
||||
{
|
||||
static bool isTest = qgetenv("OWNCLOUD_TEST_PERMISSIONS").toInt();
|
||||
static bool isTest = qEnvironmentVariableIntValue("OWNCLOUD_TEST_PERMISSIONS");
|
||||
if (isTest) {
|
||||
QRegExp rx("_PERM_([^_]*)_[^/]*$");
|
||||
if (rx.indexIn(file) != -1) {
|
||||
|
@ -1555,6 +1565,12 @@ AccountPtr SyncEngine::account() const
|
|||
return _account;
|
||||
}
|
||||
|
||||
void SyncEngine::setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> dirs)
|
||||
{
|
||||
_csync_ctx->local_discovery_style = style;
|
||||
_csync_ctx->locally_touched_dirs = std::move(dirs);
|
||||
}
|
||||
|
||||
void SyncEngine::abort()
|
||||
{
|
||||
if (_propagator)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <QMap>
|
||||
#include <QStringList>
|
||||
#include <QSharedPointer>
|
||||
#include <set>
|
||||
|
||||
#include <csync.h>
|
||||
|
||||
|
@ -92,6 +93,7 @@ public:
|
|||
AccountPtr account() const;
|
||||
SyncJournalDb *journal() const { return _journal; }
|
||||
QString localPath() const { return _localPath; }
|
||||
|
||||
/**
|
||||
* Minimum age, in milisecond, of a file that can be uploaded.
|
||||
* Files more recent than that are not going to be uploaeded as they are considered
|
||||
|
@ -99,14 +101,28 @@ public:
|
|||
*/
|
||||
static qint64 minimumFileAgeForUpload; // in ms
|
||||
|
||||
/**
|
||||
* Control whether local discovery should read from filesystem or db.
|
||||
*
|
||||
* If style is Partial, the paths is a set of file paths relative to
|
||||
* the synced folder. All the parent directories of these paths will not
|
||||
* be read from the db and scanned on the filesystem.
|
||||
*
|
||||
* Note, the style and paths are only retained for the next sync and
|
||||
* revert afterwards. Use _lastLocalDiscoveryStyle to discover the last
|
||||
* sync's style.
|
||||
*/
|
||||
void setLocalDiscoveryOptions(LocalDiscoveryStyle style, std::set<QByteArray> dirs = {});
|
||||
|
||||
/** Access the last sync run's local discovery style */
|
||||
LocalDiscoveryStyle lastLocalDiscoveryStyle() const { return _lastLocalDiscoveryStyle; }
|
||||
|
||||
signals:
|
||||
void csyncUnavailable();
|
||||
|
||||
// During update, before reconcile
|
||||
void rootEtag(QString);
|
||||
|
||||
// before actual syncing (after update+reconcile) for each item
|
||||
void syncItemDiscovered(const SyncFileItem &);
|
||||
// after the above signals. with the items that actually need propagating
|
||||
void aboutToPropagate(SyncFileItemVector &);
|
||||
|
||||
|
@ -272,6 +288,9 @@ private:
|
|||
|
||||
/** List of unique errors that occurred in a sync run. */
|
||||
QSet<QString> _uniqueErrors;
|
||||
|
||||
/** The kind of local discovery the last sync run used */
|
||||
LocalDiscoveryStyle _lastLocalDiscoveryStyle = LocalDiscoveryStyle::DatabaseAndFilesystem;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue