mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-27 09:30:13 +03:00
Merge branch 'master' into upstream/owncloudsynclog
This commit is contained in:
commit
689ab5b8b3
100 changed files with 10330 additions and 4928 deletions
|
@ -11,3 +11,14 @@ diff -ruN nextcloud-client-2.4.0.orig/src/CMakeLists.txt nextcloud-client-2.4.0/
|
|||
if (Qt5Core_VERSION VERSION_LESS 5.9.0)
|
||||
message(STATUS "For HTTP/2 support, compile with Qt 5.9 or higher.")
|
||||
endif()
|
||||
--- nextcloud-client-2.4.0.orig/admin/osx/CMakeLists.txt 2018-05-22 07:01:48.248646951 +0200
|
||||
+++ nextcloud-client-2.4.0/admin/osx/CMakeLists.txt 2018-05-22 07:01:51.012689711 +0200
|
||||
@@ -11,7 +11,7 @@
|
||||
set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "0")
|
||||
endif()
|
||||
|
||||
-find_package(Qt5 5.6 COMPONENTS Core REQUIRED)
|
||||
+find_package(Qt5 5.5 COMPONENTS Core REQUIRED)
|
||||
configure_file(create_mac.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
|
||||
configure_file(macosx.pkgproj.cmake ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
||||
configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh)
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -12,7 +12,7 @@ else()
|
|||
endif()
|
||||
|
||||
find_package(Qt5 5.6 COMPONENTS Core REQUIRED)
|
||||
configure_file(create_mac_pkg.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
|
||||
configure_file(macosx.pkgproj ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
||||
configure_file(create_mac.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
|
||||
configure_file(macosx.pkgproj.cmake ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
||||
configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh)
|
||||
configure_file(post_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/post_install.sh)
|
||||
|
|
|
@ -337,6 +337,13 @@ for binary in binaries:
|
|||
for plugin in QT_PLUGINS:
|
||||
FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
||||
|
||||
if LooseVersion(qt_version) >= LooseVersion("5.10.0"):
|
||||
args = ['plutil', '-insert', 'LSMinimumSystemVersion', '-string', '10.10.0', os.path.join(bundle_dir, 'Contents', 'Info.plist')]
|
||||
commands.append(args)
|
||||
else:
|
||||
args = ['plutil', '-insert', 'LSMinimumSystemVersion', '-string', '10.7.0', os.path.join(bundle_dir, 'Contents', 'Info.plist')]
|
||||
commands.append(args)
|
||||
|
||||
if len(sys.argv) <= 2:
|
||||
print 'Will run %d commands:' % len(commands)
|
||||
for command in commands:
|
||||
|
|
|
@ -579,7 +579,9 @@
|
|||
</dict>
|
||||
</dict>
|
||||
<key>INSTALLATION TYPE</key>
|
||||
<integer>0</integer>
|
||||
<integer>1</integer>
|
||||
<key>MODE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>INSTALLATION_STEPS</key>
|
||||
<array>
|
||||
|
@ -699,7 +701,7 @@
|
|||
<key>BUILD_PATH</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>../install/.</string>
|
||||
<string>@CMAKE_INSTALL_PREFIX@/.</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
|
@ -1,12 +1,16 @@
|
|||
#!/bin/sh
|
||||
|
||||
osascript << EOF
|
||||
# Check if Finder is running (for systems with Finder disabled)
|
||||
finder_status=`ps aux | grep "/System/Library/CoreServices/Finder.app/Contents/MacOS/Finder" | grep -v "grep"`
|
||||
if ! [ "$finder_status" == "" ] ; then # Finder is running
|
||||
osascript << EOF
|
||||
tell application "Finder"
|
||||
activate
|
||||
select the last Finder window
|
||||
reveal POSIX file "/Applications/@APPLICATION_EXECUTABLE@.app"
|
||||
end tell
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Always enable the new 10.10 finder plugin if available
|
||||
if [ -x "$(command -v pluginkit)" ]; then
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<file>resources/lock-https.png</file>
|
||||
<file>resources/lock-https@2x.png</file>
|
||||
<file>resources/account.png</file>
|
||||
<file>resources/more.png</file>
|
||||
<file>resources/more.svg</file>
|
||||
<file>resources/delete.png</file>
|
||||
<file>resources/bell.png</file>
|
||||
</qresource>
|
||||
|
|
|
@ -27,11 +27,9 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>@MIRALL_VERSION_STRING@</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(C) 2014-2016 @APPLICATION_VENDOR@</string>
|
||||
<string>(C) 2014-2018 @APPLICATION_VENDOR@</string>
|
||||
<key>SUShowReleaseNotes</key>
|
||||
<false/>
|
||||
<key>LSMinimumBundleVersion</key>
|
||||
<string>10.7.0</string>
|
||||
<key>SUPublicDSAKeyFile</key>
|
||||
<string>dsa_pub.pem</string>
|
||||
</dict>
|
||||
|
|
|
@ -7,10 +7,11 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|||
|
||||
# Fix sqlite compilation on macOS
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types-discards-qualifiers")
|
||||
# Fix sqlite compilation on MinGW
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-discarded-qualifiers")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
# Fix sqlite compilation on MinGW
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-discarded-qualifiers")
|
||||
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion
|
||||
OUTPUT_VARIABLE GCC_VERSION)
|
||||
if(GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8)
|
||||
|
|
|
@ -250,9 +250,14 @@ Some system wide file patterns that are used to exclude or ignore files are incl
|
|||
|
||||
By default, the Nextcloud Client ignores the following files:
|
||||
|
||||
* Files matched by one of the patterns defined in the Ignored Files Editor
|
||||
* Files containing characters that do not work on certain file systems ``(`\, /, :, ?, *, ", >, <, |`)``.
|
||||
* Files starting with ``._sync_xxxxxxx.db`` and the old format ``.csync_journal.db``, as these files are reserved for journalling.
|
||||
* Files matched by one of the patterns defined in the Ignored Files Editor.
|
||||
* Files starting with ``._sync_*.db*``, ``.sync_*.db*``, ``.csync_journal.db*``, ``.owncloudsync.log*``, as these files are reserved for journalling.
|
||||
* Files with a name longer than 254 characters.
|
||||
* The file ``Desktop.ini`` in the root of a synced folder.
|
||||
* Files matching the pattern ``*_conflict-*`` unless conflict file uploading is enabled.
|
||||
* Windows only: Files containing characters that do not work on typical Windows filesystems ``(`\, /, :, ?, *, ", >, <, |`)``.
|
||||
* Windows only: Files with a trailing space or dot.
|
||||
* Windows only: Filenames that are reserved on Windows.
|
||||
|
||||
If a pattern selected using a checkbox in the `ignoredFilesEditor-label` (or if
|
||||
a line in the exclude file starts with the character ``]`` directly followed by
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
The Automatic Updater
|
||||
=====================
|
||||
|
||||
The Automatic Updater ensures that you always have the
|
||||
The Automatic Updater ensures that you always have the
|
||||
latest features and bug fixes for your Nextcloud synchronization client.
|
||||
|
||||
The Automatic Updater updates only on Mac OS X and Windows computers; Linux
|
||||
users only need to use their normal package managers. However, on Linux systems
|
||||
the Updater will check for updates and notify you when a new version is
|
||||
The Automatic Updater updates only on macOS and Windows computers; Linux
|
||||
users only need to use their normal package managers. However, on Linux systems
|
||||
the Updater will check for updates and notify you when a new version is
|
||||
available.
|
||||
|
||||
Basic Workflow
|
||||
--------------
|
||||
|
||||
The following sections describe how to use the Automatic Updater on different
|
||||
The following sections describe how to use the Automatic Updater on different
|
||||
operating systems.
|
||||
|
||||
Windows
|
||||
|
@ -29,20 +29,20 @@ itself. Should the silent update fail, the client offers a manual download.
|
|||
|
||||
.. note:: Administrative privileges are required to perform the update.
|
||||
|
||||
Mac OS X
|
||||
^^^^^^^^
|
||||
macOS
|
||||
^^^^^
|
||||
|
||||
If a new update is available, the Nextcloud client initializes a pop-up dialog
|
||||
to alert you of the update and requesting that you update to the latest
|
||||
version. Due to their use of the Sparkle frameworks, this is the default
|
||||
process for Mac OS X applications.
|
||||
process for macOS applications.
|
||||
|
||||
Linux
|
||||
^^^^^
|
||||
|
||||
Linux distributions provide their own update tools, so Nextcloud clients that use
|
||||
the Linux operating system do not perform any updates on their own. The client
|
||||
will inform you (``Settings -> General -> Updates``) when an update is
|
||||
the Linux operating system do not perform any updates on their own. The client
|
||||
will inform you (``Settings -> General -> Updates``) when an update is
|
||||
available.
|
||||
|
||||
Preventing Automatic Updates
|
||||
|
@ -57,14 +57,14 @@ auto-update mechanism for different operating systems.
|
|||
Preventing Automatic Updates in Windows Environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Users may disable automatic updates by adding this line to the [General]
|
||||
Users may disable automatic updates by adding this line to the [General]
|
||||
section of their ``nextcloud.cfg`` files::
|
||||
|
||||
skipUpdateCheck=true
|
||||
|
||||
Windows administrators have more options for preventing automatic updates in
|
||||
Windows environments by using one of two methods. The first method allows users
|
||||
to override the automatic update check mechanism, whereas the second method
|
||||
Windows administrators have more options for preventing automatic updates in
|
||||
Windows environments by using one of two methods. The first method allows users
|
||||
to override the automatic update check mechanism, whereas the second method
|
||||
prevents any manual overrides.
|
||||
|
||||
To prevent automatic updates, but allow manual overrides:
|
||||
|
@ -82,7 +82,7 @@ To manually override this key, use the same value in ``HKEY_CURRENT_USER``.
|
|||
|
||||
To prevent automatic updates and disallow manual overrides:
|
||||
|
||||
.. note:: This is the preferred method of controlling the updater behavior using
|
||||
.. note:: This is the preferred method of controlling the updater behavior using
|
||||
Group Policies.
|
||||
|
||||
1. Edit this Registry key:
|
||||
|
@ -96,10 +96,10 @@ To prevent automatic updates and disallow manual overrides:
|
|||
.. note:: branded clients have different key names
|
||||
|
||||
|
||||
Preventing Automatic Updates in Mac OS X Environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Preventing Automatic Updates in macOS Environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can disable the automatic update mechanism, in the Mac OS X operating system,
|
||||
You can disable the automatic update mechanism, in the macOS operating system,
|
||||
by copying the file
|
||||
``nextcloud.app/Contents/Resources/deny_autoupdate_com.nextcloud.desktopclient.plist``
|
||||
to ``/Library/Preferences/com.nextcloud.desktopclient.plist``.
|
||||
|
|
|
@ -16,14 +16,14 @@ These instructions are updated to work with version |version| of the Nextcloud C
|
|||
Getting Source Code
|
||||
-------------------
|
||||
|
||||
The :ref:`generic-build-instructions` pull the latest code directly from
|
||||
GitHub, and work on Linux, Mac OS X, and Windows.
|
||||
The :ref:`generic-build-instructions` pull the latest code directly from
|
||||
GitHub, and work on Linux, macOS, and Windows.
|
||||
|
||||
Mac OS X
|
||||
--------
|
||||
macOS
|
||||
-----
|
||||
|
||||
In addition to needing XCode (along with the command line tools), developing in
|
||||
the Mac OS X environment requires extra dependencies. You can install these
|
||||
the macOS environment requires extra dependencies. You can install these
|
||||
dependencies through MacPorts_ or Homebrew_. These dependencies are required
|
||||
only on the build machine, because non-standard libs are deployed in the app
|
||||
bundle.
|
||||
|
@ -60,14 +60,14 @@ To set up your build environment for development using HomeBrew_:
|
|||
Where ``x.y`` is the current version of Qt 5 that brew has installed
|
||||
on your machine.
|
||||
8. Install qtkeychain from here: git clone https://github.com/frankosterfeld/qtkeychain.git
|
||||
make sure you make the same install prefix as later while building the client e.g. -
|
||||
make sure you make the same install prefix as later while building the client e.g. -
|
||||
``DCMAKE_INSTALL_PREFIX=/Path/to/client-install``
|
||||
|
||||
9. For compilation of the client, follow the :ref:`generic-build-instructions`.
|
||||
|
||||
10. Install the Packages_ package creation tool.
|
||||
|
||||
11. In the build directory, run ``admin/osx/create_mac.sh <build_dir> <install_dir>``.
|
||||
11. In the build directory, run ``admin/osx/create_mac.sh <build_dir> <install_dir>``.
|
||||
If you have a developer signing certificate, you can specify
|
||||
its Common Name as a third parameter (use quotes) to have the package
|
||||
signed automatically.
|
||||
|
@ -137,7 +137,7 @@ is **currently only officially supported on openSUSE**, by using the MinGW cross
|
|||
You can set up any currently supported version of openSUSE in a virtual machine if you do not
|
||||
have it installed already.
|
||||
|
||||
In order to make setup simple, you can use the provided Dockerfile to build your own image.
|
||||
In order to make setup simple, you can use the provided Dockerfile to build your own image.
|
||||
|
||||
1. Assuming you are in the root of the Nextcloud Client's source tree, you can
|
||||
build an image from this Dockerfile like this::
|
||||
|
@ -209,24 +209,24 @@ To build the most up-to-date version of the client:
|
|||
3. Configure the client build::
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE="Debug" ..
|
||||
|
||||
|
||||
.. note:: You must use absolute paths for the ``include`` and ``library``
|
||||
directories.
|
||||
|
||||
.. note:: On Mac OS X, you need to specify ``-DCMAKE_INSTALL_PREFIX=target``,
|
||||
.. note:: On macOS, you need to specify ``-DCMAKE_INSTALL_PREFIX=target``,
|
||||
where ``target`` is a private location, i.e. in parallel to your build
|
||||
dir by specifying ``../install``.
|
||||
|
||||
|
||||
.. note:: qtkeychain must be compiled with the same prefix e.g ``CMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ .``
|
||||
|
||||
|
||||
.. note:: Example:: ``cmake -DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 -DCMAKE_INSTALL_PREFIX=/Users/path/to/client/install/ -DNO_SHIBBOLETH=1``
|
||||
|
||||
|
||||
4. Call ``make``.
|
||||
|
||||
The Nextcloud binary will appear in the ``bin`` directory.
|
||||
|
||||
5. (Optional) Call ``make install`` to install the client to the
|
||||
``/usr/local/bin`` directory.
|
||||
|
||||
5. (Optional) Call ``make install`` to install the client to the
|
||||
``/usr/local/bin`` directory.
|
||||
|
||||
The following are known cmake parameters:
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ On Linux distributions:
|
|||
On Microsoft Windows systems:
|
||||
``%APPDATA%\Nextcloud\nextcloud.cfg``
|
||||
|
||||
On MAC OS X systems:
|
||||
On macOS systems:
|
||||
``$HOME/Library/Preferences/Nextcloud/nextcloud.cfg``
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ Glossary
|
|||
Nextcloud Sync Client
|
||||
Nextcloud Client
|
||||
Name of the official Nextcloud syncing client for desktop, which runs on
|
||||
Windows, Mac OS X and Linux. It uses the CSync sync engine for
|
||||
Windows, macOS and Linux. It uses the CSync sync engine for
|
||||
synchronization with the Nextcloud server.
|
||||
|
||||
Nextcloud Server
|
||||
|
|
|
@ -2,69 +2,78 @@
|
|||
Installing the Desktop Synchronization Client
|
||||
=============================================
|
||||
|
||||
You can download the latest version of the Nextcloud Desktop Synchronization
|
||||
Client from the `Nextcloud download page`_.
|
||||
There are clients for Linux, Mac OS X, and Microsoft Windows.
|
||||
You can download the latest version of the Nextcloud Desktop Synchronization
|
||||
Client from the `Nextcloud download page`_.
|
||||
There are clients for Linux, macOs, and Microsoft Windows.
|
||||
|
||||
Installation on Mac OS X and Windows is the same as for any software
|
||||
application: download the program and then double-click it to launch the
|
||||
installation, and then follow the installation wizard. After it is installed and
|
||||
configured the sync client will automatically keep itself updated; see
|
||||
Installation on Mac OS X and Windows is the same as for any software
|
||||
application: download the program and then double-click it to launch the
|
||||
installation, and then follow the installation wizard. After it is installed and
|
||||
configured the sync client will automatically keep itself updated; see
|
||||
:doc:`autoupdate` for more information.
|
||||
|
||||
Linux users must follow the instructions on the download page to add the
|
||||
appropriate repository for their Linux distribution, install the signing key,
|
||||
and then use their package managers to install the desktop sync client. Linux
|
||||
users will also update their sync clients via package manager, and the client
|
||||
will display a notification when an update is available.
|
||||
Linux users must follow the instructions on the download page to add the
|
||||
appropriate repository for their Linux distribution, install the signing key,
|
||||
and then use their package managers to install the desktop sync client. Linux
|
||||
users will also update their sync clients via package manager, and the client
|
||||
will display a notification when an update is available.
|
||||
|
||||
Linux users must also have a password manager enabled, such as GNOME Keyring or
|
||||
KWallet, so that the sync client can login automatically.
|
||||
|
||||
You will also find links to source code archives and older versions on the
|
||||
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**)
|
||||
- macOS 10.7+ (**64-bit only**)
|
||||
- CentOS 6 & 7 (64-bit only)
|
||||
- Debian 8.0 & 9.0
|
||||
- Fedora 25 & 26 & 27
|
||||
- Ubuntu 16.04 & 17.04 & 17.10
|
||||
- openSUSE Leap 42.2 & 42.3
|
||||
|
||||
.. note::
|
||||
For Linux distributions, we support, if technically feasible, the latest 2 versions per platform and the previous `LTS`_.
|
||||
>>>>>>> b2da03441... update supported linux platforms
|
||||
|
||||
Installation Wizard
|
||||
-------------------
|
||||
|
||||
The installation wizard takes you step-by-step through configuration options and
|
||||
The installation wizard takes you step-by-step through configuration options and
|
||||
account setup. First you need to enter the URL of your Nextcloud server.
|
||||
|
||||
.. image:: images/client-1.png
|
||||
:alt: form for entering Nextcloud server URL
|
||||
|
||||
|
||||
Enter your Nextcloud login on the next screen.
|
||||
|
||||
.. image:: images/client-2.png
|
||||
:alt: form for entering your Nextcloud login
|
||||
|
||||
On the Local Folder Option screen you may sync
|
||||
all of your files on the Nextcloud server, or select individual folders. The
|
||||
default local sync folder is ``Nextcloud``, in your home directory. You may
|
||||
On the Local Folder Option screen you may sync
|
||||
all of your files on the Nextcloud server, or select individual folders. The
|
||||
default local sync folder is ``Nextcloud``, in your home directory. You may
|
||||
change this as well.
|
||||
|
||||
.. image:: images/client-3.png
|
||||
:alt: Select which remote folders to sync, and which local folder to store
|
||||
:alt: Select which remote folders to sync, and which local folder to store
|
||||
them in.
|
||||
|
||||
When you have completed selecting your sync folders, click the Connect button
|
||||
at the bottom right. The client will attempt to connect to your Nextcloud
|
||||
server, and when it is successful you'll see two buttons: one to connect to
|
||||
your Nextcloud Web GUI, and one to open your local folder. It will also start
|
||||
|
||||
When you have completed selecting your sync folders, click the Connect button
|
||||
at the bottom right. The client will attempt to connect to your Nextcloud
|
||||
server, and when it is successful you'll see two buttons: one to connect to
|
||||
your Nextcloud Web GUI, and one to open your local folder. It will also start
|
||||
synchronizing your files.
|
||||
|
||||
.. image:: images/client-4.png
|
||||
:alt: A successful server connection, showing a button to connect to your
|
||||
:alt: A successful server connection, showing a button to connect to your
|
||||
Web GUI, and one to open your local Nextcloud folder
|
||||
|
||||
Click the Finish button, and you're all done.
|
||||
Click the Finish button, and you're all done.
|
||||
|
||||
.. Links
|
||||
|
||||
|
||||
.. _Nextcloud download page: https://nextcloud.com/download/#install-clients
|
||||
|
|
|
@ -2,27 +2,26 @@
|
|||
Introduction
|
||||
============
|
||||
|
||||
Available for Windows, Mac OS X, and various Linux distributions, the Nextcloud
|
||||
Available for Windows, macOS, and various Linux distributions, the Nextcloud
|
||||
Desktop Sync client enables you to:
|
||||
|
||||
- Specify one or more directories on your computer that you want to synchronize
|
||||
to the Nextcloud server.
|
||||
- Always have the latest files synchronized, wherever they are located.
|
||||
|
||||
Your files are always automatically synchronized between your Nextcloud server
|
||||
Your files are always automatically synchronized between your Nextcloud server
|
||||
and local PC.
|
||||
|
||||
Improvements and New Features
|
||||
-----------------------------
|
||||
|
||||
The |version| release of the Nextcloud desktop sync client has many new features and
|
||||
The |version| release of the Nextcloud desktop sync client has many new features and
|
||||
improvements.
|
||||
|
||||
|
||||
* Show server notifications on the client
|
||||
* Improved sync speed
|
||||
* Improved handling of Win32 file locks and network files
|
||||
* Improved handling of Win32 file locks and network files
|
||||
* Improved user notifications about ignored files and conflicts
|
||||
* Add warnings for old server versions
|
||||
* Update of QtKeyChain to support Windows credential store
|
||||
* Packaging of dolphin overlay icon module for bleeding edge distributions
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@ Using the Synchronization Client
|
|||
|
||||
.. index:: navigating, usage
|
||||
|
||||
The Nextcloud Desktop Client remains in the background and is visible as an icon
|
||||
in the system tray (Windows, KDE), status bar (Mac OS X), or notification area
|
||||
The Nextcloud Desktop Client remains in the background and is visible as an icon
|
||||
in the system tray (Windows, KDE), status bar (macOS), or notification area
|
||||
(Linux).
|
||||
|
||||
.. figure:: images/icon.png
|
||||
:alt: Status icon, green circle and white checkmark
|
||||
:alt: Status icon, green circle and white checkmark
|
||||
|
||||
The status indicator uses icons to indicate the current status of your
|
||||
synchronization. The green circle with the white checkmark tells you that your
|
||||
The status indicator uses icons to indicate the current status of your
|
||||
synchronization. The green circle with the white checkmark tells you that your
|
||||
synchronization is current and you are connected to your Nextcloud server.
|
||||
|
||||
.. figure:: images/icon-syncing.png
|
||||
|
@ -21,35 +21,35 @@ synchronization is current and you are connected to your Nextcloud server.
|
|||
The blue icon with the white semi-circles means synchronization is in progress.
|
||||
|
||||
.. figure:: images/icon-paused.png
|
||||
:alt: Status icon, yellow circle and vertical parallel
|
||||
:alt: Status icon, yellow circle and vertical parallel
|
||||
lines
|
||||
|
||||
The yellow icon with the parallel lines tells you your synchronization
|
||||
The yellow icon with the parallel lines tells you your synchronization
|
||||
has been paused. (Most likely by you.)
|
||||
|
||||
.. figure:: images/icon-offline.png
|
||||
:alt: Status icon, gray circle and three horizontal
|
||||
:alt: Status icon, gray circle and three horizontal
|
||||
white dots
|
||||
|
||||
The gray icon with three white dots means your sync client has lost its
|
||||
The gray icon with three white dots means your sync client has lost its
|
||||
connection with your Nextcloud server.
|
||||
|
||||
.. figure:: images/icon-information.png
|
||||
:alt: Status icon, sign "!" in yellow circle
|
||||
|
||||
When you see a yellow circle with the sign "!" that is the informational icon,
|
||||
When you see a yellow circle with the sign "!" that is the informational icon,
|
||||
so you should click it to see what it has to tell you.
|
||||
|
||||
.. figure:: images/icon-error.png
|
||||
:alt: Status icon, red circle and white x
|
||||
|
||||
The red circle with the white "x" indicates a configuration error, such as an
|
||||
The red circle with the white "x" indicates a configuration error, such as an
|
||||
incorrect login or server URL.
|
||||
|
||||
Systray Icon
|
||||
------------
|
||||
|
||||
A right-click on the systray icon opens a menu for quick access to multiple
|
||||
A right-click on the systray icon opens a menu for quick access to multiple
|
||||
operations.
|
||||
|
||||
.. figure:: images/menu.png
|
||||
|
@ -66,7 +66,7 @@ This menu provides the following options:
|
|||
* An option to log in or log out of all of your accounts at once
|
||||
* Quit Nextcloud, logging out and closing the client
|
||||
|
||||
A left-click on your systray icon opens the desktop client to the account
|
||||
A left-click on your systray icon opens the desktop client to the account
|
||||
settings window.
|
||||
|
||||
.. figure:: images/client-6.png
|
||||
|
@ -77,151 +77,151 @@ Configuring Nextcloud Account Settings
|
|||
|
||||
.. index:: account settings, user, password, Server URL
|
||||
|
||||
At the top of the window are tabs for each configured sync account, and three
|
||||
others for Activity, General and Network settings. On your account tabs you
|
||||
At the top of the window are tabs for each configured sync account, and three
|
||||
others for Activity, General and Network settings. On your account tabs you
|
||||
have the following features:
|
||||
|
||||
* Connection status, showing which Nextcloud server you are connected to, and
|
||||
* Connection status, showing which Nextcloud server you are connected to, and
|
||||
your Nextcloud username.
|
||||
* An **Account** button, which contains a dropdown menu with **Add New**,
|
||||
* An **Account** button, which contains a dropdown menu with **Add New**,
|
||||
**Log Out**, and **Remove**.
|
||||
* Used and available space on the server.
|
||||
* Current synchronization status.
|
||||
* **Add Folder Sync Connection** button.
|
||||
|
||||
The little button with three dots (the overflow menu) that sits to the right of
|
||||
The little button with three dots (the overflow menu) that sits to the right of
|
||||
the sync status bar offers four additional options:
|
||||
|
||||
* Open Folder
|
||||
* Choose What to Sync (This appears only when your file tree is collapsed, and
|
||||
* Choose What to Sync (This appears only when your file tree is collapsed, and
|
||||
expands the file tree)
|
||||
* Pause Sync / Resume Sync
|
||||
* Remove folder sync connection
|
||||
|
||||
**Open Folder** opens your local Nextcloud sync folder.
|
||||
|
||||
**Pause Sync** pauses sync operations without making any changes to your
|
||||
account. It will continue to update file and folder lists, without
|
||||
downloading or updating files. To stop all sync activity use **Remove
|
||||
**Pause Sync** pauses sync operations without making any changes to your
|
||||
account. It will continue to update file and folder lists, without
|
||||
downloading or updating files. To stop all sync activity use **Remove
|
||||
Folder Sync Connection**.
|
||||
|
||||
.. figure:: images/client-7.png
|
||||
:alt: Extra options for sync operations
|
||||
|
||||
.. note:: Nextcloud does not preserve the mtime (modification time) of
|
||||
directories, though it does update the mtimes on files. See
|
||||
`Wrong folder date when syncing
|
||||
<https://github.com/owncloud/core/issues/7009>`_ for discussion of this.
|
||||
|
||||
|
||||
.. note:: Nextcloud does not preserve the mtime (modification time) of
|
||||
directories, though it does update the mtimes on files. See
|
||||
`Wrong folder date when syncing
|
||||
<https://github.com/owncloud/core/issues/7009>`_ for discussion of this.
|
||||
|
||||
Adding New Accounts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You may configure multiple Nextcloud accounts in your desktop sync client. Simply
|
||||
click the **Account** > **Add New** button on any account tab to add a new
|
||||
account, and then follow the account creation wizard. The new account will
|
||||
appear as a new tab in the settings dialog, where you can adjust its settings at
|
||||
any time. Use **Account** > **Remove** to delete accounts.
|
||||
You may configure multiple Nextcloud accounts in your desktop sync client. Simply
|
||||
click the **Account** > **Add New** button on any account tab to add a new
|
||||
account, and then follow the account creation wizard. The new account will
|
||||
appear as a new tab in the settings dialog, where you can adjust its settings at
|
||||
any time. Use **Account** > **Remove** to delete accounts.
|
||||
|
||||
File Manager Overlay Icons
|
||||
--------------------------
|
||||
|
||||
The Nextcloud sync client provides overlay icons, in addition to the normal file
|
||||
type icons, for your system file manager (Explorer on Windows, Finder on Mac and
|
||||
The Nextcloud sync client provides overlay icons, in addition to the normal file
|
||||
type icons, for your system file manager (Explorer on Windows, Finder on Mac and
|
||||
Nautilus on Linux) to indicate the sync status of your Nextcloud files.
|
||||
|
||||
The overlay icons are similar to the systray icons introduced above. They
|
||||
behave differently on files and directories according to sync status
|
||||
and errors.
|
||||
The overlay icons are similar to the systray icons introduced above. They
|
||||
behave differently on files and directories according to sync status
|
||||
and errors.
|
||||
|
||||
The overlay icon of an individual file indicates its current sync state. If the
|
||||
file is in sync with the server version, it displays a green checkmark.
|
||||
|
||||
If the file is ignored from syncing, for example because it is on your
|
||||
If the file is ignored from syncing, for example because it is on your
|
||||
exclude list, or because it is a symbolic link, it displays a warning icon.
|
||||
|
||||
If there is a sync error, or the file is blacklisted, it displays an
|
||||
If there is a sync error, or the file is blacklisted, it displays an
|
||||
eye-catching red X.
|
||||
|
||||
If the file is waiting to be synced, or is currently syncing, the overlay
|
||||
If the file is waiting to be synced, or is currently syncing, the overlay
|
||||
icon displays a blue cycling icon.
|
||||
|
||||
When the client is offline, no icons are shown to reflect that the
|
||||
folder is currently out of sync and no changes are synced to the server.
|
||||
When the client is offline, no icons are shown to reflect that the
|
||||
folder is currently out of sync and no changes are synced to the server.
|
||||
|
||||
The overlay icon of a synced directory indicates the status of the files in the
|
||||
directory. If there are any sync errors, the directory is marked with a warning
|
||||
The overlay icon of a synced directory indicates the status of the files in the
|
||||
directory. If there are any sync errors, the directory is marked with a warning
|
||||
icon.
|
||||
|
||||
If a directory includes ignored files that are marked with warning icons
|
||||
If a directory includes ignored files that are marked with warning icons
|
||||
that does not change the status of the parent directories.
|
||||
|
||||
Sharing From Your Desktop
|
||||
-------------------------
|
||||
|
||||
The Nextcloud desktop sync client integrates with your file manager: Finder on
|
||||
Mac OS X, Explorer on Windows, and Nautilus on Linux. (Linux users must install
|
||||
the ``Nextcloud-client-nautilus`` plugin.) You can create share links, and share
|
||||
The Nextcloud desktop sync client integrates with your file manager: Finder on
|
||||
macOS, Explorer on Windows, and Nautilus on Linux. (Linux users must install
|
||||
the ``Nextcloud-client-nautilus`` plugin.) You can create share links, and share
|
||||
with internal Nextcloud users the same way as in your Nextcloud Web interface.
|
||||
|
||||
.. figure:: images/mac-share.png
|
||||
:alt: Sync client integration in Windows Explorer.
|
||||
|
||||
Right-click your systray icon, hover over the account you want to use, and
|
||||
left-click "Open folder [folder name] to quickly enter your local Nextcloud
|
||||
folder. Right-click the file or folder you want to share to expose the share
|
||||
|
||||
Right-click your systray icon, hover over the account you want to use, and
|
||||
left-click "Open folder [folder name] to quickly enter your local Nextcloud
|
||||
folder. Right-click the file or folder you want to share to expose the share
|
||||
dialog, and click **Share with Nextcloud**.
|
||||
|
||||
.. figure:: images/share-1.png
|
||||
:alt: Sharing from Windows Explorer.
|
||||
|
||||
|
||||
The share dialog has all the same options as your Nextcloud Web interface.
|
||||
|
||||
.. figure:: images/share-2.png
|
||||
:alt: Share dialog in Windows Explorer.
|
||||
|
||||
Use **Share with Nextcloud** to see who you have shared with, and to modify
|
||||
their permissions, or to delete the share.
|
||||
|
||||
Use **Share with Nextcloud** to see who you have shared with, and to modify
|
||||
their permissions, or to delete the share.
|
||||
|
||||
Activity Window
|
||||
---------------
|
||||
|
||||
The Activity window contains the log of your recent activities, organized over
|
||||
three tabs: **Server Activities**, which includes new shares and files
|
||||
downloaded and deleted, **Sync Protocol**, which displays local activities such
|
||||
as which local folders your files went into, and **Not Synced** shows errors
|
||||
The Activity window contains the log of your recent activities, organized over
|
||||
three tabs: **Server Activities**, which includes new shares and files
|
||||
downloaded and deleted, **Sync Protocol**, which displays local activities such
|
||||
as which local folders your files went into, and **Not Synced** shows errors
|
||||
such as files not synced. Double clicking an entry pointing to an existing
|
||||
file in **Server Activities** or **Sync Protocol** will open the folder containing
|
||||
the file and highlight it.
|
||||
|
||||
.. figure:: images/client-8.png
|
||||
:alt: Activity windows logs all server and client activities.
|
||||
|
||||
|
||||
Server Notifications
|
||||
--------------------
|
||||
|
||||
Starting with version 2.2.0, the client will display notifications from your
|
||||
Nextcloud server that require manual interaction by you. For example, when a
|
||||
user on a remote Nextcloud creates a new Federated share for you, you can accept
|
||||
Starting with version 2.2.0, the client will display notifications from your
|
||||
Nextcloud server that require manual interaction by you. For example, when a
|
||||
user on a remote Nextcloud creates a new Federated share for you, you can accept
|
||||
it from your desktop client.
|
||||
|
||||
The desktop client automatically checks for available notifications
|
||||
automatically on a regular basis. Notifications are displayed in the Server
|
||||
Activity tab, and if you have **Show Desktop Notifications** enabled (General
|
||||
The desktop client automatically checks for available notifications
|
||||
automatically on a regular basis. Notifications are displayed in the Server
|
||||
Activity tab, and if you have **Show Desktop Notifications** enabled (General
|
||||
tab) you'll also see a systray notification.
|
||||
|
||||
.. figure:: images/client-12.png
|
||||
:alt: Activity window with notification.
|
||||
|
||||
This also displays notifications sent to users by the Nextcloud admin via the
|
||||
This also displays notifications sent to users by the Nextcloud admin via the
|
||||
Announcements app.
|
||||
|
||||
General Window
|
||||
--------------
|
||||
|
||||
The General window has configuration options such as **Launch on System
|
||||
Startup**, **Use Monochrome Icons**, and **Show Desktop Notifications**. This
|
||||
is where you will find the **Edit Ignored Files** button, to launch the ignored
|
||||
files editor, and **Ask confirmation before downloading
|
||||
The General window has configuration options such as **Launch on System
|
||||
Startup**, **Use Monochrome Icons**, and **Show Desktop Notifications**. This
|
||||
is where you will find the **Edit Ignored Files** button, to launch the ignored
|
||||
files editor, and **Ask confirmation before downloading
|
||||
folders larger than [folder size]**.
|
||||
|
||||
.. figure:: images/client-9.png
|
||||
|
@ -232,7 +232,7 @@ Using the Network Window
|
|||
|
||||
.. index:: proxy settings, SOCKS, bandwith, throttling, limiting
|
||||
|
||||
The Network settings window enables you to define network proxy settings, and
|
||||
The Network settings window enables you to define network proxy settings, and
|
||||
also to limit download and upload bandwidth.
|
||||
|
||||
.. figure:: images/settings_network.png
|
||||
|
@ -244,20 +244,20 @@ Using the Ignored Files Editor
|
|||
|
||||
.. index:: ignored files, exclude files, pattern
|
||||
|
||||
You might have some local files or directories that you do not want to backup
|
||||
You might have some local files or directories that you do not want to backup
|
||||
and store on the server. To identify and exclude these files or directories, you
|
||||
can use the *Ignored Files Editor* (General tab.)
|
||||
|
||||
.. figure:: images/ignored_files_editor.png
|
||||
|
||||
For your convenience, the editor is pre-populated with a default list of
|
||||
typical
|
||||
ignore patterns. These patterns are contained in a system file (typically
|
||||
``sync-exclude.lst``) located in the Nextcloud Client application directory. You
|
||||
cannot modify these pre-populated patterns directly from the editor. However,
|
||||
if
|
||||
necessary, you can hover over any pattern in the list to show the path and
|
||||
filename associated with that pattern, locate the file, and edit the
|
||||
For your convenience, the editor is pre-populated with a default list of
|
||||
typical
|
||||
ignore patterns. These patterns are contained in a system file (typically
|
||||
``sync-exclude.lst``) located in the Nextcloud Client application directory. You
|
||||
cannot modify these pre-populated patterns directly from the editor. However,
|
||||
if
|
||||
necessary, you can hover over any pattern in the list to show the path and
|
||||
filename associated with that pattern, locate the file, and edit the
|
||||
``sync-exclude.lst`` file.
|
||||
|
||||
.. note:: Modifying the global exclude definition file might render the client
|
||||
|
@ -267,14 +267,14 @@ Each line in the editor contains an ignore pattern string. When creating custom
|
|||
patterns, in addition to being able to use normal characters to define an
|
||||
ignore pattern, you can use wildcards characters for matching values. As an
|
||||
example, you can use an asterisk (``*``) to identify an arbitrary number of
|
||||
characters or a question mark (``?``) to identify a single character.
|
||||
characters or a question mark (``?``) to identify a single character.
|
||||
|
||||
Patterns that end with a slash character (``/``) are applied to only directory
|
||||
components of the path being checked.
|
||||
|
||||
.. note:: Custom entries are currently not validated for syntactical
|
||||
correctness by the editor, so you will not see any warnings for bad
|
||||
syntax. If your synchronization does not work as you expected, check your
|
||||
syntax. If your synchronization does not work as you expected, check your
|
||||
syntax.
|
||||
|
||||
Each pattern string in the list is preceded by a checkbox. When the check box
|
||||
|
@ -288,8 +288,8 @@ this list:
|
|||
- The Nextcloud Client always excludes files containing characters that cannot
|
||||
be synchronized to other file systems.
|
||||
|
||||
- Files are removed that cause individual errors three times during a
|
||||
synchronization. However, the client provides the option of retrying a
|
||||
- Files are removed that cause individual errors three times during a
|
||||
synchronization. However, the client provides the option of retrying a
|
||||
synchronization three additional times on files that produce errors.
|
||||
|
||||
For more detailed information see :ref:`ignored-files-label`.
|
||||
|
|
|
@ -4,7 +4,7 @@ Appendix C: Troubleshooting
|
|||
The following two general issues can result in failed synchronization:
|
||||
|
||||
- The server setup is incorrect.
|
||||
- The client contains a bug.
|
||||
- The client contains a bug.
|
||||
|
||||
When reporting bugs, it is helpful if you first determine what part of the
|
||||
system is causing the issue.
|
||||
|
@ -16,7 +16,7 @@ Identifying Basic Functionality Problems
|
|||
The first step in troubleshooting synchronization issues is to verify that
|
||||
you can log on to the Nextcloud web application. To verify connectivity to the
|
||||
Nextcloud server try logging in via your Web browser.
|
||||
|
||||
|
||||
If you are not prompted for your username and password, or if a red warning
|
||||
box appears on the page, your server setup requires modification. Please verify
|
||||
that your server installation is working correctly.
|
||||
|
@ -30,7 +30,7 @@ Identifying Basic Functionality Problems
|
|||
Verify that you can log on to Nextcloud's WebDAV server. To verify connectivity
|
||||
with the Nextcloud WebDAV server:
|
||||
|
||||
- Open a browser window and enter the address to the Nextcloud WebDAV server.
|
||||
- Open a browser window and enter the address to the Nextcloud WebDAV server.
|
||||
|
||||
For example, if your Nextcloud instance is installed at
|
||||
``http://yourserver.com/nextcloud``, your WebDAV server address is
|
||||
|
@ -40,29 +40,29 @@ Identifying Basic Functionality Problems
|
|||
correct credentials, authentication fails, please ensure that your
|
||||
authentication backend is configured properly.
|
||||
|
||||
:Use a WebDAV command line tool to test:
|
||||
:Use a WebDAV command line tool to test:
|
||||
A more sophisticated test method for troubleshooting synchronization issues
|
||||
is to use a WebDAV command line client and log into the Nextcloud WebDAV server.
|
||||
One such command line client -- called ``cadaver`` -- is available for Linux
|
||||
distributions. You can use this application to further verify that the WebDAV
|
||||
server is running properly using PROPFIND calls.
|
||||
server is running properly using PROPFIND calls.
|
||||
|
||||
As an example, after installing the ``cadaver`` app, you can issue the
|
||||
``propget`` command to obtain various properties pertaining to the current
|
||||
directory and also verify WebDAV server connection.
|
||||
|
||||
|
||||
"CSync unknown error"
|
||||
---------------------
|
||||
|
||||
If you see this error message stop your client, delete the
|
||||
``._sync_xxxxxxx.db`` file, and then restart your client.
|
||||
There is a hidden ``._sync_xxxxxxx.db`` file inside the folder of every account
|
||||
configured on your client.
|
||||
configured on your client.
|
||||
|
||||
.. NOTE::
|
||||
Please note that this will also erase some of your settings about which
|
||||
files to download.
|
||||
|
||||
|
||||
See https://github.com/owncloud/client/issues/5226 for more discussion of this
|
||||
issue.
|
||||
|
||||
|
@ -145,7 +145,7 @@ mentioned above to save the log to a file.
|
|||
restarting the client using the following command:
|
||||
|
||||
* Windows: ``C:\Program Files (x86)\Nextcloud\nextcloud.exe --logwindow``
|
||||
* Mac OS X: ``/Applications/nextcloud.app/Contents/MacOS/nextcloud --logwindow``
|
||||
* macOS: ``/Applications/nextcloud.app/Contents/MacOS/nextcloud --logwindow``
|
||||
* Linux: ``nextcloud --logwindow``
|
||||
|
||||
Saving Files Directly
|
||||
|
@ -184,7 +184,7 @@ The Nextcloud server also maintains an Nextcloud specific log file. This log fil
|
|||
must be enabled through the Nextcloud Administration page. On that page, you can
|
||||
adjust the log level. We recommend that when setting the log file level that
|
||||
you set it to a verbose level like ``Debug`` or ``Info``.
|
||||
|
||||
|
||||
You can view the server log file using the web interface or you can open it
|
||||
directly from the file system in the Nextcloud server data directory.
|
||||
|
||||
|
@ -202,21 +202,21 @@ Nextcloud-related problems. For Apache on Linux, the error logs are typically
|
|||
located in the ``/var/log/apache2`` directory. Some helpful files include the
|
||||
following:
|
||||
|
||||
- ``error_log`` -- Maintains errors associated with PHP code.
|
||||
- ``error_log`` -- Maintains errors associated with PHP code.
|
||||
- ``access_log`` -- Typically records all requests handled by the server; very
|
||||
useful as a debugging tool because the log line contains information specific
|
||||
to each request and its result.
|
||||
|
||||
|
||||
You can find more information about Apache logging at
|
||||
``http://httpd.apache.org/docs/current/logs.html``.
|
||||
|
||||
Core Dumps
|
||||
----------
|
||||
|
||||
On Mac OS X and Linux systems, and in the unlikely event the client software
|
||||
On macOS and Linux systems, and in the unlikely event the client software
|
||||
crashes, the client is able to write a core dump file. Obtaining a core dump
|
||||
file can assist Nextcloud Customer Support tremendously in the debugging
|
||||
process.
|
||||
process.
|
||||
|
||||
To enable the writing of core dump files, you must define the
|
||||
``OWNCLOUD_CORE_DUMP`` environment variable on the system.
|
||||
|
@ -228,7 +228,7 @@ OWNCLOUD_CORE_DUMP=1 nextcloud
|
|||
```
|
||||
|
||||
This command starts the client with core dumping enabled and saves the files in
|
||||
the current working directory.
|
||||
the current working directory.
|
||||
|
||||
.. note:: Core dump files can be fairly large. Before enabling core dumps on
|
||||
your system, ensure that you have enough disk space to accommodate these files.
|
||||
|
|
|
@ -8,7 +8,7 @@ Icon
|
|||
|
||||
The Nextcloud Client remains in the background and is visible
|
||||
as an icon in the system tray (Windows, KDE), status bar
|
||||
(MAC OS X), or notification area (Ubuntu).
|
||||
(macOS), or notification area (Ubuntu).
|
||||
|
||||
.. image:: images/icon.png
|
||||
|
||||
|
@ -17,7 +17,7 @@ Menu
|
|||
|
||||
.. image:: images/menu.png
|
||||
|
||||
A right click on the icon (left click on Ubuntu and Mac OS X)
|
||||
A right click on the icon (left click on Ubuntu and macOS)
|
||||
provides the following menu:
|
||||
|
||||
* ``Open Nextcloud in browser``: Opens the Nextcloud web interface
|
||||
|
@ -124,7 +124,7 @@ The tab provides several useful options:
|
|||
* ``Show Desktop Nofications``: When checked, bubble notifications when
|
||||
a set of sync operations has been performed are provided.
|
||||
* ``Use Monochrome Icons``: Use less obtrusive icons. Especially useful
|
||||
on Mac OS X.
|
||||
on macOS.
|
||||
* ``About``: provides information about authors as well as build conditions.
|
||||
This information is valuable when submitting a support request.
|
||||
|
||||
|
@ -179,11 +179,11 @@ The Ignored Files Editor
|
|||
.. index:: ignored files, exclude files, pattern
|
||||
|
||||
Nextcloud Client has the ability to exclude files from the sync process.
|
||||
The ignored files editor allows editing of custom patterns for files or
|
||||
directories that should be excluded from the sync process.
|
||||
The ignored files editor allows editing of custom patterns for files or
|
||||
directories that should be excluded from the sync process.
|
||||
|
||||
There is a system wide list of default ignore patterns. These global defaults
|
||||
cannot be directly modified within the editor. Hovering with the mouse will
|
||||
There is a system wide list of default ignore patterns. These global defaults
|
||||
cannot be directly modified within the editor. Hovering with the mouse will
|
||||
reveal the location of the global exclude definition file.
|
||||
|
||||
.. image:: images/ignored_files_editor.png
|
||||
|
@ -206,9 +206,9 @@ which are matched by this pattern are fleeting metadata which the client will
|
|||
correctness by the editor, but might fail to load correctly.
|
||||
|
||||
In addition to this list, Nextcloud Client always excludes files with
|
||||
characters that cannot be synced to other file systems.
|
||||
characters that cannot be synced to other file systems.
|
||||
|
||||
With version 1.5.0 it also ignores files that caused individual errors
|
||||
With version 1.5.0 it also ignores files that caused individual errors
|
||||
while syncing for a three times. These are listed in the activity view.
|
||||
There also is a button to retry the sync for another three times.
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 122 B |
5
resources/more.svg
Normal file
5
resources/more.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<rect style="color:#000000" fill-opacity="0" height="97.986" width="163.31" y="-32.993" x="-62.897"/>
|
||||
<path d="m3 6c-1.1046 0-2 0.8954-2 2s0.8954 2 2 2 2-0.8954 2-2-0.8954-2-2-2zm5 0c-1.1046 0-2 0.8954-2 2s0.8954 2 2 2 2-0.8954 2-2-0.8954-2-2-2zm5 0c-1.105 0-2 0.8954-2 2s0.895 2 2 2 2-0.8954 2-2-0.895-2-2-2z" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 660 B |
|
@ -23,6 +23,7 @@
|
|||
NSMutableSet *_registeredDirectories;
|
||||
NSString *_shareMenuTitle;
|
||||
NSMutableDictionary *_strings;
|
||||
NSMutableArray *_menuItems;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
- (instancetype)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
|
||||
FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
|
||||
NSBundle *extBundle = [NSBundle bundleForClass:[self class]];
|
||||
// This was added to the bundle's Info.plist to get it from the build system
|
||||
|
@ -43,7 +43,7 @@
|
|||
[syncController setBadgeImage:sync label:@"Synchronizing" forBadgeIdentifier:@"NEW+SWM"];
|
||||
[syncController setBadgeImage:warning label:@"Ignored" forBadgeIdentifier:@"IGNORE+SWM"];
|
||||
[syncController setBadgeImage:error label:@"Error" forBadgeIdentifier:@"ERROR+SWM"];
|
||||
|
||||
|
||||
// The Mach port name needs to:
|
||||
// - Be prefixed with the code signing Team ID
|
||||
// - Then infixed with the sandbox App Group
|
||||
|
@ -55,12 +55,12 @@
|
|||
// the sandboxed App Extension needs.
|
||||
// https://developer.apple.com/library/mac/documentation/Security/Conceptual/AppSandboxDesignGuide/AppSandboxInDepth/AppSandboxInDepth.html#//apple_ref/doc/uid/TP40011183-CH3-SW24
|
||||
NSString *serverName = [socketApiPrefix stringByAppendingString:@".socketApi"];
|
||||
// NSLog(@"FinderSync serverName %@", serverName);
|
||||
//NSLog(@"FinderSync serverName %@", serverName);
|
||||
|
||||
_syncClientProxy = [[SyncClientProxy alloc] initWithDelegate:self serverName:serverName];
|
||||
_registeredDirectories = [[NSMutableSet alloc] init];
|
||||
_strings = [[NSMutableDictionary alloc] init];
|
||||
|
||||
|
||||
[_syncClientProxy start];
|
||||
return self;
|
||||
}
|
||||
|
@ -74,13 +74,27 @@
|
|||
NSLog(@"ERROR: Could not determine file type of %@", [url path]);
|
||||
isDir = NO;
|
||||
}
|
||||
|
||||
|
||||
NSString* normalizedPath = [[url path] decomposedStringWithCanonicalMapping];
|
||||
[_syncClientProxy askForIcon:normalizedPath isDirectory:isDir];
|
||||
}
|
||||
|
||||
#pragma mark - Menu and toolbar item support
|
||||
|
||||
- (NSString*) selectedPathsSeparatedByRecordSeparator
|
||||
{
|
||||
FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
|
||||
NSMutableString *string = [[NSMutableString alloc] init];
|
||||
[syncController.selectedItemURLs enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if (string.length > 0) {
|
||||
[string appendString:@"\x1e"]; // record separator
|
||||
}
|
||||
NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
|
||||
[string appendString:normalizedPath];
|
||||
}];
|
||||
return string;
|
||||
}
|
||||
|
||||
- (NSMenu *)menuForMenuKind:(FIMenuKind)whichMenu
|
||||
{
|
||||
FIFinderSyncController *syncController = [FIFinderSyncController defaultController];
|
||||
|
@ -101,54 +115,43 @@
|
|||
}
|
||||
}];
|
||||
|
||||
NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
|
||||
// calling this IPC calls us back from client with several MENU_ITEM entries and then our askOnSocket returns again
|
||||
[_syncClientProxy askOnSocket:paths query:@"GET_MENU_ITEMS"];
|
||||
|
||||
id contextMenuTitle = [_strings objectForKey:@"CONTEXT_MENU_TITLE"];
|
||||
id shareTitle = [_strings objectForKey:@"SHARE_MENU_TITLE"];
|
||||
id copyLinkTitle = [_strings objectForKey:@"COPY_PRIVATE_LINK_MENU_TITLE"];
|
||||
id emailLinkTitle = [_strings objectForKey:@"EMAIL_PRIVATE_LINK_MENU_TITLE"];
|
||||
if (contextMenuTitle && !onlyRootsSelected) {
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
|
||||
NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""];
|
||||
NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""];
|
||||
subMenuItem.submenu = subMenu;
|
||||
subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"];
|
||||
|
||||
[subMenu addItemWithTitle:shareTitle action:@selector(shareMenuAction:) keyEquivalent:@""];
|
||||
[subMenu addItemWithTitle:copyLinkTitle action:@selector(copyLinkMenuAction:) keyEquivalent:@""];
|
||||
[subMenu addItemWithTitle:emailLinkTitle action:@selector(emailLinkMenuAction:) keyEquivalent:@""];
|
||||
NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];
|
||||
NSMenu *subMenu = [[NSMenu alloc] initWithTitle:@""];
|
||||
NSMenuItem *subMenuItem = [menu addItemWithTitle:contextMenuTitle action:nil keyEquivalent:@""];
|
||||
subMenuItem.submenu = subMenu;
|
||||
subMenuItem.image = [[NSBundle mainBundle] imageForResource:@"app.icns"];
|
||||
|
||||
// There is an annoying bug in macOS (at least 10.13.3), it does not use/copy over the representedObject of a menu item
|
||||
// So we have to use tag instead.
|
||||
int idx = 0;
|
||||
for (NSArray* item in _menuItems) {
|
||||
NSMenuItem *actionItem = [subMenu addItemWithTitle:[item valueForKey:@"text"]
|
||||
action:@selector(subMenuActionClicked:)
|
||||
keyEquivalent:@""];
|
||||
[actionItem setTag:idx];
|
||||
[actionItem setTarget:self];
|
||||
NSString *flags = [item valueForKey:@"flags"]; // e.g. "d"
|
||||
if ([flags rangeOfString:@"d"].location != NSNotFound) {
|
||||
[actionItem setEnabled:false];
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (IBAction)shareMenuAction:(id)sender
|
||||
{
|
||||
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
|
||||
|
||||
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
|
||||
[_syncClientProxy askOnSocket:normalizedPath query:@"SHARE"];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)copyLinkMenuAction:(id)sender
|
||||
{
|
||||
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
|
||||
|
||||
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
|
||||
[_syncClientProxy askOnSocket:normalizedPath query:@"COPY_PRIVATE_LINK"];
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)emailLinkMenuAction:(id)sender
|
||||
{
|
||||
NSArray* items = [[FIFinderSyncController defaultController] selectedItemURLs];
|
||||
|
||||
[items enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSString* normalizedPath = [[obj path] decomposedStringWithCanonicalMapping];
|
||||
[_syncClientProxy askOnSocket:normalizedPath query:@"EMAIL_PRIVATE_LINK"];
|
||||
}];
|
||||
- (void)subMenuActionClicked:(id)sender {
|
||||
long idx = [(NSMenuItem*)sender tag];
|
||||
NSString *command = [[_menuItems objectAtIndex:idx] valueForKey:@"command"];
|
||||
NSString *paths = [self selectedPathsSeparatedByRecordSeparator];
|
||||
[_syncClientProxy askOnSocket:paths query:command];
|
||||
}
|
||||
|
||||
#pragma mark - SyncClientProxyDelegate implementation
|
||||
|
@ -181,6 +184,14 @@
|
|||
[_strings setObject:value forKey:key];
|
||||
}
|
||||
|
||||
- (void)resetMenuItems
|
||||
{
|
||||
_menuItems = [[NSMutableArray alloc] init];
|
||||
}
|
||||
- (void)addMenuItem:(NSDictionary *)item {
|
||||
[_menuItems addObject:item];
|
||||
}
|
||||
|
||||
- (void)connectionDidDie
|
||||
{
|
||||
[_strings removeAllObjects];
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
- (void)registerPath:(NSString*)path;
|
||||
- (void)unregisterPath:(NSString*)path;
|
||||
- (void)setString:(NSString*)key value:(NSString*)value;
|
||||
- (void)resetMenuItems;
|
||||
- (void)addMenuItem:(NSDictionary *)item;
|
||||
- (void)connectionDidDie;
|
||||
@end
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
- (instancetype)initWithDelegate:(id)arg1 serverName:(NSString*)serverName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
|
||||
self.delegate = arg1;
|
||||
_serverName = serverName;
|
||||
_remoteEnd = nil;
|
||||
|
@ -41,20 +41,20 @@
|
|||
{
|
||||
if (_remoteEnd)
|
||||
return;
|
||||
|
||||
|
||||
// Lookup the server connection
|
||||
NSConnection *conn = [NSConnection connectionWithRegisteredName:_serverName host:nil];
|
||||
|
||||
|
||||
if (!conn) {
|
||||
// Could not connect to the sync client
|
||||
[self scheduleRetry];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(connectionDidDie:)
|
||||
name:NSConnectionDidDieNotification
|
||||
object:conn];
|
||||
selector:@selector(connectionDidDie:)
|
||||
name:NSConnectionDidDieNotification
|
||||
object:conn];
|
||||
|
||||
NSDistantObject <ServerProtocol> *server = (NSDistantObject <ServerProtocol> *)[conn rootProxy];
|
||||
assert(server);
|
||||
|
@ -71,7 +71,7 @@
|
|||
// The server replied with the distant object that we will use for tx
|
||||
_remoteEnd = (NSDistantObject <ChannelProtocol> *)tx;
|
||||
[_remoteEnd setProtocolForProxy:@protocol(ChannelProtocol)];
|
||||
|
||||
|
||||
// Everything is set up, start querying
|
||||
[self askOnSocket:@"" query:@"GET_STRINGS"];
|
||||
}
|
||||
|
@ -83,7 +83,7 @@
|
|||
|
||||
- (void)connectionDidDie:(NSNotification*)notification
|
||||
{
|
||||
#pragma unused(notification)
|
||||
#pragma unused(notification)
|
||||
_remoteEnd = nil;
|
||||
[_delegate connectionDidDie];
|
||||
|
||||
|
@ -95,11 +95,11 @@
|
|||
- (void)sendMessage:(NSData*)msg
|
||||
{
|
||||
NSString *answer = [[NSString alloc] initWithData:msg encoding:NSUTF8StringEncoding];
|
||||
|
||||
// Cut the trailing newline
|
||||
|
||||
// Cut the trailing newline. We always only receive one line from the client.
|
||||
answer = [answer substringToIndex:[answer length] - 1];
|
||||
NSArray *chunks = [answer componentsSeparatedByString: @":"];
|
||||
|
||||
|
||||
if( [[chunks objectAtIndex:0] isEqualToString:@"STATUS"] ) {
|
||||
NSString *result = [chunks objectAtIndex:1];
|
||||
NSString *path = [chunks objectAtIndex:2];
|
||||
|
@ -123,6 +123,18 @@
|
|||
// BEGIN and END messages, do nothing.
|
||||
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"STRING"] ) {
|
||||
[_delegate setString:[chunks objectAtIndex:1] value:[chunks objectAtIndex:2]];
|
||||
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"GET_MENU_ITEMS"] ) {
|
||||
if ([[chunks objectAtIndex:1] isEqualToString:@"BEGIN"]) {
|
||||
[_delegate resetMenuItems];
|
||||
} else if ([[chunks objectAtIndex:1] isEqualToString:@"END"]) {
|
||||
// Don't do anything special, the askOnSocket call in FinderSync menuForMenuKind will return after this line
|
||||
}
|
||||
} else if( [[chunks objectAtIndex:0 ] isEqualToString:@"MENU_ITEM"] ) {
|
||||
NSMutableDictionary *item = [[NSMutableDictionary alloc] init];
|
||||
[item setValue:[chunks objectAtIndex:1] forKey:@"command"]; // e.g. "COPY_PRIVATE_LINK"
|
||||
[item setValue:[chunks objectAtIndex:2] forKey:@"flags"]; // e.g. "d"
|
||||
[item setValue:[chunks objectAtIndex:3] forKey:@"text"]; // e.g. "Copy private link to clipboard"
|
||||
[_delegate addMenuItem:item];
|
||||
} else {
|
||||
NSLog(@"SyncState: Unknown command %@", [chunks objectAtIndex:0]);
|
||||
}
|
||||
|
@ -131,7 +143,7 @@
|
|||
- (void)askOnSocket:(NSString*)path query:(NSString*)verb
|
||||
{
|
||||
NSString *query = [NSString stringWithFormat:@"%@:%@\n", verb,path];
|
||||
|
||||
|
||||
@try {
|
||||
[_remoteEnd sendMessage:[query dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
} @catch(NSException* e) {
|
||||
|
|
12301
src/3rdparty/sqlite3/sqlite3.c
vendored
12301
src/3rdparty/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load diff
658
src/3rdparty/sqlite3/sqlite3.h
vendored
658
src/3rdparty/sqlite3/sqlite3.h
vendored
File diff suppressed because it is too large
Load diff
|
@ -67,7 +67,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
|
|||
|
||||
static QString defaultJournalMode(const QString &dbPath)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
#if defined(Q_OS_WIN)
|
||||
// See #2693: Some exFAT file systems seem unable to cope with the
|
||||
// WAL journaling mode. They work fine with DELETE.
|
||||
QString fileSystem = FileSystem::fileSystemForPath(dbPath);
|
||||
|
@ -76,6 +76,11 @@ static QString defaultJournalMode(const QString &dbPath)
|
|||
qCInfo(lcDb) << "Filesystem contains FAT - using DELETE journal mode";
|
||||
return "DELETE";
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
if (dbPath.startsWith("/Volumes/")) {
|
||||
qCInfo(lcDb) << "Mounted sync dir, do not use WAL for" << dbPath;
|
||||
return "DELETE";
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(dbPath)
|
||||
#endif
|
||||
|
@ -273,6 +278,13 @@ bool SyncJournalDb::sqlFail(const QString &log, const SqlQuery &query)
|
|||
bool SyncJournalDb::checkConnect()
|
||||
{
|
||||
if (_db.isOpen()) {
|
||||
// Unfortunately the sqlite isOpen check can return true even when the underlying storage
|
||||
// has become unavailable - and then some operations may cause crashes. See #6049
|
||||
if (!QFile::exists(_dbFile)) {
|
||||
qCWarning(lcDb) << "Database open, but file " + _dbFile + " does not exist";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1886,6 +1898,8 @@ void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType ty
|
|||
return;
|
||||
}
|
||||
|
||||
startTransaction();
|
||||
|
||||
//first, delete all entries of this type
|
||||
SqlQuery delQuery("DELETE FROM selectivesync WHERE type == ?1", _db);
|
||||
delQuery.bindValue(1, int(type));
|
||||
|
@ -1902,6 +1916,8 @@ void SyncJournalDb::setSelectiveSyncList(SyncJournalDb::SelectiveSyncListType ty
|
|||
qCWarning(lcDb) << "SQL error when inserting into selective sync" << type << path << delQuery.error();
|
||||
}
|
||||
}
|
||||
|
||||
commitInternal("setSelectiveSyncList");
|
||||
}
|
||||
|
||||
void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
|
||||
|
|
|
@ -114,6 +114,12 @@ public:
|
|||
int _errorCount;
|
||||
bool _valid;
|
||||
QByteArray _contentChecksum;
|
||||
/**
|
||||
* Returns true if this entry refers to a chunked upload that can be continued.
|
||||
* (As opposed to a small file transfer which is stored in the db so we can detect the case
|
||||
* when the upload succeeded, but the connection was dropped before we got the answer)
|
||||
*/
|
||||
bool isChunked() const { return _transferid != 0; }
|
||||
};
|
||||
|
||||
struct PollInfo
|
||||
|
|
|
@ -126,12 +126,7 @@ int csync_update(CSYNC *ctx) {
|
|||
}
|
||||
|
||||
int csync_reconcile(CSYNC *ctx) {
|
||||
int rc = -1;
|
||||
|
||||
if (ctx == NULL) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
}
|
||||
Q_ASSERT(ctx);
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
|
||||
/* Reconciliation for local replica */
|
||||
|
@ -140,54 +135,31 @@ int csync_reconcile(CSYNC *ctx) {
|
|||
|
||||
ctx->current = LOCAL_REPLICA;
|
||||
|
||||
rc = csync_reconcile_updates(ctx);
|
||||
csync_reconcile_updates(ctx);
|
||||
|
||||
qCInfo(lcCSync) << "Reconciliation for local replica took " << timer.elapsed() / 1000.
|
||||
<< "seconds visiting " << ctx->local.files.size() << " files.";
|
||||
|
||||
if (rc < 0) {
|
||||
if (!CSYNC_STATUS_IS_OK(ctx->status_code)) {
|
||||
ctx->status_code = csync_errno_to_status( errno, CSYNC_STATUS_RECONCILE_ERROR );
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Reconciliation for remote replica */
|
||||
timer.restart();
|
||||
|
||||
ctx->current = REMOTE_REPLICA;
|
||||
|
||||
rc = csync_reconcile_updates(ctx);
|
||||
csync_reconcile_updates(ctx);
|
||||
|
||||
qCInfo(lcCSync) << "Reconciliation for remote replica took " << timer.elapsed() / 1000.
|
||||
<< "seconds visiting " << ctx->remote.files.size() << " files.";
|
||||
|
||||
if (rc < 0) {
|
||||
if (!CSYNC_STATUS_IS_OK(ctx->status_code)) {
|
||||
ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_RECONCILE_ERROR );
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
ctx->status |= CSYNC_STATUS_RECONCILE;
|
||||
|
||||
rc = 0;
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* local visitor which calls the user visitor with repacked stat info.
|
||||
*/
|
||||
static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
int rc = 0;
|
||||
csync_treewalk_visit_func *visitor = NULL;
|
||||
_csync_treewalk_context *twctx = NULL;
|
||||
static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx, const csync_treewalk_visit_func &visitor) {
|
||||
csync_s::FileMap *other_tree = nullptr;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* we need the opposite tree! */
|
||||
switch (ctx->current) {
|
||||
case LOCAL_REPLICA:
|
||||
|
@ -220,80 +192,41 @@ static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
|
||||
twctx = (_csync_treewalk_context*) ctx->callbacks.userdata;
|
||||
if (twctx == NULL) {
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (twctx->instruction_filter > 0 &&
|
||||
!(twctx->instruction_filter & cur->instruction) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
visitor = (csync_treewalk_visit_func*)(twctx->user_visitor);
|
||||
if (visitor != NULL) {
|
||||
rc = (*visitor)(cur, other, twctx->userdata);
|
||||
|
||||
return rc;
|
||||
}
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
return -1;
|
||||
Q_ASSERT(visitor);
|
||||
return visitor(cur, other);
|
||||
}
|
||||
|
||||
/*
|
||||
* treewalk function, called from its wrappers below.
|
||||
*
|
||||
* it encapsulates the user visitor function, the filter and the userdata
|
||||
* into a treewalk_context structure and calls the rb treewalk function,
|
||||
* which calls the local _csync_treewalk_visitor in this module.
|
||||
* The user visitor is called from there.
|
||||
*/
|
||||
static int _csync_walk_tree(CSYNC *ctx, csync_s::FileMap *tree, csync_treewalk_visit_func *visitor, int filter)
|
||||
static int _csync_walk_tree(CSYNC *ctx, csync_s::FileMap &tree, const csync_treewalk_visit_func &visitor)
|
||||
{
|
||||
_csync_treewalk_context tw_ctx;
|
||||
int rc = 0;
|
||||
|
||||
tw_ctx.userdata = ctx->callbacks.userdata;
|
||||
tw_ctx.user_visitor = visitor;
|
||||
tw_ctx.instruction_filter = filter;
|
||||
|
||||
ctx->callbacks.userdata = &tw_ctx;
|
||||
|
||||
for (auto &pair : *tree) {
|
||||
if (_csync_treewalk_visitor(pair.second.get(), ctx) < 0) {
|
||||
rc = -1;
|
||||
break;
|
||||
for (auto &pair : tree) {
|
||||
if (_csync_treewalk_visitor(pair.second.get(), ctx, visitor) < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc < 0 ) {
|
||||
if( ctx->status_code == CSYNC_STATUS_OK )
|
||||
ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_TREE_ERROR);
|
||||
}
|
||||
ctx->callbacks.userdata = tw_ctx.userdata;
|
||||
|
||||
return rc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* wrapper function for treewalk on the remote tree
|
||||
*/
|
||||
int csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter)
|
||||
int csync_walk_remote_tree(CSYNC *ctx, const csync_treewalk_visit_func &visitor)
|
||||
{
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = REMOTE_REPLICA;
|
||||
return _csync_walk_tree(ctx, &ctx->remote.files, visitor, filter);
|
||||
return _csync_walk_tree(ctx, ctx->remote.files, visitor);
|
||||
}
|
||||
|
||||
/*
|
||||
* wrapper function for treewalk on the local tree
|
||||
*/
|
||||
int csync_walk_local_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter)
|
||||
int csync_walk_local_tree(CSYNC *ctx, const csync_treewalk_visit_func &visitor)
|
||||
{
|
||||
ctx->status_code = CSYNC_STATUS_OK;
|
||||
ctx->current = LOCAL_REPLICA;
|
||||
return _csync_walk_tree(ctx, &ctx->local.files, visitor, filter);
|
||||
return _csync_walk_tree(ctx, ctx->local.files, visitor);
|
||||
}
|
||||
|
||||
int csync_s::reinitialize() {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <config_csync.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <QByteArray>
|
||||
#include "common/remotepermissions.h"
|
||||
|
@ -64,40 +65,17 @@ enum csync_status_codes_e {
|
|||
CSYNC_STATUS_ERROR = 1024, /* don't use this code,
|
||||
*/
|
||||
CSYNC_STATUS_UNSUCCESSFUL, /* Unspecific problem happend */
|
||||
CSYNC_STATUS_NO_LOCK, /* OBSOLETE does not happen anymore */
|
||||
CSYNC_STATUS_STATEDB_LOAD_ERROR, /* Statedb can not be loaded. */
|
||||
CSYNC_STATUS_STATEDB_CORRUPTED, /* Statedb is corrupted */
|
||||
CSYNC_STATUS_NO_MODULE, /* URL passed to csync does not start with owncloud:// or ownclouds:// */
|
||||
CSYNC_STATUS_TIMESKEW, /* OBSOLETE */
|
||||
CSYNC_STATUS_FILESYSTEM_UNKNOWN, /* UNUSED */
|
||||
CSYNC_STATUS_TREE_ERROR, /* csync trees could not be created */
|
||||
CSYNC_STATUS_PARAM_ERROR, /* parameter is zero where not expected */
|
||||
CSYNC_STATUS_UPDATE_ERROR, /* general update or discovery error */
|
||||
CSYNC_STATUS_RECONCILE_ERROR, /* general reconcile error */
|
||||
CSYNC_STATUS_PROPAGATE_ERROR, /* OBSOLETE */
|
||||
CSYNC_STATUS_REMOTE_ACCESS_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_REMOTE_CREATE_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_REMOTE_STAT_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_LOCAL_CREATE_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_LOCAL_STAT_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_PROXY_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_LOOKUP_ERROR, /* Neon fails to find proxy. Almost OBSOLETE */
|
||||
CSYNC_STATUS_SERVER_AUTH_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_PROXY_AUTH_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_CONNECT_ERROR, /* neon driven connection failed */
|
||||
CSYNC_STATUS_TIMEOUT, /* UNUSED */
|
||||
CSYNC_STATUS_HTTP_ERROR, /* UNUSED */
|
||||
CSYNC_STATUS_PERMISSION_DENIED, /* */
|
||||
CSYNC_STATUS_NOT_FOUND,
|
||||
CSYNC_STATUS_FILE_EXISTS,
|
||||
CSYNC_STATUS_OUT_OF_SPACE,
|
||||
CSYNC_STATUS_QUOTA_EXCEEDED, /* UNUSED */
|
||||
CSYNC_STATUS_SERVICE_UNAVAILABLE,
|
||||
CSYNC_STATUS_STORAGE_UNAVAILABLE,
|
||||
CSYNC_STATUS_FILE_SIZE_ERROR,
|
||||
CSYNC_STATUS_CONTEXT_LOST,
|
||||
CSYNC_STATUS_MERGE_FILETREE_ERROR,
|
||||
CSYNC_STATUS_CSYNC_STATUS_ERROR,
|
||||
CSYNC_STATUS_OPENDIR_ERROR,
|
||||
CSYNC_STATUS_READDIR_ERROR,
|
||||
CSYNC_STATUS_OPEN_ERROR,
|
||||
|
@ -306,29 +284,27 @@ CSYNC_STATUS OCSYNC_EXPORT csync_get_status(CSYNC *ctx);
|
|||
/* Used for special modes or debugging */
|
||||
int OCSYNC_EXPORT csync_set_status(CSYNC *ctx, int status);
|
||||
|
||||
typedef int csync_treewalk_visit_func(csync_file_stat_t *cur, csync_file_stat_t *other, void*);
|
||||
using csync_treewalk_visit_func = std::function<int(csync_file_stat_t *cur, csync_file_stat_t *other)>;
|
||||
|
||||
/**
|
||||
* @brief Walk the local file tree and call a visitor function for each file.
|
||||
*
|
||||
* @param ctx The csync context.
|
||||
* @param visitor A callback function to handle the file info.
|
||||
* @param filter A filter, built from or'ed csync_instructions_e
|
||||
*
|
||||
* @return 0 on success, less than 0 if an error occurred.
|
||||
*/
|
||||
int OCSYNC_EXPORT csync_walk_local_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter);
|
||||
int OCSYNC_EXPORT csync_walk_local_tree(CSYNC *ctx, const csync_treewalk_visit_func &visitor);
|
||||
|
||||
/**
|
||||
* @brief Walk the remote file tree and call a visitor function for each file.
|
||||
*
|
||||
* @param ctx The csync context.
|
||||
* @param visitor A callback function to handle the file info.
|
||||
* @param filter A filter, built from and'ed csync_instructions_e
|
||||
*
|
||||
* @return 0 on success, less than 0 if an error occurred.
|
||||
*/
|
||||
int OCSYNC_EXPORT csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter);
|
||||
int OCSYNC_EXPORT csync_walk_remote_tree(CSYNC *ctx, const csync_treewalk_visit_func &visitor);
|
||||
|
||||
/**
|
||||
* @brief Get the csync status string.
|
||||
|
|
|
@ -217,8 +217,8 @@ static CSYNC_EXCLUDE_TYPE _csync_excluded_common(const char *path, bool excludeC
|
|||
}
|
||||
#endif
|
||||
|
||||
/* We create a desktop.ini on Windows for the sidebar icon, make sure we don't sync them. */
|
||||
if (blen == 11) {
|
||||
/* We create a Desktop.ini on Windows for the sidebar icon, make sure we don't sync it. */
|
||||
if (blen == 11 && path == bname) {
|
||||
rc = csync_fnmatch("Desktop.ini", bname, 0);
|
||||
if (rc == 0) {
|
||||
match = CSYNC_FILE_SILENTLY_EXCLUDED;
|
||||
|
|
|
@ -32,19 +32,8 @@
|
|||
* should always be larger than the highest system errno. */
|
||||
#define CSYNC_CUSTOM_ERRNO_BASE 10000
|
||||
|
||||
#define ERRNO_GENERAL_ERROR CSYNC_CUSTOM_ERRNO_BASE+2
|
||||
#define ERRNO_LOOKUP_ERROR CSYNC_CUSTOM_ERRNO_BASE+3
|
||||
#define ERRNO_USER_UNKNOWN_ON_SERVER CSYNC_CUSTOM_ERRNO_BASE+4
|
||||
#define ERRNO_PROXY_AUTH CSYNC_CUSTOM_ERRNO_BASE+5
|
||||
#define ERRNO_CONNECT CSYNC_CUSTOM_ERRNO_BASE+6
|
||||
#define ERRNO_TIMEOUT CSYNC_CUSTOM_ERRNO_BASE+7
|
||||
#define ERRNO_PRECONDITION CSYNC_CUSTOM_ERRNO_BASE+8
|
||||
#define ERRNO_RETRY CSYNC_CUSTOM_ERRNO_BASE+9
|
||||
#define ERRNO_REDIRECT CSYNC_CUSTOM_ERRNO_BASE+10
|
||||
#define ERRNO_WRONG_CONTENT CSYNC_CUSTOM_ERRNO_BASE+11
|
||||
#define ERRNO_ERROR_STRING CSYNC_CUSTOM_ERRNO_BASE+13
|
||||
#define ERRNO_SERVICE_UNAVAILABLE CSYNC_CUSTOM_ERRNO_BASE+14
|
||||
#define ERRNO_USER_ABORT CSYNC_CUSTOM_ERRNO_BASE+16
|
||||
#define ERRNO_STORAGE_UNAVAILABLE CSYNC_CUSTOM_ERRNO_BASE+17
|
||||
#define ERRNO_FORBIDDEN CSYNC_CUSTOM_ERRNO_BASE+18
|
||||
|
||||
|
|
|
@ -78,24 +78,6 @@ CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status)
|
|||
status = CSYNC_STATUS_OK;
|
||||
break;
|
||||
/* The custom errnos first. */
|
||||
case ERRNO_GENERAL_ERROR:
|
||||
status = CSYNC_STATUS_UNSUCCESSFUL;
|
||||
break;
|
||||
case ERRNO_LOOKUP_ERROR: /* In Neon: Server or proxy hostname lookup failed */
|
||||
status = CSYNC_STATUS_LOOKUP_ERROR;
|
||||
break;
|
||||
case ERRNO_USER_UNKNOWN_ON_SERVER: /* Neon: User authentication on server failed. */
|
||||
status = CSYNC_STATUS_SERVER_AUTH_ERROR;
|
||||
break;
|
||||
case ERRNO_PROXY_AUTH:
|
||||
status = CSYNC_STATUS_PROXY_AUTH_ERROR; /* Neon: User authentication on proxy failed */
|
||||
break;
|
||||
case ERRNO_CONNECT:
|
||||
status = CSYNC_STATUS_CONNECT_ERROR; /* Network: Connection error */
|
||||
break;
|
||||
case ERRNO_TIMEOUT:
|
||||
status = CSYNC_STATUS_TIMEOUT; /* Network: Timeout error */
|
||||
break;
|
||||
case ERRNO_SERVICE_UNAVAILABLE:
|
||||
status = CSYNC_STATUS_SERVICE_UNAVAILABLE; /* Service temporarily down */
|
||||
break;
|
||||
|
@ -105,9 +87,6 @@ CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status)
|
|||
case EFBIG:
|
||||
status = CSYNC_STATUS_FILE_SIZE_ERROR; /* File larger than 2MB */
|
||||
break;
|
||||
case ERRNO_PRECONDITION:
|
||||
case ERRNO_RETRY:
|
||||
case ERRNO_REDIRECT:
|
||||
case ERRNO_WRONG_CONTENT:
|
||||
status = CSYNC_STATUS_HTTP_ERROR;
|
||||
break;
|
||||
|
@ -125,14 +104,12 @@ CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status)
|
|||
case EEXIST: /* File exists */
|
||||
status = CSYNC_STATUS_FILE_EXISTS;
|
||||
break;
|
||||
case EINVAL:
|
||||
status = CSYNC_STATUS_PARAM_ERROR;
|
||||
break;
|
||||
case ENOSPC:
|
||||
status = CSYNC_STATUS_OUT_OF_SPACE;
|
||||
break;
|
||||
|
||||
/* All the remaining basic errnos: */
|
||||
case EINVAL: /* Invalid argument */
|
||||
case EIO: /* I/O error */
|
||||
case ESRCH: /* No such process */
|
||||
case EINTR: /* Interrupted system call */
|
||||
|
@ -162,7 +139,6 @@ CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status)
|
|||
case EMLINK: /* Too many links */
|
||||
case EPIPE: /* Broken pipe */
|
||||
|
||||
case ERRNO_ERROR_STRING:
|
||||
default:
|
||||
status = default_status;
|
||||
}
|
||||
|
|
|
@ -219,17 +219,6 @@ struct OCSYNC_EXPORT csync_s {
|
|||
csync_s &operator=(const csync_s &) = delete;
|
||||
};
|
||||
|
||||
/*
|
||||
* context for the treewalk function
|
||||
*/
|
||||
struct _csync_treewalk_context_s
|
||||
{
|
||||
csync_treewalk_visit_func *user_visitor;
|
||||
int instruction_filter;
|
||||
void *userdata;
|
||||
};
|
||||
typedef struct _csync_treewalk_context_s _csync_treewalk_context;
|
||||
|
||||
void set_errno_from_http_errcode( int err );
|
||||
|
||||
/**
|
||||
|
|
|
@ -92,7 +92,7 @@ static csync_file_stat_t *_csync_check_ignored(csync_s::FileMap *tree, const Byt
|
|||
* (timestamp is newer), it is not overwritten. If both files, on the
|
||||
* source and the destination, have been changed, the newer file wins.
|
||||
*/
|
||||
static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
static void _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
csync_s::FileMap *our_tree = nullptr;
|
||||
csync_s::FileMap *other_tree = nullptr;
|
||||
|
||||
|
@ -442,11 +442,9 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||
cur->path.constData());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int csync_reconcile_updates(CSYNC *ctx) {
|
||||
void csync_reconcile_updates(CSYNC *ctx) {
|
||||
csync_s::FileMap *tree = nullptr;
|
||||
|
||||
switch (ctx->current) {
|
||||
|
@ -461,12 +459,8 @@ int csync_reconcile_updates(CSYNC *ctx) {
|
|||
}
|
||||
|
||||
for (auto &pair : *tree) {
|
||||
if (_csync_merge_algorithm_visitor(pair.second.get(), ctx) < 0) {
|
||||
ctx->status_code = CSYNC_STATUS_RECONCILE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
_csync_merge_algorithm_visitor(pair.second.get(), ctx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* vim: set ts=8 sw=2 et cindent: */
|
||||
|
|
|
@ -46,11 +46,9 @@
|
|||
*
|
||||
* @param ctx The csync context to use.
|
||||
*
|
||||
* @return 0 on success, < 0 on error.
|
||||
*
|
||||
* @todo Add an argument to set the algorithm to use.
|
||||
*/
|
||||
int OCSYNC_EXPORT csync_reconcile_updates(CSYNC *ctx);
|
||||
void OCSYNC_EXPORT csync_reconcile_updates(CSYNC *ctx);
|
||||
|
||||
/**
|
||||
* }@
|
||||
|
|
|
@ -108,15 +108,9 @@ static bool _csync_mtime_equal(time_t a, time_t b)
|
|||
* See doc/dev/sync-algorithm.md for an overview.
|
||||
*/
|
||||
static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> fs) {
|
||||
Q_ASSERT(fs);
|
||||
OCC::SyncJournalFileRecord base;
|
||||
CSYNC_EXCLUDE_TYPE excluded = CSYNC_NOT_EXCLUDED;
|
||||
|
||||
if (fs == NULL) {
|
||||
errno = EINVAL;
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fs->type == ItemTypeSkip) {
|
||||
excluded =CSYNC_FILE_EXCLUDE_STAT_FAILED;
|
||||
} else {
|
||||
|
@ -216,14 +210,12 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
|||
/* we have an update! */
|
||||
qCInfo(lcUpdate, "Database entry found for %s, compare: %" PRId64 " <-> %" PRId64
|
||||
", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64
|
||||
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x, ignore: %d, e2e: %s",
|
||||
base._path.constData(),
|
||||
((int64_t) fs->modtime), ((int64_t) base._modtime),
|
||||
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x"
|
||||
", checksum: %s <-> %s , ignore: %d, e2e: %s",
|
||||
base._path.constData(), ((int64_t) fs->modtime), ((int64_t) base._modtime),
|
||||
fs->etag.constData(), base._etag.constData(), (uint64_t) fs->inode, (uint64_t) base._inode,
|
||||
(uint64_t) fs->size, (uint64_t) base._fileSize,
|
||||
*reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm),
|
||||
base._serverHasIgnoredFiles,
|
||||
base._e2eMangledName.constData());
|
||||
(uint64_t) fs->size, (uint64_t) base._fileSize, *reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm),
|
||||
fs->checksumHeader.constData(), base._checksumHeader.constData(), base._serverHasIgnoredFiles, base._e2eMangledName.constData());
|
||||
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL;
|
||||
|
||||
|
@ -711,10 +703,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||
// Now process to have a relative path to the sync root for the local replica, or to the data root on the remote.
|
||||
dirent->path = fullpath;
|
||||
if (ctx->current == LOCAL_REPLICA) {
|
||||
if (dirent->path.size() <= (int)strlen(ctx->local.uri)) {
|
||||
ctx->status_code = CSYNC_STATUS_PARAM_ERROR;
|
||||
goto error;
|
||||
}
|
||||
ASSERT(dirent->path.startsWith(ctx->local.uri)); // path is relative to uri
|
||||
// "len + 1" to include the slash in-between.
|
||||
dirent->path = dirent->path.mid(strlen(ctx->local.uri) + 1);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include "tooltipupdater.h"
|
||||
#include "filesystem.h"
|
||||
#include "clientsideencryptionjobs.h"
|
||||
#include "syncresult.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
|
@ -335,9 +336,36 @@ void AccountSettings::slotUnlockFolderSuccess(const QByteArray& fileId)
|
|||
{
|
||||
qCInfo(lcAccountSettings()) << "Unlocking success!";
|
||||
}
|
||||
void AccountSettings::slotMarkSubfolderEncrpted(const QByteArray& fileId)
|
||||
|
||||
bool AccountSettings::canEncryptOrDecrypt (const FolderStatusModel::SubFolderInfo* info) {
|
||||
if (info->_folder->syncResult().status() != SyncResult::Status::Success) {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Please wait for the folder to sync before trying to encrypt it.");
|
||||
msgBox.exec();
|
||||
return false;
|
||||
}
|
||||
|
||||
// for some reason the actual folder in disk is info->_folder->path + info->_path.
|
||||
QDir folderPath(info->_folder->path() + info->_path);
|
||||
folderPath.setFilter( QDir::AllEntries | QDir::NoDotAndDotDot );
|
||||
|
||||
if (folderPath.count() != 0) {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("You cannot encyrpt a folder with contents, please remove the files \n"
|
||||
"Wait for the new sync, then encrypt it.");
|
||||
msgBox.exec();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AccountSettings::slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
{
|
||||
auto job = new OCC::SetEncryptionFlagApiJob(accountsState()->account(), fileId);
|
||||
if (!canEncryptOrDecrypt(folderInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto job = new OCC::SetEncryptionFlagApiJob(accountsState()->account(), folderInfo->_fileId);
|
||||
connect(job, &OCC::SetEncryptionFlagApiJob::success, this, &AccountSettings::slotEncryptionFlagSuccess);
|
||||
connect(job, &OCC::SetEncryptionFlagApiJob::error, this, &AccountSettings::slotEncryptionFlagError);
|
||||
job->start();
|
||||
|
@ -349,11 +377,17 @@ void AccountSettings::slotMarkSubfolderEncrpted(const QByteArray& fileId)
|
|||
// 2 - Delete Metadata,
|
||||
// 3 - Unlock Folder,
|
||||
// 4 - Mark as Decrypted.
|
||||
void AccountSettings::slotMarkSubfolderDecrypted(const QByteArray& fileId)
|
||||
|
||||
|
||||
void AccountSettings::slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo)
|
||||
{
|
||||
if (!canEncryptOrDecrypt(folderInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Starting to mark as decrypted";
|
||||
qDebug() << "Locking the folder";
|
||||
auto lockJob = new LockEncryptFolderApiJob(accountsState()->account(), fileId);
|
||||
auto lockJob = new LockEncryptFolderApiJob(accountsState()->account(), folderInfo->_fileId);
|
||||
connect(lockJob, &LockEncryptFolderApiJob::success,
|
||||
this, &AccountSettings::slotLockForDecryptionSuccess);
|
||||
connect(lockJob, &LockEncryptFolderApiJob::error,
|
||||
|
@ -444,13 +478,15 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
|
|||
auto acc = _accountState->account();
|
||||
|
||||
if (acc->capabilities().clientSideEncryptionAvaliable()) {
|
||||
// Verify if the folder is empty before attempting to encrypt.
|
||||
|
||||
bool isEncrypted = acc->e2e()->isFolderEncrypted(info->_path);
|
||||
ac = menu.addAction( isEncrypted ? tr("Decrypt") : tr("Encrypt"));
|
||||
|
||||
if (not isEncrypted) {
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info->_fileId); });
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderEncrpted(info); });
|
||||
} else {
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info->_fileId); });
|
||||
connect(ac, &QAction::triggered, [this, &info] { slotMarkSubfolderDecrypted(info); });
|
||||
}
|
||||
}
|
||||
menu.exec(QCursor::pos());
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "quotainfo.h"
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
#include "folderstatusmodel.h"
|
||||
|
||||
class QModelIndex;
|
||||
class QNetworkReply;
|
||||
|
@ -55,7 +56,7 @@ public:
|
|||
explicit AccountSettings(AccountState *accountState, QWidget *parent = 0);
|
||||
~AccountSettings();
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE { return ownCloudGui::settingsDialogSize(); }
|
||||
|
||||
bool canEncryptOrDecrypt(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
|
||||
signals:
|
||||
void folderChanged();
|
||||
|
@ -85,8 +86,8 @@ protected slots:
|
|||
void slotOpenAccountWizard();
|
||||
void slotAccountAdded(AccountState *);
|
||||
void refreshSelectiveSyncStatus();
|
||||
void slotMarkSubfolderEncrpted(const QByteArray& fileId);
|
||||
void slotMarkSubfolderDecrypted(const QByteArray& fileId);
|
||||
void slotMarkSubfolderEncrpted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||
void slotSubfolderContextMenuRequested(const QModelIndex& idx, const QPoint& point);
|
||||
void slotCustomContextMenuRequested(const QPoint &);
|
||||
void slotFolderListClicked(const QModelIndex &indx);
|
||||
|
|
|
@ -214,11 +214,11 @@ void AccountState::checkConnectivity()
|
|||
// IF the account is connected the connection check can be skipped
|
||||
// if the last successful etag check job is not so long ago.
|
||||
ConfigFile cfg;
|
||||
int polltime = cfg.remotePollInterval();
|
||||
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
||||
|
||||
if (isConnected() && _timeSinceLastETagCheck.isValid()
|
||||
&& _timeSinceLastETagCheck.elapsed() < polltime) {
|
||||
qCDebug(lcAccountState) << account()->displayName() << "The last ETag check succeeded within the last " << polltime / 1000 << " secs. No connection check needed!";
|
||||
&& _timeSinceLastETagCheck.hasExpired(polltime.count())) {
|
||||
qCDebug(lcAccountState) << account()->displayName() << "The last ETag check succeeded within the last " << polltime.count() / 1000 << " secs. No connection check needed!";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -533,10 +533,10 @@ ActivitySettings::ActivitySettings(QWidget *parent)
|
|||
_tab->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void ActivitySettings::setNotificationRefreshInterval(quint64 interval)
|
||||
void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
||||
{
|
||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval / 1000 << " sec interval";
|
||||
_notificationCheckTimer.start(interval);
|
||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
||||
_notificationCheckTimer.start(interval.count());
|
||||
}
|
||||
|
||||
void ActivitySettings::setActivityTabHidden(bool hidden)
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
#include <QAbstractListModel>
|
||||
#include <chrono>
|
||||
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
|
@ -91,7 +92,7 @@ private slots:
|
|||
void slotNotifyNetworkError(QNetworkReply *);
|
||||
void slotNotifyServerFinished(const QString &reply, int replyCode);
|
||||
void endNotificationRequest(NotificationWidget *widget, int replyCode);
|
||||
void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 4500);
|
||||
void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 100);
|
||||
void slotCheckToCleanWidgets();
|
||||
|
||||
private:
|
||||
|
@ -137,7 +138,7 @@ public slots:
|
|||
void slotRefresh(AccountState *ptr);
|
||||
void slotRemoveAccount(AccountState *ptr);
|
||||
|
||||
void setNotificationRefreshInterval(quint64 interval);
|
||||
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
||||
|
||||
void slotShowIssuesTab(const QString &folderAlias);
|
||||
|
||||
|
|
|
@ -376,13 +376,17 @@ void Application::slotownCloudWizardDone(int res)
|
|||
void Application::setupLogging()
|
||||
{
|
||||
// might be called from second instance
|
||||
Logger::instance()->setLogFile(_logFile);
|
||||
Logger::instance()->setLogDir(_logDir);
|
||||
Logger::instance()->setLogExpire(_logExpire);
|
||||
Logger::instance()->setLogFlush(_logFlush);
|
||||
Logger::instance()->setLogDebug(_logDebug);
|
||||
auto logger = Logger::instance();
|
||||
logger->setLogFile(_logFile);
|
||||
logger->setLogDir(_logDir);
|
||||
logger->setLogExpire(_logExpire);
|
||||
logger->setLogFlush(_logFlush);
|
||||
logger->setLogDebug(_logDebug);
|
||||
if (!logger->isLoggingToFile() && ConfigFile().automaticLogDir()) {
|
||||
logger->setupTemporaryFolderLogDir();
|
||||
}
|
||||
|
||||
Logger::instance()->enterNextLogFile();
|
||||
logger->enterNextLogFile();
|
||||
|
||||
qCInfo(lcApplication) << QString::fromLatin1("################## %1 locale:[%2] ui_lang:[%3] version:[%4] os:[%5]").arg(_theme->appName()).arg(QLocale::system().name()).arg(property("ui_lang").toString()).arg(_theme->version()).arg(Utility::platformName());
|
||||
}
|
||||
|
|
|
@ -120,8 +120,8 @@ void CloudProviderWrapper::slotUpdateProgress(const QString &folder, const Progr
|
|||
|
||||
// Build status details text
|
||||
QString msg;
|
||||
if (!progress._currentDiscoveredFolder.isEmpty()) {
|
||||
msg = tr("Checking for changes in '%1'").arg(progress._currentDiscoveredFolder);
|
||||
if (!progress._currentDiscoveredRemoteFolder.isEmpty()) {
|
||||
msg = tr("Checking for changes in '%1'").arg(progress._currentDiscoveredRemoteFolder);
|
||||
} else if (progress.totalSize() == 0) {
|
||||
quint64 currentFile = progress.currentFile();
|
||||
quint64 totalFileCount = qMax(progress.totalFiles(), currentFile);
|
||||
|
|
|
@ -122,6 +122,10 @@ void Folder::checkLocalPath()
|
|||
{
|
||||
const QFileInfo fi(_definition.localPath);
|
||||
_canonicalLocalPath = fi.canonicalFilePath();
|
||||
#ifdef Q_OS_MAC
|
||||
// Workaround QTBUG-55896 (Should be fixed in Qt 5.8)
|
||||
_canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C);
|
||||
#endif
|
||||
if (_canonicalLocalPath.isEmpty()) {
|
||||
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
|
||||
_canonicalLocalPath = _definition.localPath;
|
||||
|
@ -631,18 +635,17 @@ void Folder::startSync(const QStringList &pathList)
|
|||
setDirtyNetworkLimits();
|
||||
setSyncOptions();
|
||||
|
||||
static qint64 fullLocalDiscoveryInterval = []() {
|
||||
static std::chrono::milliseconds fullLocalDiscoveryInterval = []() {
|
||||
auto interval = ConfigFile().fullLocalDiscoveryInterval();
|
||||
QByteArray env = qgetenv("OWNCLOUD_FULL_LOCAL_DISCOVERY_INTERVAL");
|
||||
if (!env.isEmpty()) {
|
||||
interval = env.toLongLong();
|
||||
interval = std::chrono::milliseconds(env.toLongLong());
|
||||
}
|
||||
return interval;
|
||||
}();
|
||||
if (_folderWatcher && _folderWatcher->isReliable()
|
||||
&& _timeSinceLastFullLocalDiscovery.isValid()
|
||||
&& (fullLocalDiscoveryInterval < 0
|
||||
|| _timeSinceLastFullLocalDiscovery.elapsed() < fullLocalDiscoveryInterval)) {
|
||||
if (_folderWatcher && _folderWatcher->isReliable() && _timeSinceLastFullLocalDiscovery.isValid()
|
||||
&& (fullLocalDiscoveryInterval.count() < 0
|
||||
|| _timeSinceLastFullLocalDiscovery.hasExpired(fullLocalDiscoveryInterval.count()))) {
|
||||
qCInfo(lcFolder) << "Allowing local discovery to read from the database";
|
||||
_engine->setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, _localDiscoveryPaths);
|
||||
|
||||
|
@ -706,7 +709,7 @@ void Folder::setSyncOptions()
|
|||
|
||||
QByteArray targetChunkUploadDurationEnv = qgetenv("OWNCLOUD_TARGET_CHUNK_UPLOAD_DURATION");
|
||||
if (!targetChunkUploadDurationEnv.isEmpty()) {
|
||||
opt._targetChunkUploadDuration = targetChunkUploadDurationEnv.toUInt();
|
||||
opt._targetChunkUploadDuration = std::chrono::milliseconds(targetChunkUploadDurationEnv.toUInt());
|
||||
} else {
|
||||
opt._targetChunkUploadDuration = cfgFile.targetChunkUploadDuration();
|
||||
}
|
||||
|
@ -828,7 +831,7 @@ void Folder::slotSyncFinished(bool success)
|
|||
// all come in.
|
||||
QTimer::singleShot(200, this, &Folder::slotEmitFinishedDelayed);
|
||||
|
||||
_lastSyncDuration = _timeSinceLastSyncStart.elapsed();
|
||||
_lastSyncDuration = std::chrono::milliseconds(_timeSinceLastSyncStart.elapsed());
|
||||
_timeSinceLastSyncDone.start();
|
||||
|
||||
// Increment the follow-up sync counter if necessary.
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <QStringList>
|
||||
#include <QUuid>
|
||||
#include <set>
|
||||
#include <chrono>
|
||||
|
||||
class QThread;
|
||||
class QSettings;
|
||||
|
@ -193,8 +194,8 @@ public:
|
|||
SyncEngine &syncEngine() { return *_engine; }
|
||||
|
||||
RequestEtagJob *etagJob() { return _requestEtagJob; }
|
||||
qint64 msecSinceLastSync() const { return _timeSinceLastSyncDone.elapsed(); }
|
||||
qint64 msecLastSyncDuration() const { return _lastSyncDuration; }
|
||||
std::chrono::milliseconds msecSinceLastSync() const { return std::chrono::milliseconds(_timeSinceLastSyncDone.elapsed()); }
|
||||
std::chrono::milliseconds msecLastSyncDuration() const { return _lastSyncDuration; }
|
||||
int consecutiveFollowUpSyncs() const { return _consecutiveFollowUpSyncs; }
|
||||
int consecutiveFailingSyncs() const { return _consecutiveFailingSyncs; }
|
||||
|
||||
|
@ -355,7 +356,7 @@ private:
|
|||
QElapsedTimer _timeSinceLastSyncDone;
|
||||
QElapsedTimer _timeSinceLastSyncStart;
|
||||
QElapsedTimer _timeSinceLastFullLocalDiscovery;
|
||||
qint64 _lastSyncDuration;
|
||||
std::chrono::milliseconds _lastSyncDuration;
|
||||
|
||||
/// The number of syncs that failed in a row.
|
||||
/// Reset when a sync is successful.
|
||||
|
|
|
@ -59,9 +59,9 @@ FolderMan::FolderMan(QObject *parent)
|
|||
_socketApi.reset(new SocketApi);
|
||||
|
||||
ConfigFile cfg;
|
||||
int polltime = cfg.remotePollInterval();
|
||||
qCInfo(lcFolderMan) << "setting remote poll timer interval to" << polltime << "msec";
|
||||
_etagPollTimer.setInterval(polltime);
|
||||
std::chrono::milliseconds polltime = cfg.remotePollInterval();
|
||||
qCInfo(lcFolderMan) << "setting remote poll timer interval to" << polltime.count() << "msec";
|
||||
_etagPollTimer.setInterval(polltime.count());
|
||||
QObject::connect(&_etagPollTimer, &QTimer::timeout, this, &FolderMan::slotEtagPollTimerTimeout);
|
||||
_etagPollTimer.start();
|
||||
|
||||
|
@ -653,13 +653,13 @@ void FolderMan::startScheduledSyncSoon()
|
|||
|
||||
// Require a pause based on the duration of the last sync run.
|
||||
if (Folder *lastFolder = _lastSyncFolder) {
|
||||
msSinceLastSync = lastFolder->msecSinceLastSync();
|
||||
msSinceLastSync = lastFolder->msecSinceLastSync().count();
|
||||
|
||||
// 1s -> 1.5s pause
|
||||
// 10s -> 5s pause
|
||||
// 1min -> 12s pause
|
||||
// 1h -> 90s pause
|
||||
qint64 pause = qSqrt(lastFolder->msecLastSyncDuration()) / 20.0 * 1000.0;
|
||||
qint64 pause = qSqrt(lastFolder->msecLastSyncDuration().count()) / 20.0 * 1000.0;
|
||||
msDelay = qMax(msDelay, pause);
|
||||
}
|
||||
|
||||
|
@ -724,7 +724,7 @@ void FolderMan::slotStartScheduledFolderSync()
|
|||
void FolderMan::slotEtagPollTimerTimeout()
|
||||
{
|
||||
ConfigFile cfg;
|
||||
int polltime = cfg.remotePollInterval();
|
||||
auto polltime = cfg.remotePollInterval();
|
||||
|
||||
foreach (Folder *f, _folderMap) {
|
||||
if (!f) {
|
||||
|
@ -808,11 +808,10 @@ void FolderMan::slotScheduleFolderByTime()
|
|||
auto msecsSinceSync = f->msecSinceLastSync();
|
||||
|
||||
// Possibly it's just time for a new sync run
|
||||
bool forceSyncIntervalExpired =
|
||||
quint64(msecsSinceSync) > ConfigFile().forceSyncInterval();
|
||||
bool forceSyncIntervalExpired = msecsSinceSync > ConfigFile().forceSyncInterval();
|
||||
if (forceSyncIntervalExpired) {
|
||||
qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
|
||||
<< "because it has been" << msecsSinceSync << "ms "
|
||||
<< "because it has been" << msecsSinceSync.count() << "ms "
|
||||
<< "since the last sync";
|
||||
|
||||
scheduleFolder(f);
|
||||
|
@ -823,16 +822,15 @@ void FolderMan::slotScheduleFolderByTime()
|
|||
bool syncAgain =
|
||||
(f->consecutiveFailingSyncs() > 0 && f->consecutiveFailingSyncs() < 3)
|
||||
|| f->syncEngine().isAnotherSyncNeeded() == DelayedFollowUp;
|
||||
qint64 syncAgainDelay = 10 * 1000; // 10s for the first retry-after-fail
|
||||
auto syncAgainDelay = std::chrono::seconds(10); // 10s for the first retry-after-fail
|
||||
if (f->consecutiveFailingSyncs() > 1)
|
||||
syncAgainDelay = 60 * 1000; // 60s for each further attempt
|
||||
if (syncAgain
|
||||
&& msecsSinceSync > syncAgainDelay) {
|
||||
syncAgainDelay = std::chrono::seconds(60); // 60s for each further attempt
|
||||
if (syncAgain && msecsSinceSync > syncAgainDelay) {
|
||||
qCInfo(lcFolderMan) << "Scheduling folder" << f->alias()
|
||||
<< ", the last" << f->consecutiveFailingSyncs() << "syncs failed"
|
||||
<< ", anotherSyncNeeded" << f->syncEngine().isAnotherSyncNeeded()
|
||||
<< ", last status:" << f->syncResult().statusString()
|
||||
<< ", time since last sync:" << msecsSinceSync;
|
||||
<< ", time since last sync:" << msecsSinceSync.count();
|
||||
|
||||
scheduleFolder(f);
|
||||
continue;
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace OCC {
|
|||
FolderStatusDelegate::FolderStatusDelegate()
|
||||
: QStyledItemDelegate()
|
||||
{
|
||||
m_moreIcon = QIcon(QLatin1String(":/client/resources/more.png"));
|
||||
m_moreIcon = QIcon(QLatin1String(":/client/resources/more.svg"));
|
||||
}
|
||||
|
||||
QString FolderStatusDelegate::addFolderText()
|
||||
|
@ -336,7 +336,6 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
|||
|
||||
{
|
||||
QStyleOptionToolButton btnOpt;
|
||||
//btnOpt.text = QLatin1String("...");
|
||||
btnOpt.state = option.state;
|
||||
btnOpt.state &= ~(QStyle::State_Selected | QStyle::State_HasFocus);
|
||||
btnOpt.state |= QStyle::State_Raised;
|
||||
|
@ -344,7 +343,8 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
|||
btnOpt.subControls = QStyle::SC_ToolButton;
|
||||
btnOpt.rect = optionsButtonVisualRect;
|
||||
btnOpt.icon = m_moreIcon;
|
||||
btnOpt.iconSize = btnOpt.rect.size();
|
||||
int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize);
|
||||
btnOpt.iconSize = QSize(e,e);
|
||||
QApplication::style()->drawComplexControl(QStyle::CC_ToolButton, &btnOpt, painter);
|
||||
}
|
||||
}
|
||||
|
@ -364,10 +364,9 @@ QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection
|
|||
within.setHeight(FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm));
|
||||
|
||||
QStyleOptionToolButton opt;
|
||||
opt.text = QLatin1String("...");
|
||||
QSize textSize = fm.size(Qt::TextShowMnemonic, opt.text);
|
||||
opt.rect.setSize(textSize);
|
||||
QSize size = QApplication::style()->sizeFromContents(QStyle::CT_ToolButton, &opt, textSize).expandedTo(QApplication::globalStrut());
|
||||
int e = QApplication::style()->pixelMetric(QStyle::PM_ButtonIconSize);
|
||||
opt.rect.setSize(QSize(e,e));
|
||||
QSize size = QApplication::style()->sizeFromContents(QStyle::CT_ToolButton, &opt, opt.rect.size()).expandedTo(QApplication::globalStrut());
|
||||
|
||||
int margin = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
|
||||
QRect r(QPoint(within.right() - size.width() - margin,
|
||||
|
|
|
@ -248,9 +248,15 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
|
|||
} else if (status == SyncResult::Undefined) {
|
||||
return theme->syncStateIcon(SyncResult::SyncRunning);
|
||||
} else {
|
||||
// keep the previous icon for the prepare phase.
|
||||
if (status == SyncResult::Problem) {
|
||||
return theme->syncStateIcon(SyncResult::Success);
|
||||
// The "Problem" *result* just means some files weren't
|
||||
// synced, so we show "Success" in these cases. But we
|
||||
// do use the "Problem" *icon* for unresolved conflicts.
|
||||
if (status == SyncResult::Success || status == SyncResult::Problem) {
|
||||
if (f->syncResult().hasUnresolvedConflicts()) {
|
||||
return theme->syncStateIcon(SyncResult::Problem);
|
||||
} else {
|
||||
return theme->syncStateIcon(SyncResult::Success);
|
||||
}
|
||||
} else {
|
||||
return theme->syncStateIcon(status);
|
||||
}
|
||||
|
@ -547,7 +553,7 @@ bool FolderStatusModel::canFetchMore(const QModelIndex &parent) const
|
|||
return false;
|
||||
}
|
||||
auto info = infoForIndex(parent);
|
||||
if (!info || info->_fetched || info->_fetching)
|
||||
if (!info || info->_fetched || info->_fetchingJob)
|
||||
return false;
|
||||
if (info->_hasError) {
|
||||
// Keep showing the error to the user, it will be hidden when the account reconnects
|
||||
|
@ -561,10 +567,9 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
|
|||
{
|
||||
auto info = infoForIndex(parent);
|
||||
|
||||
if (!info || info->_fetched || info->_fetching)
|
||||
if (!info || info->_fetched || info->_fetchingJob)
|
||||
return;
|
||||
info->resetSubs(this, parent);
|
||||
info->_fetching = true;
|
||||
QString path = info->_folder->remotePath();
|
||||
if (info->_path != QLatin1String("/")) {
|
||||
if (!path.endsWith(QLatin1Char('/'))) {
|
||||
|
@ -580,6 +585,7 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
|
|||
}
|
||||
|
||||
LsColJob *job = new LsColJob(_accountState->account(), path, this);
|
||||
info->_fetchingJob = job;
|
||||
job->setProperties(QList<QByteArray>() << "resourcetype"
|
||||
<< "http://owncloud.org/ns:size"
|
||||
<< "http://owncloud.org/ns:permissions"
|
||||
|
@ -626,18 +632,18 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
|
|||
if (!parentInfo) {
|
||||
return;
|
||||
}
|
||||
ASSERT(parentInfo->_fetching); // we should only get a result if we were doing a fetch
|
||||
ASSERT(parentInfo->_fetchingJob == job);
|
||||
ASSERT(parentInfo->_subs.isEmpty());
|
||||
|
||||
if (parentInfo->hasLabel()) {
|
||||
beginRemoveRows(idx, 0, 0);
|
||||
parentInfo->_lastErrorString.clear();
|
||||
parentInfo->_hasError = false;
|
||||
parentInfo->_fetchingLabel = false;
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
parentInfo->_fetching = false;
|
||||
parentInfo->_lastErrorString.clear();
|
||||
parentInfo->_fetchingJob = nullptr;
|
||||
parentInfo->_fetched = true;
|
||||
|
||||
QUrl url = parentInfo->_folder->remoteUrl();
|
||||
|
@ -1229,7 +1235,7 @@ void FolderStatusModel::slotShowFetchProgress()
|
|||
if (it.value().elapsed() > 800) {
|
||||
auto idx = it.key();
|
||||
auto *info = infoForIndex(idx);
|
||||
if (info && info->_fetching) {
|
||||
if (info && info->_fetchingJob) {
|
||||
bool add = !info->hasLabel();
|
||||
if (add) {
|
||||
beginInsertRows(idx, 0, 0);
|
||||
|
@ -1252,7 +1258,7 @@ bool FolderStatusModel::SubFolderInfo::hasLabel() const
|
|||
void FolderStatusModel::SubFolderInfo::resetSubs(FolderStatusModel *model, QModelIndex index)
|
||||
{
|
||||
_fetched = false;
|
||||
_fetching = false;
|
||||
delete _fetchingJob;
|
||||
if (hasLabel()) {
|
||||
model->beginRemoveRows(index, 0, 0);
|
||||
_fetchingLabel = false;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QLoggingCategory>
|
||||
#include <QVector>
|
||||
#include <QElapsedTimer>
|
||||
#include <QPointer>
|
||||
|
||||
class QNetworkReply;
|
||||
namespace OCC {
|
||||
|
@ -28,6 +29,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcFolderStatus)
|
|||
|
||||
class Folder;
|
||||
class ProgressInfo;
|
||||
class LsColJob;
|
||||
|
||||
/**
|
||||
* @brief The FolderStatusModel class
|
||||
|
@ -61,7 +63,6 @@ public:
|
|||
, _size(0)
|
||||
, _isExternal(false)
|
||||
, _fetched(false)
|
||||
, _fetching(false)
|
||||
, _hasError(false)
|
||||
, _fetchingLabel(false)
|
||||
, _isUndecided(false)
|
||||
|
@ -77,7 +78,7 @@ public:
|
|||
bool _isExternal;
|
||||
|
||||
bool _fetched; // If we did the LSCOL for this folder already
|
||||
bool _fetching; // Whether a LSCOL job is currently running
|
||||
QPointer<LsColJob> _fetchingJob; // Currently running LsColJob
|
||||
bool _hasError; // If the last fetching job ended in an error
|
||||
QString _lastErrorString;
|
||||
bool _fetchingLabel; // Whether a 'fetching in progress' label is shown.
|
||||
|
|
|
@ -96,6 +96,20 @@ LogBrowser::LogBrowser(QWidget *parent)
|
|||
|
||||
mainLayout->addWidget(btnbox);
|
||||
|
||||
// button to permanently save logs
|
||||
_permanentLogging = new QCheckBox;
|
||||
_permanentLogging->setText(tr("Permanently save logs"));
|
||||
_permanentLogging->setToolTip(
|
||||
tr("When this option is enabled and no other logging is configured, "
|
||||
"logs will be written to a temporary folder and expire after a few hours. "
|
||||
"This setting persists across client restarts.\n"
|
||||
"\n"
|
||||
"Logs will be written to %1")
|
||||
.arg(Logger::instance()->temporaryFolderLogDirPath()));
|
||||
_permanentLogging->setChecked(ConfigFile().automaticLogDir());
|
||||
btnbox->addButton(_permanentLogging, QDialogButtonBox::ActionRole);
|
||||
connect(_permanentLogging, &QCheckBox::toggled, this, &LogBrowser::togglePermanentLogging);
|
||||
|
||||
// clear button
|
||||
_clearBtn = new QPushButton;
|
||||
_clearBtn->setText(tr("Clear"));
|
||||
|
@ -216,4 +230,18 @@ void LogBrowser::slotClearLog()
|
|||
_logWidget->clear();
|
||||
}
|
||||
|
||||
void LogBrowser::togglePermanentLogging(bool enabled)
|
||||
{
|
||||
ConfigFile().setAutomaticLogDir(enabled);
|
||||
|
||||
auto logger = Logger::instance();
|
||||
if (enabled) {
|
||||
if (!logger->isLoggingToFile()) {
|
||||
logger->setupTemporaryFolderLogDir();
|
||||
}
|
||||
} else {
|
||||
logger->disableTemporaryFolderLogDir();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -66,11 +66,13 @@ protected slots:
|
|||
void search(const QString &);
|
||||
void slotSave();
|
||||
void slotClearLog();
|
||||
void togglePermanentLogging(bool enabled);
|
||||
|
||||
private:
|
||||
LogWidget *_logWidget;
|
||||
QLineEdit *_findTermEdit;
|
||||
QCheckBox *_logDebugCheckBox;
|
||||
QCheckBox *_permanentLogging;
|
||||
QPushButton *_saveBtn;
|
||||
QPushButton *_clearBtn;
|
||||
QLabel *_statusLabel;
|
||||
|
|
|
@ -217,4 +217,16 @@ void NetworkSettings::checkEmptyProxyHost()
|
|||
}
|
||||
}
|
||||
|
||||
void NetworkSettings::showEvent(QShowEvent *event)
|
||||
{
|
||||
if (!event->spontaneous()
|
||||
&& _ui->manualProxyRadioButton->isChecked()
|
||||
&& _ui->hostLineEdit->text().isEmpty()) {
|
||||
_ui->noProxyRadioButton->setChecked(true);
|
||||
checkEmptyProxyHost();
|
||||
}
|
||||
|
||||
QWidget::showEvent(event);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -35,7 +35,7 @@ class NetworkSettings : public QWidget
|
|||
public:
|
||||
explicit NetworkSettings(QWidget *parent = 0);
|
||||
~NetworkSettings();
|
||||
QSize sizeHint() const;
|
||||
QSize sizeHint() const override;
|
||||
|
||||
private slots:
|
||||
void saveProxySettings();
|
||||
|
@ -44,6 +44,9 @@ private slots:
|
|||
/// Red marking of host field if empty and enabled
|
||||
void checkEmptyProxyHost();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
void loadProxySettings();
|
||||
void loadBWLimitSettings();
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include "ocsshareejob.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
OcsShareeJob::OcsShareeJob(AccountPtr account)
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <QList>
|
||||
#include <QPair>
|
||||
|
||||
class QJsonDocument;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
|
|
|
@ -203,7 +203,10 @@ void ownCloudGui::slotSyncStateChange(Folder *folder)
|
|||
|
||||
qCInfo(lcApplication) << "Sync state changed for folder " << folder->remoteUrl().toString() << ": " << result.statusString();
|
||||
|
||||
if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
|
||||
if (result.status() == SyncResult::Success
|
||||
|| result.status() == SyncResult::Problem
|
||||
|| result.status() == SyncResult::SyncAbortRequested
|
||||
|| result.status() == SyncResult::Error) {
|
||||
Logger::instance()->enterNextLogFile();
|
||||
}
|
||||
|
||||
|
@ -1118,7 +1121,7 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
|
|||
}
|
||||
|
||||
|
||||
void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath)
|
||||
void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage)
|
||||
{
|
||||
const auto folder = FolderMan::instance()->folderForPath(localPath);
|
||||
if (!folder) {
|
||||
|
@ -1162,7 +1165,7 @@ void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &l
|
|||
w = _shareDialogs[localPath];
|
||||
} else {
|
||||
qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions;
|
||||
w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId());
|
||||
w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId(), startPage);
|
||||
w->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
_shareDialogs[localPath] = w;
|
||||
|
|
|
@ -40,6 +40,11 @@ class Application;
|
|||
class LogBrowser;
|
||||
class AccountState;
|
||||
|
||||
enum class ShareDialogStartPage {
|
||||
UsersAndGroups,
|
||||
PublicLinks,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ownCloudGui class
|
||||
* @ingroup gui
|
||||
|
@ -104,7 +109,7 @@ public slots:
|
|||
* localPath is the absolute local path to it (so not relative
|
||||
* to the folder).
|
||||
*/
|
||||
void slotShowShareDialog(const QString &sharePath, const QString &localPath);
|
||||
void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
|
||||
|
||||
void slotRemoveDestroyedShareDialogs();
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
const QString &localPath,
|
||||
SharePermissions maxSharingPermissions,
|
||||
const QByteArray &numericFileId,
|
||||
ShareDialogStartPage startPage,
|
||||
QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, _ui(new Ui::ShareDialog)
|
||||
|
@ -47,6 +48,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||
, _localPath(localPath)
|
||||
, _maxSharingPermissions(maxSharingPermissions)
|
||||
, _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
|
||||
, _startPage(startPage)
|
||||
, _linkWidget(NULL)
|
||||
, _userGroupWidget(NULL)
|
||||
, _progressIndicator(NULL)
|
||||
|
@ -218,6 +220,9 @@ void ShareDialog::showSharingUi()
|
|||
_linkWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
|
||||
_ui->shareWidgets->addTab(_linkWidget, tr("Public Links"));
|
||||
_linkWidget->getShares();
|
||||
|
||||
if (_startPage == ShareDialogStartPage::PublicLinks)
|
||||
_ui->shareWidgets->setCurrentWidget(_linkWidget);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "accountstate.h"
|
||||
#include "sharepermissions.h"
|
||||
#include "owncloudgui.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QString>
|
||||
|
@ -44,6 +45,7 @@ public:
|
|||
const QString &localPath,
|
||||
SharePermissions maxSharingPermissions,
|
||||
const QByteArray &numericFileId,
|
||||
ShareDialogStartPage startPage,
|
||||
QWidget *parent = 0);
|
||||
~ShareDialog();
|
||||
|
||||
|
@ -64,6 +66,7 @@ private:
|
|||
SharePermissions _maxSharingPermissions;
|
||||
QByteArray _numericFileId;
|
||||
QString _privateLinkUrl;
|
||||
ShareDialogStartPage _startPage;
|
||||
|
||||
ShareLinkWidget *_linkWidget;
|
||||
ShareUserGroupWidget *_userGroupWidget;
|
||||
|
|
|
@ -197,20 +197,23 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
|||
const QString versionString = _account->serverVersion();
|
||||
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
|
||||
|
||||
// Preserve the previous selection
|
||||
QString selectedShareId;
|
||||
if (auto share = selectedShare()) {
|
||||
selectedShareId = share->getId();
|
||||
}
|
||||
// ...except if selection should move to a new share
|
||||
// Select the share that was previously selected,
|
||||
// except if an explicit override was asked for
|
||||
QString reselectShareId = _selectedShareId;
|
||||
if (!_newShareOverrideSelectionId.isEmpty()) {
|
||||
selectedShareId = _newShareOverrideSelectionId;
|
||||
reselectShareId = _newShareOverrideSelectionId;
|
||||
_newShareOverrideSelectionId.clear();
|
||||
}
|
||||
|
||||
auto table = _ui->linkShares;
|
||||
|
||||
// Wipe the table without updating the ui elements, we
|
||||
// might want their state untouched if the same share ends
|
||||
// up being selected
|
||||
disconnect(table, &QTableWidget::itemSelectionChanged, this, &ShareLinkWidget::slotShareSelectionChanged);
|
||||
table->clearContents();
|
||||
table->setRowCount(0);
|
||||
connect(table, &QTableWidget::itemSelectionChanged, this, &ShareLinkWidget::slotShareSelectionChanged);
|
||||
|
||||
auto deleteIcon = QIcon::fromTheme(QLatin1String("user-trash"),
|
||||
QIcon(QLatin1String(":/client/resources/delete.png")));
|
||||
|
@ -256,14 +259,20 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
|||
table->setCellWidget(row, 2, deleteButton);
|
||||
|
||||
// Reestablish the previous selection
|
||||
if (selectedShareId == share->getId()) {
|
||||
if (reselectShareId == share->getId()) {
|
||||
table->selectRow(row);
|
||||
}
|
||||
}
|
||||
|
||||
// Select the first share by default
|
||||
if (!selectedShare() && table->rowCount() != 0) {
|
||||
table->selectRow(0);
|
||||
if (!selectedShare()) {
|
||||
if (table->rowCount() != 0) {
|
||||
// Select the first share by default
|
||||
table->selectRow(0);
|
||||
} else {
|
||||
// explicitly note the deselection,
|
||||
// since this was not triggered on table clear above
|
||||
slotShareSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_namesSupported) {
|
||||
|
@ -283,6 +292,7 @@ void ShareLinkWidget::slotShareSelectionChanged()
|
|||
|
||||
auto share = selectedShare();
|
||||
if (!share) {
|
||||
_selectedShareId.clear();
|
||||
_ui->shareProperties->setEnabled(false);
|
||||
_ui->radio_readOnly->setChecked(false);
|
||||
_ui->radio_readWrite->setChecked(false);
|
||||
|
@ -291,6 +301,8 @@ void ShareLinkWidget::slotShareSelectionChanged()
|
|||
_ui->checkBox_password->setChecked(false);
|
||||
return;
|
||||
}
|
||||
bool selectionUnchanged = _selectedShareId == share->getId();
|
||||
_selectedShareId = share->getId();
|
||||
|
||||
_ui->shareProperties->setEnabled(true);
|
||||
|
||||
|
@ -304,17 +316,17 @@ void ShareLinkWidget::slotShareSelectionChanged()
|
|||
|
||||
// Password state
|
||||
_ui->checkBox_password->setText(tr("P&assword protect"));
|
||||
if (share->isPasswordSet()) {
|
||||
_ui->checkBox_password->setChecked(true);
|
||||
_ui->lineEdit_password->setEnabled(true);
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
if (!selectionUnchanged) {
|
||||
if (share->isPasswordSet()) {
|
||||
_ui->checkBox_password->setChecked(true);
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
_ui->lineEdit_password->setEnabled(true);
|
||||
} else {
|
||||
_ui->checkBox_password->setChecked(false);
|
||||
_ui->lineEdit_password->setPlaceholderText(QString());
|
||||
_ui->lineEdit_password->setEnabled(false);
|
||||
}
|
||||
_ui->lineEdit_password->setText(QString());
|
||||
_ui->lineEdit_password->setEnabled(true);
|
||||
_ui->pushButton_setPassword->setEnabled(false);
|
||||
} else {
|
||||
_ui->checkBox_password->setChecked(false);
|
||||
_ui->lineEdit_password->setPlaceholderText(QString());
|
||||
_ui->lineEdit_password->setEnabled(false);
|
||||
_ui->pushButton_setPassword->setEnabled(false);
|
||||
}
|
||||
|
||||
|
@ -420,14 +432,27 @@ void ShareLinkWidget::setPassword(const QString &password)
|
|||
|
||||
void ShareLinkWidget::slotPasswordSet()
|
||||
{
|
||||
auto share = selectedShare();
|
||||
if (sender() != share.data())
|
||||
return;
|
||||
|
||||
_pi_password->stopAnimation();
|
||||
_ui->checkBox_password->setEnabled(true);
|
||||
_ui->lineEdit_password->setText(QString());
|
||||
_ui->lineEdit_password->setPlaceholderText(tr("Password Protected"));
|
||||
if (share->isPasswordSet()) {
|
||||
_ui->lineEdit_password->setPlaceholderText("********");
|
||||
_ui->lineEdit_password->setEnabled(true);
|
||||
} else {
|
||||
_ui->lineEdit_password->setPlaceholderText(QString());
|
||||
_ui->lineEdit_password->setEnabled(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* When setting/deleting a password from a share the old share is
|
||||
* deleted and a new one is created. So we need to refetch the shares
|
||||
* at this point.
|
||||
*
|
||||
* NOTE: I don't see this happening with oC > 10
|
||||
*/
|
||||
getShares();
|
||||
}
|
||||
|
|
|
@ -122,6 +122,11 @@ private:
|
|||
bool _expiryRequired;
|
||||
bool _namesSupported;
|
||||
|
||||
// For maintaining the selection and temporary ui state
|
||||
// when getShares() finishes, but the selection didn't
|
||||
// change.
|
||||
QString _selectedShareId;
|
||||
|
||||
// When a new share is created, we want to select it
|
||||
// the next time getShares() finishes. This stores its id.
|
||||
QString _newShareOverrideSelectionId;
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>394</width>
|
||||
<height>534</height>
|
||||
<width>441</width>
|
||||
<height>568</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -94,7 +94,7 @@
|
|||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="7" column="0">
|
||||
<item row="6" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_expire">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
|
@ -115,7 +115,7 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="4" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
|
@ -142,7 +142,7 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_password">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_password">
|
||||
|
@ -173,13 +173,6 @@
|
|||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Link properties:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QWidget" name="widget_editing" native="true">
|
||||
<layout class="QGridLayout" name="layout_editing">
|
||||
<property name="leftMargin">
|
||||
|
@ -203,7 +196,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Users can view and download contents.</string>
|
||||
<string>Recipients can view or download contents.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
@ -252,14 +245,14 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radio_readOnly">
|
||||
<property name="text">
|
||||
<string>Read only</string>
|
||||
<string>Download / View</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QRadioButton" name="radio_readWrite">
|
||||
<property name="text">
|
||||
<string>Read && Write</string>
|
||||
<string>Download / View / Upload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -272,7 +265,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Users can view, download, edit and upload contents.</string>
|
||||
<string>Recipients can view, download, edit, delete and upload contents.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
|
|
|
@ -207,7 +207,7 @@ void LinkShare::setPassword(const QString &password)
|
|||
|
||||
void LinkShare::slotPasswordSet(const QJsonDocument &, const QVariant &value)
|
||||
{
|
||||
_passwordSet = value.toString() == "";
|
||||
_passwordSet = value.toString() != "";
|
||||
emit passwordSet();
|
||||
}
|
||||
|
||||
|
|
|
@ -381,7 +381,7 @@ ShareUserLine::ShareUserLine(QSharedPointer<Share> share,
|
|||
_ui->permissionToolButton->setMenu(menu);
|
||||
_ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
|
||||
|
||||
QIcon icon(QLatin1String(":/client/resources/more.png"));
|
||||
QIcon icon(QLatin1String(":/client/resources/more.svg"));
|
||||
_ui->permissionToolButton->setIcon(icon);
|
||||
|
||||
// If there's only a single entry in the detailed permission menu, hide it
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
#include "capabilities.h"
|
||||
#include "common/asserts.h"
|
||||
#include "guiutility.h"
|
||||
#ifndef OWNCLOUD_TEST
|
||||
#include "sharemanager.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <QBitArray>
|
||||
|
@ -45,6 +48,7 @@
|
|||
#include <QApplication>
|
||||
#include <QLocalSocket>
|
||||
#include <QStringBuilder>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <QClipboard>
|
||||
|
||||
|
@ -81,7 +85,9 @@ static QString buildMessage(const QString &verb, const QString &path, const QStr
|
|||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcSocketApi, "nextcloud.gui.socketapi", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcSocketApi, "gui.socketapi", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcPublicLink, "gui.socketapi.publiclink", QtInfoMsg)
|
||||
|
||||
|
||||
class BloomFilter
|
||||
{
|
||||
|
@ -181,6 +187,10 @@ SocketApi::SocketApi(QObject *parent)
|
|||
// Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
|
||||
// Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
|
||||
socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi";
|
||||
#ifdef Q_OS_MAC
|
||||
// Tell Finder to use the Extension (checking it from System Preferences -> Extensions)
|
||||
system("pluginkit -e use -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
|
||||
#endif
|
||||
} else if (Utility::isLinux() || Utility::isBSD()) {
|
||||
QString runtimeDir;
|
||||
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
|
@ -218,10 +228,17 @@ SocketApi::~SocketApi()
|
|||
// All remaining sockets will be destroyed with _localServer, their parent
|
||||
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
|
||||
_listeners.clear();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Unload the extension (uncheck from System Preferences -> Extensions)
|
||||
system("pluginkit -e ignore -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
|
||||
#endif
|
||||
}
|
||||
|
||||
void SocketApi::slotNewConnection()
|
||||
{
|
||||
// Note that on macOS this is not actually a line-based QIODevice, it's a SocketApiSocket which is our
|
||||
// custom message based macOS IPC.
|
||||
QIODevice *socket = _localServer.nextPendingConnection();
|
||||
|
||||
if (!socket) {
|
||||
|
@ -341,6 +358,49 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait)
|
|||
}
|
||||
}
|
||||
|
||||
void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage)
|
||||
{
|
||||
auto theme = Theme::instance();
|
||||
|
||||
auto fileData = FileData::get(localFile);
|
||||
auto shareFolder = fileData.folder;
|
||||
if (!shareFolder) {
|
||||
const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
|
||||
// files that are not within a sync folder are not synced.
|
||||
listener->sendMessage(message);
|
||||
} else if (!shareFolder->accountState()->isConnected()) {
|
||||
const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile);
|
||||
// if the folder isn't connected, don't open the share dialog
|
||||
listener->sendMessage(message);
|
||||
} else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
|
||||
const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
SyncFileStatus fileStatus = fileData.syncFileStatus();
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
|
||||
const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &remotePath = fileData.accountRelativePath;
|
||||
|
||||
// Can't share root folder
|
||||
if (remotePath == "/") {
|
||||
const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
|
||||
emit shareCommandReceived(remotePath, fileData.localPath, startPage);
|
||||
}
|
||||
}
|
||||
|
||||
void SocketApi::broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus)
|
||||
{
|
||||
QString msg = buildMessage(QLatin1String("STATUS"), systemPath, fileStatus.toSocketAPIString());
|
||||
|
@ -361,23 +421,17 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
|
|||
{
|
||||
QString statusString;
|
||||
|
||||
Folder *syncFolder = FolderMan::instance()->folderForPath(argument);
|
||||
if (!syncFolder) {
|
||||
auto fileData = FileData::get(argument);
|
||||
if (!fileData.folder) {
|
||||
// this can happen in offline mode e.g.: nothing to worry about
|
||||
statusString = QLatin1String("NOP");
|
||||
} else {
|
||||
QString systemPath = QDir::cleanPath(argument);
|
||||
if (systemPath.endsWith(QLatin1Char('/'))) {
|
||||
systemPath.truncate(systemPath.length() - 1);
|
||||
qCWarning(lcSocketApi) << "Removed trailing slash for directory: " << systemPath << "Status pushes won't have one.";
|
||||
}
|
||||
// The user probably visited this directory in the file shell.
|
||||
// Let the listener know that it should now send status pushes for sibblings of this file.
|
||||
QString directory = systemPath.left(systemPath.lastIndexOf('/'));
|
||||
QString directory = fileData.localPath.left(fileData.localPath.lastIndexOf('/'));
|
||||
listener->registerMonitoredDirectory(qHash(directory));
|
||||
|
||||
QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1);
|
||||
SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(relativePath);
|
||||
SyncFileStatus fileStatus = fileData.syncFileStatus();
|
||||
statusString = fileStatus.toSocketAPIString();
|
||||
}
|
||||
|
||||
|
@ -387,46 +441,12 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
|
|||
|
||||
void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
auto theme = Theme::instance();
|
||||
processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups);
|
||||
}
|
||||
|
||||
Folder *shareFolder = FolderMan::instance()->folderForPath(localFile);
|
||||
if (!shareFolder) {
|
||||
const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
|
||||
// files that are not within a sync folder are not synced.
|
||||
listener->sendMessage(message);
|
||||
} else if (!shareFolder->accountState()->isConnected()) {
|
||||
const QString message = QLatin1String("SHARE:NOTCONNECTED:") + QDir::toNativeSeparators(localFile);
|
||||
// if the folder isn't connected, don't open the share dialog
|
||||
listener->sendMessage(message);
|
||||
} else if (!theme->linkSharing() && (!theme->userGroupSharing() || shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
|
||||
const QString message = QLatin1String("SHARE:NOP:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
const QString localFileClean = QDir::cleanPath(localFile);
|
||||
const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
|
||||
SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
|
||||
const QString message = QLatin1String("SHARE:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString remotePath = QDir(shareFolder->remotePath()).filePath(file);
|
||||
|
||||
// Can't share root folder
|
||||
if (remotePath == "/") {
|
||||
const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
|
||||
emit shareCommandReceived(remotePath, localFileClean);
|
||||
}
|
||||
void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks);
|
||||
}
|
||||
|
||||
void SocketApi::command_VERSION(const QString &, SocketListener *listener)
|
||||
|
@ -436,50 +456,49 @@ void SocketApi::command_VERSION(const QString &, SocketListener *listener)
|
|||
|
||||
void SocketApi::command_SHARE_STATUS(const QString &localFile, SocketListener *listener)
|
||||
{
|
||||
Folder *shareFolder = FolderMan::instance()->folderForPath(localFile);
|
||||
|
||||
if (!shareFolder) {
|
||||
auto fileData = FileData::get(localFile);
|
||||
if (!fileData.folder) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:NOP:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length() + 1);
|
||||
SyncFileStatus fileStatus = shareFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
SyncFileStatus fileStatus = fileData.syncFileStatus();
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::StatusUpToDate) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
const Capabilities capabilities = fileData.folder->accountState()->account()->capabilities();
|
||||
|
||||
if (!capabilities.shareAPI()) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
auto theme = Theme::instance();
|
||||
QString available;
|
||||
|
||||
if (theme->userGroupSharing()) {
|
||||
available = "USER,GROUP";
|
||||
}
|
||||
|
||||
const Capabilities capabilities = shareFolder->accountState()->account()->capabilities();
|
||||
if (theme->linkSharing() && capabilities.sharePublicLink()) {
|
||||
if (available.isEmpty()) {
|
||||
available = "LINK";
|
||||
} else {
|
||||
available += ",LINK";
|
||||
}
|
||||
}
|
||||
|
||||
if (!capabilities.shareAPI()) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:DISABLED:") + QDir::toNativeSeparators(localFile);
|
||||
if (available.isEmpty()) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
auto theme = Theme::instance();
|
||||
QString available;
|
||||
|
||||
if (theme->userGroupSharing()) {
|
||||
available = "USER,GROUP";
|
||||
}
|
||||
|
||||
if (theme->linkSharing() && capabilities.sharePublicLink()) {
|
||||
if (available.isEmpty()) {
|
||||
available = "LINK";
|
||||
} else {
|
||||
available += ",LINK";
|
||||
}
|
||||
}
|
||||
|
||||
if (available.isEmpty()) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:DISABLED") + ":" + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
} else {
|
||||
const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
}
|
||||
const QString message = QLatin1String("SHARE_STATUS:") + available + ":" + QDir::toNativeSeparators(localFile);
|
||||
listener->sendMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -489,50 +508,156 @@ void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listen
|
|||
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is ownCloud").arg(Theme::instance()->appNameGUI()));
|
||||
}
|
||||
|
||||
// Fetches the private link url asynchronously and then calls the target slot
|
||||
static void fetchPrivateLinkUrlHelper(const QString &localFile, SocketApi *target, void (SocketApi::*targetFun)(const QString &url) const)
|
||||
// don't pull the share manager into socketapi unittests
|
||||
#ifndef OWNCLOUD_TEST
|
||||
|
||||
class GetOrCreatePublicLinkShare : public QObject
|
||||
{
|
||||
Folder *shareFolder = FolderMan::instance()->folderForPath(localFile);
|
||||
if (!shareFolder) {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetOrCreatePublicLinkShare(const AccountPtr &account, const QString &localFile,
|
||||
std::function<void(const QString &link)> targetFun, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _shareManager(account)
|
||||
, _localFile(localFile)
|
||||
, _targetFun(targetFun)
|
||||
{
|
||||
connect(&_shareManager, &ShareManager::sharesFetched,
|
||||
this, &GetOrCreatePublicLinkShare::sharesFetched);
|
||||
connect(&_shareManager, &ShareManager::linkShareCreated,
|
||||
this, &GetOrCreatePublicLinkShare::linkShareCreated);
|
||||
connect(&_shareManager, &ShareManager::serverError,
|
||||
this, &GetOrCreatePublicLinkShare::serverError);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
qCDebug(lcPublicLink) << "Fetching shares";
|
||||
_shareManager.fetchShares(_localFile);
|
||||
}
|
||||
|
||||
private slots:
|
||||
void sharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
{
|
||||
auto shareName = SocketApi::tr("Context menu share");
|
||||
// If there already is a context menu share, reuse it
|
||||
for (const auto &share : shares) {
|
||||
const auto linkShare = qSharedPointerDynamicCast<LinkShare>(share);
|
||||
if (!linkShare)
|
||||
continue;
|
||||
|
||||
if (linkShare->getName() == shareName) {
|
||||
qCDebug(lcPublicLink) << "Found existing share, reusing";
|
||||
return success(linkShare->getLink().toString());
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise create a new one
|
||||
qCDebug(lcPublicLink) << "Creating new share";
|
||||
_shareManager.createLinkShare(_localFile, shareName, QString());
|
||||
}
|
||||
|
||||
void linkShareCreated(const QSharedPointer<LinkShare> &share)
|
||||
{
|
||||
qCDebug(lcPublicLink) << "New share created";
|
||||
success(share->getLink().toString());
|
||||
}
|
||||
|
||||
void serverError(int code, const QString &message)
|
||||
{
|
||||
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
|
||||
QMessageBox::warning(
|
||||
0,
|
||||
tr("Sharing error"),
|
||||
tr("Could not retrieve or create the public link share. Error:\n\n%1").arg(message),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::NoButton);
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
private:
|
||||
void success(const QString &link)
|
||||
{
|
||||
_targetFun(link);
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
ShareManager _shareManager;
|
||||
QString _localFile;
|
||||
std::function<void(const QString &url)> _targetFun;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class GetOrCreatePublicLinkShare : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetOrCreatePublicLinkShare(const AccountPtr &, const QString &,
|
||||
std::function<void(const QString &link)>, QObject *)
|
||||
{
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *)
|
||||
{
|
||||
auto fileData = FileData::get(localFile);
|
||||
if (!fileData.folder)
|
||||
return;
|
||||
|
||||
AccountPtr account = fileData.folder->accountState()->account();
|
||||
auto job = new GetOrCreatePublicLinkShare(account, fileData.accountRelativePath, [](const QString &url) { copyUrlToClipboard(url); }, this);
|
||||
job->run();
|
||||
}
|
||||
|
||||
// Fetches the private link url asynchronously and then calls the target slot
|
||||
void SocketApi::fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun)
|
||||
{
|
||||
auto fileData = FileData::get(localFile);
|
||||
if (!fileData.folder) {
|
||||
qCWarning(lcSocketApi) << "Unknown path" << localFile;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString localFileClean = QDir::cleanPath(localFile);
|
||||
const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
|
||||
|
||||
AccountPtr account = shareFolder->accountState()->account();
|
||||
|
||||
SyncJournalFileRecord rec;
|
||||
if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
|
||||
auto record = fileData.journalRecord();
|
||||
if (!record.isValid())
|
||||
return;
|
||||
|
||||
fetchPrivateLinkUrl(account, file, rec.numericFileId(), target, [=](const QString &url) {
|
||||
(target->*targetFun)(url);
|
||||
});
|
||||
fetchPrivateLinkUrl(
|
||||
fileData.folder->accountState()->account(),
|
||||
fileData.accountRelativePath,
|
||||
record.numericFileId(),
|
||||
this,
|
||||
targetFun);
|
||||
}
|
||||
|
||||
void SocketApi::command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *)
|
||||
{
|
||||
fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::copyPrivateLinkToClipboard);
|
||||
fetchPrivateLinkUrlHelper(localFile, &SocketApi::copyUrlToClipboard);
|
||||
}
|
||||
|
||||
void SocketApi::command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *)
|
||||
{
|
||||
fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::emailPrivateLink);
|
||||
fetchPrivateLinkUrlHelper(localFile, &SocketApi::emailPrivateLink);
|
||||
}
|
||||
|
||||
void SocketApi::command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *)
|
||||
{
|
||||
fetchPrivateLinkUrlHelper(localFile, this, &SocketApi::openPrivateLink);
|
||||
fetchPrivateLinkUrlHelper(localFile, &SocketApi::openPrivateLink);
|
||||
}
|
||||
|
||||
void SocketApi::copyPrivateLinkToClipboard(const QString &link) const
|
||||
void SocketApi::copyUrlToClipboard(const QString &link)
|
||||
{
|
||||
QApplication::clipboard()->setText(link);
|
||||
}
|
||||
|
||||
void SocketApi::emailPrivateLink(const QString &link) const
|
||||
void SocketApi::emailPrivateLink(const QString &link)
|
||||
{
|
||||
Utility::openEmailComposer(
|
||||
tr("I shared something with you"),
|
||||
|
@ -540,7 +665,7 @@ void SocketApi::emailPrivateLink(const QString &link) const
|
|||
0);
|
||||
}
|
||||
|
||||
void OCC::SocketApi::openPrivateLink(const QString &link) const
|
||||
void OCC::SocketApi::openPrivateLink(const QString &link)
|
||||
{
|
||||
Utility::openBrowser(link, nullptr);
|
||||
}
|
||||
|
@ -562,37 +687,89 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis
|
|||
listener->sendMessage(QString("GET_STRINGS:END"));
|
||||
}
|
||||
|
||||
void SocketApi::sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener)
|
||||
{
|
||||
auto record = fileData.journalRecord();
|
||||
bool isOnTheServer = record.isValid();
|
||||
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
|
||||
|
||||
auto capabilities = fileData.folder->accountState()->account()->capabilities();
|
||||
auto theme = Theme::instance();
|
||||
if (!capabilities.shareAPI() || !(theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink())))
|
||||
return;
|
||||
|
||||
// If sharing is globally disabled, do not show any sharing entries.
|
||||
// If there is no permission to share for this file, add a disabled entry saying so
|
||||
if (isOnTheServer && !record._remotePerm.isNull() && !record._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed"));
|
||||
} else {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share..."));
|
||||
|
||||
// Do we have public links?
|
||||
bool publicLinksEnabled = theme->linkSharing() && capabilities.sharePublicLink();
|
||||
|
||||
// Is is possible to create a public link without user choices?
|
||||
bool canCreateDefaultPublicLink = publicLinksEnabled
|
||||
&& !capabilities.sharePublicLinkEnforceExpireDate()
|
||||
&& !capabilities.sharePublicLinkEnforcePassword();
|
||||
|
||||
if (canCreateDefaultPublicLink) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PUBLIC_LINK") + flagString + tr("Copy public link to clipboard"));
|
||||
} else if (publicLinksEnabled) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:MANAGE_PUBLIC_LINKS") + flagString + tr("Copy public link to clipboard"));
|
||||
}
|
||||
}
|
||||
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard"));
|
||||
|
||||
// Disabled: only providing email option for private links would look odd,
|
||||
// and the copy option is more general.
|
||||
//listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email..."));
|
||||
}
|
||||
|
||||
SocketApi::FileData SocketApi::FileData::get(const QString &localFile)
|
||||
{
|
||||
FileData data;
|
||||
|
||||
data.localPath = QDir::cleanPath(localFile);
|
||||
if (data.localPath.endsWith(QLatin1Char('/')))
|
||||
data.localPath.chop(1);
|
||||
|
||||
data.folder = FolderMan::instance()->folderForPath(data.localPath);
|
||||
if (!data.folder)
|
||||
return data;
|
||||
|
||||
data.folderRelativePath = data.localPath.mid(data.folder->cleanPath().length() + 1);
|
||||
data.accountRelativePath = QDir(data.folder->remotePath()).filePath(data.folderRelativePath);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
SyncFileStatus SocketApi::FileData::syncFileStatus() const
|
||||
{
|
||||
if (!folder)
|
||||
return SyncFileStatus::StatusNone;
|
||||
return folder->syncEngine().syncFileStatusTracker().fileStatus(folderRelativePath);
|
||||
}
|
||||
|
||||
SyncJournalFileRecord SocketApi::FileData::journalRecord() const
|
||||
{
|
||||
SyncJournalFileRecord record;
|
||||
if (!folder)
|
||||
return record;
|
||||
folder->journalDb()->getFileRecord(folderRelativePath, &record);
|
||||
return record;
|
||||
}
|
||||
|
||||
void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListener *listener)
|
||||
{
|
||||
listener->sendMessage(QString("GET_MENU_ITEMS:BEGIN"));
|
||||
bool hasSeveralFiles = argument.contains(QLatin1Char('\x1e')); // Record Separator
|
||||
Folder *syncFolder = hasSeveralFiles ? nullptr : FolderMan::instance()->folderForPath(argument);
|
||||
if (syncFolder && syncFolder->accountState()->isConnected()) {
|
||||
QString systemPath = QDir::cleanPath(argument);
|
||||
if (systemPath.endsWith(QLatin1Char('/'))) {
|
||||
systemPath.truncate(systemPath.length() - 1);
|
||||
}
|
||||
|
||||
SyncJournalFileRecord rec;
|
||||
QString relativePath = systemPath.mid(syncFolder->cleanPath().length() + 1);
|
||||
// If the file is on the DB, it is on the server
|
||||
bool isOnTheServer = syncFolder->journalDb()->getFileRecord(relativePath, &rec) && rec.isValid();
|
||||
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
|
||||
|
||||
auto capabilities = syncFolder->accountState()->account()->capabilities();
|
||||
auto theme = Theme::instance();
|
||||
if (capabilities.shareAPI() && (theme->userGroupSharing() || (theme->linkSharing() && capabilities.sharePublicLink()))) {
|
||||
// If sharing is globally disabled, do not show any sharing entries.
|
||||
// If there is no permission to share for this file, add a disabled entry saying so
|
||||
if (isOnTheServer && !rec._remotePerm.isNull() && !rec._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:DISABLED:d:") + tr("Resharing this file is not allowed"));
|
||||
} else {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:SHARE") + flagString + tr("Share..."));
|
||||
}
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:EMAIL_PRIVATE_LINK") + flagString + tr("Send private link by email..."));
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:COPY_PRIVATE_LINK") + flagString + tr("Copy private link to clipboard"));
|
||||
}
|
||||
|
||||
FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument);
|
||||
bool isOnTheServer = fileData.journalRecord().isValid();
|
||||
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
|
||||
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
|
||||
sendSharingContextMenuOptions(fileData, listener);
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
|
||||
}
|
||||
listener->sendMessage(QString("GET_MENU_ITEMS:END"));
|
||||
|
@ -607,3 +784,5 @@ QString SocketApi::buildRegisterPathMessage(const QString &path)
|
|||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
#include "socketapi.moc"
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
#include "syncfileitem.h"
|
||||
#include "syncfilestatus.h"
|
||||
// #include "ownsql.h"
|
||||
#include "sharedialog.h" // for the ShareDialogStartPage
|
||||
#include "common/syncjournalfilerecord.h"
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
#include "socketapisocket_mac.h"
|
||||
|
@ -56,7 +57,7 @@ public slots:
|
|||
void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus);
|
||||
|
||||
signals:
|
||||
void shareCommandReceived(const QString &sharePath, const QString &localPath);
|
||||
void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
|
||||
|
||||
private slots:
|
||||
void slotNewConnection();
|
||||
|
@ -64,13 +65,31 @@ private slots:
|
|||
void slotSocketDestroyed(QObject *obj);
|
||||
void slotReadSocket();
|
||||
|
||||
void copyPrivateLinkToClipboard(const QString &link) const;
|
||||
void emailPrivateLink(const QString &link) const;
|
||||
void openPrivateLink(const QString &link) const;
|
||||
static void copyUrlToClipboard(const QString &link);
|
||||
static void emailPrivateLink(const QString &link);
|
||||
static void openPrivateLink(const QString &link);
|
||||
|
||||
private:
|
||||
// Helper structure for getting information on a file
|
||||
// based on its local path - used for nearly all remote
|
||||
// actions.
|
||||
struct FileData
|
||||
{
|
||||
static FileData get(const QString &localFile);
|
||||
SyncFileStatus syncFileStatus() const;
|
||||
SyncJournalFileRecord journalRecord() const;
|
||||
|
||||
Folder *folder;
|
||||
QString localPath;
|
||||
QString folderRelativePath;
|
||||
QString accountRelativePath;
|
||||
};
|
||||
|
||||
void broadcastMessage(const QString &msg, bool doWait = false);
|
||||
|
||||
// opens share dialog, sends reply
|
||||
void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage);
|
||||
|
||||
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener);
|
||||
Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener);
|
||||
|
||||
|
@ -81,13 +100,21 @@ private:
|
|||
|
||||
// The context menu actions
|
||||
Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_COPY_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_EMAIL_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
|
||||
Q_INVOKABLE void command_OPEN_PRIVATE_LINK(const QString &localFile, SocketListener *listener);
|
||||
|
||||
// Fetch the private link and call targetFun
|
||||
void fetchPrivateLinkUrlHelper(const QString &localFile, const std::function<void(const QString &url)> &targetFun);
|
||||
|
||||
/** Sends translated/branded strings that may be useful to the integration */
|
||||
Q_INVOKABLE void command_GET_STRINGS(const QString &argument, SocketListener *listener);
|
||||
|
||||
// Sends the context menu options relating to sharing to listener
|
||||
void sendSharingContextMenuOptions(const FileData &fileData, SocketListener *listener);
|
||||
|
||||
/** Send the list of menu item. (added in version 1.1)
|
||||
* argument is a list of files for which the menu should be shown, separated by '\x1e'
|
||||
* Reply with GET_MENU_ITEMS:BEGIN
|
||||
|
|
|
@ -58,7 +58,7 @@ static bool isSelfSigned(const QSslCertificate &certificate)
|
|||
}
|
||||
|
||||
QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
|
||||
const QList<QSslCertificate> &userApproved, int pos)
|
||||
const QList<QSslCertificate> &userApproved, int pos, const QList<QSslCertificate> &systemCaCertificates)
|
||||
{
|
||||
QString cn = QStringList(cert.subjectInfo(QSslCertificate::CommonName)).join(QChar(';'));
|
||||
QString ou = QStringList(cert.subjectInfo(QSslCertificate::OrganizationalUnitName)).join(QChar(';'));
|
||||
|
@ -129,7 +129,7 @@ QMenu *SslButton::buildCertMenu(QMenu *parent, const QSslCertificate &cert,
|
|||
|
||||
QString certId = cn.isEmpty() ? ou : cn;
|
||||
|
||||
if (QSslConfiguration::systemCaCertificates().contains(cert)) {
|
||||
if (systemCaCertificates.contains(cert)) {
|
||||
txt += certId;
|
||||
} else {
|
||||
if (isSelfSigned(cert)) {
|
||||
|
@ -189,6 +189,10 @@ void SslButton::slotUpdateMenu()
|
|||
|
||||
AccountPtr account = _accountState->account();
|
||||
|
||||
if (account->isHttp2Supported()) {
|
||||
_menu->addAction("HTTP/2")->setEnabled(false);
|
||||
}
|
||||
|
||||
if (account->url().scheme() == QLatin1String("https")) {
|
||||
QString sslVersion = account->_sessionCipher.protocolString()
|
||||
+ ", " + account->_sessionCipher.authenticationMethod()
|
||||
|
@ -232,7 +236,7 @@ void SslButton::slotUpdateMenu()
|
|||
it.toBack();
|
||||
int i = 0;
|
||||
while (it.hasPrevious()) {
|
||||
_menu->addMenu(buildCertMenu(_menu, it.previous(), account->approvedCerts(), i));
|
||||
_menu->addMenu(buildCertMenu(_menu, it.previous(), account->approvedCerts(), i, systemCerts));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public slots:
|
|||
|
||||
private:
|
||||
QMenu *buildCertMenu(QMenu *parent, const QSslCertificate &cert,
|
||||
const QList<QSslCertificate> &userApproved, int pos);
|
||||
const QList<QSslCertificate> &userApproved, int pos, const QList<QSslCertificate> &systemCaCertificates);
|
||||
QPointer<AccountState> _accountState;
|
||||
QMenu *_menu;
|
||||
};
|
||||
|
|
|
@ -54,7 +54,7 @@ UpdaterScheduler::UpdaterScheduler(QObject *parent)
|
|||
|
||||
ConfigFile cfg;
|
||||
auto checkInterval = cfg.updateCheckInterval();
|
||||
_updateCheckTimer.start(checkInterval);
|
||||
_updateCheckTimer.start(std::chrono::milliseconds(checkInterval).count());
|
||||
}
|
||||
|
||||
void UpdaterScheduler::slotTimerFired()
|
||||
|
@ -62,7 +62,7 @@ void UpdaterScheduler::slotTimerFired()
|
|||
ConfigFile cfg;
|
||||
|
||||
// re-set the check interval if it changed in the config file meanwhile
|
||||
auto checkInterval = cfg.updateCheckInterval();
|
||||
auto checkInterval = std::chrono::milliseconds(cfg.updateCheckInterval()).count();
|
||||
if (checkInterval != _updateCheckTimer.interval()) {
|
||||
_updateCheckTimer.setInterval(checkInterval);
|
||||
qCInfo(lcUpdater) << "Setting new update check interval " << checkInterval;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
// Only possible in later versions, we're not up to date here.
|
||||
- (BOOL)updaterMayCheckForUpdates:(SUUpdater *)bundle
|
||||
{
|
||||
Q_UNUSED(bundle)
|
||||
qCDebug(OCC::lcUpdater) << "may check: YES";
|
||||
return YES;
|
||||
}
|
||||
|
@ -37,17 +38,22 @@
|
|||
// Sent when a valid update is found by the update driver.
|
||||
- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update
|
||||
{
|
||||
Q_UNUSED(updater)
|
||||
Q_UNUSED(update)
|
||||
}
|
||||
|
||||
// Sent when a valid update is not found.
|
||||
// Does not seem to get called ever.
|
||||
- (void)updaterDidNotFindUpdate:(SUUpdater *)update
|
||||
{
|
||||
Q_UNUSED(update)
|
||||
}
|
||||
|
||||
// Sent immediately before installing the specified update.
|
||||
- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update
|
||||
{
|
||||
Q_UNUSED(updater)
|
||||
Q_UNUSED(update)
|
||||
}
|
||||
|
||||
// Tried implementing those methods, but they never ever seem to get called
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <QMutex>
|
||||
#include <QCoreApplication>
|
||||
#include <QAuthenticator>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include "networkjobs.h"
|
||||
#include "account.h"
|
||||
|
@ -308,6 +309,15 @@ void AbstractNetworkJob::onTimedOut()
|
|||
}
|
||||
}
|
||||
|
||||
QString AbstractNetworkJob::replyStatusString() {
|
||||
Q_ASSERT(reply());
|
||||
if (reply()->error() == QNetworkReply::NoError) {
|
||||
return QLatin1String("OK");
|
||||
} else {
|
||||
QString enumStr = QMetaEnum::fromType<QNetworkReply::NetworkError>().valueToKey(static_cast<int>(reply()->error()));
|
||||
return QStringLiteral("%1 %2").arg(enumStr, errorString());
|
||||
}
|
||||
}
|
||||
|
||||
NetworkJobTimeoutPauser::NetworkJobTimeoutPauser(QNetworkReply *reply)
|
||||
{
|
||||
|
|
|
@ -173,6 +173,8 @@ protected:
|
|||
// GET requests that don't set up any HTTP body or other flags.
|
||||
bool _followRedirects;
|
||||
|
||||
QString replyStatusString();
|
||||
|
||||
private slots:
|
||||
void slotFinished();
|
||||
void slotTimeout();
|
||||
|
|
|
@ -219,7 +219,7 @@ public:
|
|||
/** Detects a specific bug in older server versions */
|
||||
bool rootEtagChangesNotOnlySubFolderEtags();
|
||||
|
||||
/** True when the server supports HTTP2 */
|
||||
/** True when the server connection is using HTTP2 */
|
||||
bool isHttp2Supported() { return _http2Supported; }
|
||||
void setHttp2Supported(bool value) { _http2Supported = value; }
|
||||
|
||||
|
|
|
@ -38,12 +38,13 @@
|
|||
#include <QStandardPaths>
|
||||
|
||||
#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 {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcConfigFile, "nextcloud.sync.configfile", QtInfoMsg)
|
||||
namespace chrono = std::chrono;
|
||||
|
||||
Q_LOGGING_CATEGORY(lcConfigFile, "sync.configfile", QtInfoMsg)
|
||||
|
||||
//static const char caCertsKeyC[] = "CaCertificates"; only used from account.cpp
|
||||
static const char remotePollIntervalC[] = "remotePollInterval";
|
||||
|
@ -63,6 +64,7 @@ static const char chunkSizeC[] = "chunkSize";
|
|||
static const char minChunkSizeC[] = "minChunkSize";
|
||||
static const char maxChunkSizeC[] = "maxChunkSize";
|
||||
static const char targetChunkUploadDurationC[] = "targetChunkUploadDuration";
|
||||
static const char automaticLogDirC[] = "logToTemporaryLogDir";
|
||||
|
||||
static const char proxyHostC[] = "Proxy/host";
|
||||
static const char proxyTypeC[] = "Proxy/type";
|
||||
|
@ -88,6 +90,12 @@ const char certPasswd[] = "http_certificatePasswd";
|
|||
QString ConfigFile::_confDir = QString();
|
||||
bool ConfigFile::_askedUser = false;
|
||||
|
||||
static chrono::milliseconds millisecondsValue(const QSettings &setting, const char *key,
|
||||
chrono::milliseconds defaultValue)
|
||||
{
|
||||
return chrono::milliseconds(setting.value(QLatin1String(key), qlonglong(defaultValue.count())).toLongLong());
|
||||
};
|
||||
|
||||
ConfigFile::ConfigFile()
|
||||
{
|
||||
// QDesktopServices uses the application name to create a config path
|
||||
|
@ -172,10 +180,10 @@ quint64 ConfigFile::minChunkSize() const
|
|||
return settings.value(QLatin1String(minChunkSizeC), 1000 * 1000).toLongLong(); // default to 1 MB
|
||||
}
|
||||
|
||||
quint64 ConfigFile::targetChunkUploadDuration() const
|
||||
chrono::milliseconds ConfigFile::targetChunkUploadDuration() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
return settings.value(QLatin1String(targetChunkUploadDurationC), 60 * 1000).toLongLong(); // default to 1 minute
|
||||
return millisecondsValue(settings, targetChunkUploadDurationC, chrono::minutes(1));
|
||||
}
|
||||
|
||||
void ConfigFile::setOptionalServerNotifications(bool show)
|
||||
|
@ -385,7 +393,7 @@ bool ConfigFile::dataExists(const QString &group, const QString &key) const
|
|||
return settings.contains(key);
|
||||
}
|
||||
|
||||
int ConfigFile::remotePollInterval(const QString &connection) const
|
||||
chrono::milliseconds ConfigFile::remotePollInterval(const QString &connection) const
|
||||
{
|
||||
QString con(connection);
|
||||
if (connection.isEmpty())
|
||||
|
@ -394,33 +402,34 @@ int ConfigFile::remotePollInterval(const QString &connection) const
|
|||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(con);
|
||||
|
||||
int remoteInterval = settings.value(QLatin1String(remotePollIntervalC), DEFAULT_REMOTE_POLL_INTERVAL).toInt();
|
||||
if (remoteInterval < 5000) {
|
||||
auto defaultPollInterval = chrono::milliseconds(DEFAULT_REMOTE_POLL_INTERVAL);
|
||||
auto remoteInterval = millisecondsValue(settings, remotePollIntervalC, defaultPollInterval);
|
||||
if (remoteInterval < chrono::seconds(5)) {
|
||||
qCWarning(lcConfigFile) << "Remote Interval is less than 5 seconds, reverting to" << DEFAULT_REMOTE_POLL_INTERVAL;
|
||||
remoteInterval = DEFAULT_REMOTE_POLL_INTERVAL;
|
||||
remoteInterval = defaultPollInterval;
|
||||
}
|
||||
return remoteInterval;
|
||||
}
|
||||
|
||||
void ConfigFile::setRemotePollInterval(int interval, const QString &connection)
|
||||
void ConfigFile::setRemotePollInterval(chrono::milliseconds interval, const QString &connection)
|
||||
{
|
||||
QString con(connection);
|
||||
if (connection.isEmpty())
|
||||
con = defaultConnection();
|
||||
|
||||
if (interval < 5000) {
|
||||
qCWarning(lcConfigFile) << "Remote Poll interval of " << interval << " is below five seconds.";
|
||||
if (interval < chrono::seconds(5)) {
|
||||
qCWarning(lcConfigFile) << "Remote Poll interval of " << interval.count() << " is below five seconds.";
|
||||
return;
|
||||
}
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(con);
|
||||
settings.setValue(QLatin1String(remotePollIntervalC), interval);
|
||||
settings.setValue(QLatin1String(remotePollIntervalC), qlonglong(interval.count()));
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
quint64 ConfigFile::forceSyncInterval(const QString &connection) const
|
||||
chrono::milliseconds ConfigFile::forceSyncInterval(const QString &connection) const
|
||||
{
|
||||
uint pollInterval = remotePollInterval(connection);
|
||||
auto pollInterval = remotePollInterval(connection);
|
||||
|
||||
QString con(connection);
|
||||
if (connection.isEmpty())
|
||||
|
@ -428,23 +437,23 @@ quint64 ConfigFile::forceSyncInterval(const QString &connection) const
|
|||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(con);
|
||||
|
||||
quint64 defaultInterval = 2 * 60 * 60 * 1000ull; // 2h
|
||||
quint64 interval = settings.value(QLatin1String(forceSyncIntervalC), defaultInterval).toULongLong();
|
||||
auto defaultInterval = chrono::hours(2);
|
||||
auto interval = millisecondsValue(settings, forceSyncIntervalC, defaultInterval);
|
||||
if (interval < pollInterval) {
|
||||
qCWarning(lcConfigFile) << "Force sync interval is less than the remote poll inteval, reverting to" << pollInterval;
|
||||
qCWarning(lcConfigFile) << "Force sync interval is less than the remote poll inteval, reverting to" << pollInterval.count();
|
||||
interval = pollInterval;
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
qint64 ConfigFile::fullLocalDiscoveryInterval() const
|
||||
chrono::milliseconds OCC::ConfigFile::fullLocalDiscoveryInterval() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(defaultConnection());
|
||||
return settings.value(QLatin1String(fullLocalDiscoveryIntervalC), DEFAULT_FULL_LOCAL_DISCOVERY_INTERVAL).toLongLong();
|
||||
return millisecondsValue(settings, fullLocalDiscoveryIntervalC, chrono::hours(1));
|
||||
}
|
||||
|
||||
quint64 ConfigFile::notificationRefreshInterval(const QString &connection) const
|
||||
chrono::milliseconds ConfigFile::notificationRefreshInterval(const QString &connection) const
|
||||
{
|
||||
QString con(connection);
|
||||
if (connection.isEmpty())
|
||||
|
@ -452,16 +461,16 @@ quint64 ConfigFile::notificationRefreshInterval(const QString &connection) const
|
|||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(con);
|
||||
|
||||
quint64 defaultInterval = 5 * 60 * 1000ull; // 5 minutes
|
||||
quint64 interval = settings.value(QLatin1String(notificationRefreshIntervalC), defaultInterval).toULongLong();
|
||||
if (interval < 60 * 1000ull) {
|
||||
auto defaultInterval = chrono::minutes(5);
|
||||
auto interval = millisecondsValue(settings, notificationRefreshIntervalC, defaultInterval);
|
||||
if (interval < chrono::minutes(1)) {
|
||||
qCWarning(lcConfigFile) << "Notification refresh interval smaller than one minute, setting to one minute";
|
||||
interval = 60 * 1000ull;
|
||||
interval = chrono::minutes(1);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
int ConfigFile::updateCheckInterval(const QString &connection) const
|
||||
chrono::milliseconds ConfigFile::updateCheckInterval(const QString &connection) const
|
||||
{
|
||||
QString con(connection);
|
||||
if (connection.isEmpty())
|
||||
|
@ -469,12 +478,12 @@ int ConfigFile::updateCheckInterval(const QString &connection) const
|
|||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.beginGroup(con);
|
||||
|
||||
int defaultInterval = 1000 * 60 * 60 * 10; // ten hours
|
||||
int interval = settings.value(QLatin1String(updateCheckIntervalC), defaultInterval).toInt();
|
||||
auto defaultInterval = chrono::hours(10);
|
||||
auto interval = millisecondsValue(settings, updateCheckIntervalC, defaultInterval);
|
||||
|
||||
int minInterval = 1000 * 60 * 5;
|
||||
auto minInterval = chrono::minutes(5);
|
||||
if (interval < minInterval) {
|
||||
qCWarning(lcConfigFile) << "Update check interval less than five minutes, setting " << minInterval;
|
||||
qCWarning(lcConfigFile) << "Update check interval less than five minutes, resetting to 5 minutes";
|
||||
interval = minInterval;
|
||||
}
|
||||
return interval;
|
||||
|
@ -728,6 +737,18 @@ void ConfigFile::setCrashReporter(bool enabled)
|
|||
settings.setValue(QLatin1String(crashReporterC), enabled);
|
||||
}
|
||||
|
||||
bool ConfigFile::automaticLogDir() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
return settings.value(QLatin1String(automaticLogDirC), false).toBool();
|
||||
}
|
||||
|
||||
void ConfigFile::setAutomaticLogDir(bool enabled)
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.setValue(QLatin1String(automaticLogDirC), enabled);
|
||||
}
|
||||
|
||||
QString ConfigFile::certificatePath() const
|
||||
{
|
||||
return retrieveData(QString(), QLatin1String(certPath)).toString();
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QSettings>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <chrono>
|
||||
|
||||
class QWidget;
|
||||
class QHeaderView;
|
||||
|
@ -62,22 +63,22 @@ public:
|
|||
void setMaxLogLines(int);
|
||||
|
||||
/* Server poll interval in milliseconds */
|
||||
int remotePollInterval(const QString &connection = QString()) const;
|
||||
std::chrono::milliseconds remotePollInterval(const QString &connection = QString()) const;
|
||||
/* Set poll interval. Value in milliseconds has to be larger than 5000 */
|
||||
void setRemotePollInterval(int interval, const QString &connection = QString());
|
||||
void setRemotePollInterval(std::chrono::milliseconds interval, const QString &connection = QString());
|
||||
|
||||
/* Interval to check for new notifications */
|
||||
quint64 notificationRefreshInterval(const QString &connection = QString()) const;
|
||||
std::chrono::milliseconds notificationRefreshInterval(const QString &connection = QString()) const;
|
||||
|
||||
/* Force sync interval, in milliseconds */
|
||||
quint64 forceSyncInterval(const QString &connection = QString()) const;
|
||||
std::chrono::milliseconds 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;
|
||||
std::chrono::milliseconds fullLocalDiscoveryInterval() const;
|
||||
|
||||
bool monoIcons() const;
|
||||
void setMonoIcons(bool);
|
||||
|
@ -88,6 +89,9 @@ public:
|
|||
bool crashReporter() const;
|
||||
void setCrashReporter(bool enabled);
|
||||
|
||||
bool automaticLogDir() const;
|
||||
void setAutomaticLogDir(bool enabled);
|
||||
|
||||
// proxy settings
|
||||
void setProxyType(int proxyType,
|
||||
const QString &host = QString(),
|
||||
|
@ -134,13 +138,13 @@ public:
|
|||
quint64 chunkSize() const;
|
||||
quint64 maxChunkSize() const;
|
||||
quint64 minChunkSize() const;
|
||||
quint64 targetChunkUploadDuration() const;
|
||||
std::chrono::milliseconds targetChunkUploadDuration() const;
|
||||
|
||||
void saveGeometry(QWidget *w);
|
||||
void restoreGeometry(QWidget *w);
|
||||
|
||||
// how often the check about new versions runs, default two hours
|
||||
int updateCheckInterval(const QString &connection = QString()) const;
|
||||
// how often the check about new versions runs
|
||||
std::chrono::milliseconds updateCheckInterval(const QString &connection = QString()) const;
|
||||
|
||||
bool skipUpdateCheck(const QString &connection = QString()) const;
|
||||
void setSkipUpdateCheck(bool, const QString &);
|
||||
|
|
|
@ -429,7 +429,7 @@ void DiscoverySingleDirectoryJob::lsJobFinishedWithoutErrorSlot()
|
|||
deleteLater();
|
||||
return;
|
||||
} else if (!_error.isEmpty()) {
|
||||
emit finishedWithError(ERRNO_ERROR_STRING, _error);
|
||||
emit finishedWithError(ERRNO_WRONG_CONTENT, _error);
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -14,11 +14,15 @@
|
|||
|
||||
#include "logger.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QStringList>
|
||||
#include <QThread>
|
||||
#include <qmetaobject.h>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
static void mirallLogCatcher(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
|
||||
|
@ -95,10 +99,15 @@ void Logger::log(Log log)
|
|||
*/
|
||||
bool Logger::isNoop() const
|
||||
{
|
||||
QMutexLocker lock(const_cast<QMutex *>(&_mutex));
|
||||
QMutexLocker lock(&_mutex);
|
||||
return !_logstream && !_logWindowActivated;
|
||||
}
|
||||
|
||||
bool Logger::isLoggingToFile() const
|
||||
{
|
||||
QMutexLocker lock(&_mutex);
|
||||
return _logstream;
|
||||
}
|
||||
|
||||
void Logger::doLog(const QString &msg)
|
||||
{
|
||||
|
@ -181,34 +190,99 @@ void Logger::setLogDebug(bool debug)
|
|||
_logDebug = debug;
|
||||
}
|
||||
|
||||
QString Logger::temporaryFolderLogDirPath() const
|
||||
{
|
||||
QString dirName = APPLICATION_SHORTNAME + QString("-logdir");
|
||||
return QDir::temp().filePath(dirName);
|
||||
}
|
||||
|
||||
void Logger::setupTemporaryFolderLogDir()
|
||||
{
|
||||
auto dir = temporaryFolderLogDirPath();
|
||||
if (!QDir().mkpath(dir))
|
||||
return;
|
||||
setLogDebug(true);
|
||||
setLogExpire(4 /*hours*/);
|
||||
setLogDir(dir);
|
||||
_temporaryFolderLogDir = true;
|
||||
}
|
||||
|
||||
void Logger::disableTemporaryFolderLogDir()
|
||||
{
|
||||
if (!_temporaryFolderLogDir)
|
||||
return;
|
||||
|
||||
enterNextLogFile();
|
||||
setLogDir(QString());
|
||||
setLogDebug(false);
|
||||
setLogFile(QString());
|
||||
_temporaryFolderLogDir = false;
|
||||
}
|
||||
|
||||
static bool compressLog(const QString &originalName, const QString &targetName)
|
||||
{
|
||||
QFile original(originalName);
|
||||
if (!original.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
auto compressed = gzopen(targetName.toUtf8(), "wb");
|
||||
if (!compressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!original.atEnd()) {
|
||||
auto data = original.read(1024 * 1024);
|
||||
auto written = gzwrite(compressed, data.data(), data.size());
|
||||
if (written != data.size()) {
|
||||
gzclose(compressed);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
gzclose(compressed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Logger::enterNextLogFile()
|
||||
{
|
||||
if (!_logDirectory.isEmpty()) {
|
||||
|
||||
QDir dir(_logDirectory);
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
|
||||
// Find out what is the file with the highest number if any
|
||||
QStringList files = dir.entryList(QStringList("owncloud.log.*"),
|
||||
// Tentative new log name, will be adjusted if one like this already exists
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QString newLogName = now.toString("yyyyMMdd_HHmm") + "_owncloud.log";
|
||||
|
||||
// Expire old log files and deal with conflicts
|
||||
QStringList files = dir.entryList(QStringList("*owncloud.log.*"),
|
||||
QDir::Files);
|
||||
QRegExp rx("owncloud.log.(\\d+)");
|
||||
uint maxNumber = 0;
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
QRegExp rx(R"(.*owncloud\.log\.(\d+).*)");
|
||||
int maxNumber = -1;
|
||||
foreach (const QString &s, files) {
|
||||
if (rx.exactMatch(s)) {
|
||||
maxNumber = qMax(maxNumber, rx.cap(1).toUInt());
|
||||
if (_logExpire > 0) {
|
||||
QFileInfo fileInfo = dir.absoluteFilePath(s);
|
||||
if (fileInfo.lastModified().addSecs(60 * 60 * _logExpire) < now) {
|
||||
dir.remove(s);
|
||||
}
|
||||
if (_logExpire > 0) {
|
||||
QFileInfo fileInfo(dir.absoluteFilePath(s));
|
||||
if (fileInfo.lastModified().addSecs(60 * 60 * _logExpire) < now) {
|
||||
dir.remove(s);
|
||||
}
|
||||
}
|
||||
if (s.startsWith(newLogName) && rx.exactMatch(s)) {
|
||||
maxNumber = qMax(maxNumber, rx.cap(1).toInt());
|
||||
}
|
||||
}
|
||||
newLogName.append("." + QString::number(maxNumber + 1));
|
||||
|
||||
QString filename = _logDirectory + "/owncloud.log." + QString::number(maxNumber + 1);
|
||||
setLogFile(filename);
|
||||
auto previousLog = _logFile.fileName();
|
||||
setLogFile(dir.filePath(newLogName));
|
||||
|
||||
if (!previousLog.isEmpty()) {
|
||||
QString compressedName = previousLog + ".gz";
|
||||
if (compressLog(previousLog, compressedName)) {
|
||||
QFile::remove(previousLog);
|
||||
} else {
|
||||
QFile::remove(compressedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ class OWNCLOUDSYNC_EXPORT Logger : public QObject
|
|||
Q_OBJECT
|
||||
public:
|
||||
bool isNoop() const;
|
||||
bool isLoggingToFile() const;
|
||||
|
||||
void log(Log log);
|
||||
void doLog(const QString &log);
|
||||
|
||||
|
@ -65,6 +67,22 @@ public:
|
|||
bool logDebug() const { return _logDebug; }
|
||||
void setLogDebug(bool debug);
|
||||
|
||||
/** Returns where the automatic logdir would be */
|
||||
QString temporaryFolderLogDirPath() const;
|
||||
|
||||
/** Sets up default dir log setup.
|
||||
*
|
||||
* logdir: a temporary folder
|
||||
* logexpire: 4 hours
|
||||
* logdebug: true
|
||||
*
|
||||
* Used in conjunction with ConfigFile::automaticLogDir
|
||||
*/
|
||||
void setupTemporaryFolderLogDir();
|
||||
|
||||
/** For switching off via logwindow */
|
||||
void disableTemporaryFolderLogDir();
|
||||
|
||||
signals:
|
||||
void logWindowLog(const QString &);
|
||||
|
||||
|
@ -86,8 +104,9 @@ private:
|
|||
int _logExpire;
|
||||
bool _logDebug;
|
||||
QScopedPointer<QTextStream> _logstream;
|
||||
QMutex _mutex;
|
||||
mutable QMutex _mutex;
|
||||
QString _logDirectory;
|
||||
bool _temporaryFolderLogDir = false;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -92,8 +92,7 @@ void RequestEtagJob::start()
|
|||
bool RequestEtagJob::finished()
|
||||
{
|
||||
qCInfo(lcEtagJob) << "Request Etag of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
|
||||
// Parse DAV response
|
||||
|
@ -150,8 +149,7 @@ void MkColJob::start()
|
|||
bool MkColJob::finished()
|
||||
{
|
||||
qCInfo(lcMkColJob) << "MKCOL of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
emit finished(reply()->error());
|
||||
return true;
|
||||
|
@ -359,8 +357,7 @@ void LsColJob::start()
|
|||
bool LsColJob::finished()
|
||||
{
|
||||
qCInfo(lcLsColJob) << "LSCOL of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
QString contentType = reply()->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||
int httpCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
@ -586,8 +583,7 @@ QList<QByteArray> PropfindJob::properties() const
|
|||
bool PropfindJob::finished()
|
||||
{
|
||||
qCInfo(lcPropfindJob) << "PROPFIND of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
|
@ -748,8 +744,7 @@ QMap<QByteArray, QByteArray> ProppatchJob::properties() const
|
|||
bool ProppatchJob::finished()
|
||||
{
|
||||
qCInfo(lcProppatchJob) << "PROPPATCH of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
|
@ -812,8 +807,7 @@ void JsonApiJob::start()
|
|||
bool JsonApiJob::finished()
|
||||
{
|
||||
qCInfo(lcJsonApiJob) << "JsonApiJob of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
int statusCode = 0;
|
||||
int httpStatusCode = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
@ -896,6 +890,8 @@ void DetermineAuthTypeJob::start()
|
|||
get->setFollowRedirects(false);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(this)
|
||||
Q_UNUSED(get)
|
||||
Q_UNUSED(target)
|
||||
#endif
|
||||
});
|
||||
|
|
|
@ -517,8 +517,7 @@ bool OwncloudPropagator::localFileNameClash(const QString &relFile)
|
|||
re = false;
|
||||
qCWarning(lcPropagator) << "No valid fileinfo";
|
||||
} else {
|
||||
// Need to normalize to composited form because of
|
||||
// https://bugreports.qt-project.org/browse/QTBUG-39622
|
||||
// Need to normalize to composited form because of QTBUG-39622/QTBUG-55896
|
||||
const QString cName = fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_C);
|
||||
bool equal = (file == cName);
|
||||
re = (!equal && !cName.endsWith(relFile, Qt::CaseSensitive));
|
||||
|
@ -835,10 +834,13 @@ void PropagatorCompositeJob::slotSubJobFinished(SyncFileItem::Status status)
|
|||
ASSERT(i >= 0);
|
||||
_runningJobs.remove(i);
|
||||
|
||||
// Any sub job error will cause the whole composite to fail. This is important
|
||||
// for knowing whether to update the etag in PropagateDirectory, for example.
|
||||
if (status == SyncFileItem::FatalError
|
||||
|| status == SyncFileItem::NormalError
|
||||
|| status == SyncFileItem::SoftError
|
||||
|| status == SyncFileItem::DetailError) {
|
||||
|| status == SyncFileItem::DetailError
|
||||
|| status == SyncFileItem::BlacklistedError) {
|
||||
_hasError = status;
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ class PropagateItemJob : public PropagatorJob
|
|||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
void done(SyncFileItem::Status status, const QString &errorString = QString());
|
||||
virtual void done(SyncFileItem::Status status, const QString &errorString = QString());
|
||||
|
||||
/*
|
||||
* set a custom restore job message that is used if the restore job succeeded.
|
||||
|
|
|
@ -64,6 +64,12 @@ inline SyncFileItem::Status classifyError(QNetworkReply::NetworkError nerror,
|
|||
{
|
||||
Q_ASSERT(nerror != QNetworkReply::NoError); // we should only be called when there is an error
|
||||
|
||||
if (nerror == QNetworkReply::RemoteHostClosedError) {
|
||||
// Sometimes server bugs lead to a connection close on certain files,
|
||||
// that shouldn't bring the rest of the syncing to a halt.
|
||||
return SyncFileItem::NormalError;
|
||||
}
|
||||
|
||||
if (nerror > QNetworkReply::NoError && nerror <= QNetworkReply::UnknownProxyError) {
|
||||
// network error or proxy error -> fatal
|
||||
return SyncFileItem::FatalError;
|
||||
|
|
|
@ -313,8 +313,7 @@ void GETFileJob::slotReadyRead()
|
|||
}
|
||||
if (!_hasEmittedFinishedSignal) {
|
||||
qCInfo(lcGetJob) << "GET of" << reply()->request().url().toString() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString())
|
||||
<< replyStatusString()
|
||||
<< reply()->rawHeader("Content-Range") << reply()->rawHeader("Content-Length");
|
||||
|
||||
emit finishedSignal();
|
||||
|
|
|
@ -54,8 +54,7 @@ void DeleteJob::start()
|
|||
bool DeleteJob::finished()
|
||||
{
|
||||
qCInfo(lcDeleteJob) << "DELETE of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
emit finishedSignal();
|
||||
return true;
|
||||
|
|
|
@ -67,8 +67,7 @@ void MoveJob::start()
|
|||
bool MoveJob::finished()
|
||||
{
|
||||
qCInfo(lcMoveJob) << "MOVE of" << reply()->request().url() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString());
|
||||
<< replyStatusString();
|
||||
|
||||
emit finishedSignal();
|
||||
return true;
|
||||
|
|
|
@ -529,7 +529,6 @@ void PropagateUploadFileCommon::slotPollFinished()
|
|||
propagator()->_activeJobList.removeOne(this);
|
||||
|
||||
if (job->_item->_status != SyncFileItem::Success) {
|
||||
_finished = true;
|
||||
done(job->_item->_status, job->_item->_errorString);
|
||||
return;
|
||||
}
|
||||
|
@ -537,6 +536,12 @@ void PropagateUploadFileCommon::slotPollFinished()
|
|||
finalize();
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::done(SyncFileItem::Status status, const QString &errorString)
|
||||
{
|
||||
_finished = true;
|
||||
PropagateItemJob::done(status, errorString);
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::checkResettingErrors()
|
||||
{
|
||||
if (_item->_httpErrorCode == 412
|
||||
|
@ -601,28 +606,24 @@ void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job)
|
|||
abortWithError(status, errorString);
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::adjustLastJobTimeout(AbstractNetworkJob *job, quint64 fileSize)
|
||||
{
|
||||
job->setTimeout(qBound(
|
||||
job->timeoutMsec(),
|
||||
// Calculate 3 minutes for each gigabyte of data
|
||||
qint64((3 * 60 * 1000) * fileSize / 1e9),
|
||||
// Maximum of 30 minutes
|
||||
qint64(30 * 60 * 1000)));
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::slotJobDestroyed(QObject *job)
|
||||
{
|
||||
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end());
|
||||
}
|
||||
|
||||
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(AbortType::Synchronous);
|
||||
done(status, error);
|
||||
}
|
||||
|
@ -671,9 +672,6 @@ QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
|
|||
|
||||
void PropagateUploadFileCommon::finalize()
|
||||
{
|
||||
qDebug() << "Finalizing the upload. Check later if this is encrypted";
|
||||
_finished = true;
|
||||
|
||||
// Update the quota, if known
|
||||
auto quotaIt = propagator()->_folderQuota.find(QFileInfo(_item->_file).path());
|
||||
if (quotaIt != propagator()->_folderQuota.end())
|
||||
|
@ -697,32 +695,45 @@ 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()
|
||||
void PropagateUploadFileCommon::abortNetworkJobs(
|
||||
PropagatorJob::AbortType abortType,
|
||||
const std::function<bool(AbstractNetworkJob *)> &mayAbortJob)
|
||||
{
|
||||
_abortCount--;
|
||||
// Count the number of jobs that need aborting, and emit the overall
|
||||
// abort signal when they're all done.
|
||||
QSharedPointer<int> runningCount(new int(0));
|
||||
auto oneAbortFinished = [this, runningCount]() {
|
||||
(*runningCount)--;
|
||||
if (*runningCount == 0) {
|
||||
emit this->abortFinished();
|
||||
}
|
||||
};
|
||||
|
||||
if (_abortCount == 0) {
|
||||
emit abortFinished();
|
||||
// Abort all running jobs, except for explicitly excluded ones
|
||||
foreach (AbstractNetworkJob *job, _jobs) {
|
||||
auto reply = job->reply();
|
||||
if (!reply || !reply->isRunning())
|
||||
continue;
|
||||
|
||||
(*runningCount)++;
|
||||
|
||||
// If a job should not be aborted that means we'll never abort before
|
||||
// the hard abort timeout signal comes as runningCount will never go to
|
||||
// zero.
|
||||
// We may however finish before that if the un-abortable job completes
|
||||
// normally.
|
||||
if (!mayAbortJob(job))
|
||||
continue;
|
||||
|
||||
// Abort the job
|
||||
if (abortType == AbortType::Asynchronous) {
|
||||
// Connect to finished signal of job reply to asynchonously finish the abort
|
||||
connect(reply, &QNetworkReply::finished, this, oneAbortFinished);
|
||||
}
|
||||
reply->abort();
|
||||
}
|
||||
}
|
||||
|
||||
if (*runningCount == 0 && abortType == AbortType::Asynchronous)
|
||||
emit abortFinished();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,8 +120,7 @@ public:
|
|||
virtual bool finished() Q_DECL_OVERRIDE
|
||||
{
|
||||
qCInfo(lcPutJob) << "PUT of" << reply()->request().url().toString() << "FINISHED WITH STATUS"
|
||||
<< reply()->error()
|
||||
<< (reply()->error() == QNetworkReply::NoError ? QLatin1String("") : errorString())
|
||||
<< replyStatusString()
|
||||
<< reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
|
||||
<< reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
|
||||
|
||||
|
@ -139,9 +138,9 @@ public:
|
|||
return _errorString.isEmpty() ? AbstractNetworkJob::errorString() : _errorString;
|
||||
}
|
||||
|
||||
quint64 msSinceStart() const
|
||||
std::chrono::milliseconds msSinceStart() const
|
||||
{
|
||||
return _requestTimer.elapsed();
|
||||
return std::chrono::milliseconds(_requestTimer.elapsed());
|
||||
}
|
||||
|
||||
signals:
|
||||
|
@ -212,7 +211,6 @@ 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
|
||||
|
||||
/* This is a minified version of the SyncFileItem,
|
||||
* that holds only the specifics about the file that's
|
||||
|
@ -234,7 +232,6 @@ public:
|
|||
: PropagateItemJob(propagator, item)
|
||||
, _finished(false)
|
||||
, _deleteExisting(false)
|
||||
, _abortCount(0)
|
||||
, _uploadEncryptedHelper(0)
|
||||
, _uploadingEncrypted(false)
|
||||
{
|
||||
|
@ -271,19 +268,21 @@ public:
|
|||
void abortWithError(SyncFileItem::Status status, const QString &error);
|
||||
|
||||
public slots:
|
||||
void abort(PropagatorJob::AbortType abortType) Q_DECL_OVERRIDE;
|
||||
void slotJobDestroyed(QObject *job);
|
||||
|
||||
private slots:
|
||||
void slotReplyAbortFinished();
|
||||
void slotPollFinished();
|
||||
|
||||
protected:
|
||||
void done(SyncFileItem::Status status, const QString &errorString = QString()) override;
|
||||
|
||||
/**
|
||||
* Prepares the abort e.g. connects proper signals and slots
|
||||
* to the subjobs to abort asynchronously
|
||||
* Aborts all running network jobs, except for the ones that mayAbortJob
|
||||
* returns false on and, for async aborts, emits abortFinished when done.
|
||||
*/
|
||||
void prepareAbort(PropagatorJob::AbortType abortType);
|
||||
void abortNetworkJobs(
|
||||
AbortType abortType,
|
||||
const std::function<bool(AbstractNetworkJob *job)> &mayAbortJob);
|
||||
|
||||
/**
|
||||
* Checks whether the current error is one that should reset the whole
|
||||
|
@ -297,6 +296,17 @@ protected:
|
|||
*/
|
||||
void commonErrorHandling(AbstractNetworkJob *job);
|
||||
|
||||
/**
|
||||
* Increases the timeout for the final MOVE/PUT for large files.
|
||||
*
|
||||
* This is an unfortunate workaround since the drawback is not being able to
|
||||
* detect real disconnects in a timely manner. Shall go away when the server
|
||||
* response starts coming quicker, or there is some sort of async api.
|
||||
*
|
||||
* See #6527, enterprise#2480
|
||||
*/
|
||||
static void adjustLastJobTimeout(AbstractNetworkJob *job, quint64 fileSize);
|
||||
|
||||
// Bases headers that need to be sent with every chunk
|
||||
QMap<QByteArray, QByteArray> headers();
|
||||
private:
|
||||
|
|
|
@ -83,7 +83,7 @@ void PropagateUploadFileNG::doStartUpload()
|
|||
propagator()->_activeJobList.append(this);
|
||||
|
||||
const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);
|
||||
if (progressInfo._valid && progressInfo._modtime == _item->_modtime) {
|
||||
if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime) {
|
||||
_transferId = progressInfo._transferid;
|
||||
auto url = chunkUrl();
|
||||
auto job = new LsColJob(propagator()->account(), url, this);
|
||||
|
@ -98,7 +98,7 @@ void PropagateUploadFileNG::doStartUpload()
|
|||
this, &PropagateUploadFileNG::slotPropfindIterate);
|
||||
job->start();
|
||||
return;
|
||||
} else if (progressInfo._valid) {
|
||||
} else if (progressInfo._valid && progressInfo.isChunked()) {
|
||||
// The upload info is stale. remove the stale chunks on the server
|
||||
_transferId = progressInfo._transferid;
|
||||
// Fire and forget. Any error will be ignored.
|
||||
|
@ -276,6 +276,7 @@ void PropagateUploadFileNG::startNextChunk()
|
|||
if (_currentChunkSize == 0) {
|
||||
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
|
||||
_finished = true;
|
||||
|
||||
// Finish with a MOVE
|
||||
// If we changed the file name, we must store the changed filename in the remote folder, not the original one.
|
||||
QString destination = QDir::cleanPath(propagator()->account()->url().path() + QLatin1Char('/')
|
||||
|
@ -299,6 +300,7 @@ void PropagateUploadFileNG::startNextChunk()
|
|||
connect(job, &MoveJob::finishedSignal, this, &PropagateUploadFileNG::slotMoveJobFinished);
|
||||
connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
|
||||
propagator()->_activeJobList.append(this);
|
||||
adjustLastJobTimeout(job, fileSize);
|
||||
job->start();
|
||||
return;
|
||||
}
|
||||
|
@ -367,12 +369,10 @@ void PropagateUploadFileNG::slotPutFinished()
|
|||
//
|
||||
// Dynamic chunk sizing is enabled if the server configured a
|
||||
// target duration for each chunk upload.
|
||||
double targetDuration = propagator()->syncOptions()._targetChunkUploadDuration;
|
||||
if (targetDuration > 0) {
|
||||
double uploadTime = job->msSinceStart() + 1; // add one to avoid div-by-zero
|
||||
|
||||
auto predictedGoodSize = static_cast<quint64>(
|
||||
_currentChunkSize / uploadTime * targetDuration);
|
||||
auto targetDuration = propagator()->syncOptions()._targetChunkUploadDuration;
|
||||
if (targetDuration.count() > 0) {
|
||||
auto uploadTime = ++job->msSinceStart(); // add one to avoid div-by-zero
|
||||
qint64 predictedGoodSize = (_currentChunkSize * targetDuration) / uploadTime;
|
||||
|
||||
// The whole targeting is heuristic. The predictedGoodSize will fluctuate
|
||||
// quite a bit because of external factors (like available bandwidth)
|
||||
|
@ -388,25 +388,18 @@ void PropagateUploadFileNG::slotPutFinished()
|
|||
targetSize,
|
||||
propagator()->syncOptions()._maxChunkSize);
|
||||
|
||||
qCInfo(lcPropagateUpload) << "Chunked upload of" << _currentChunkSize << "bytes took" << uploadTime
|
||||
<< "ms, desired is" << targetDuration << "ms, expected good chunk size is"
|
||||
qCInfo(lcPropagateUpload) << "Chunked upload of" << _currentChunkSize << "bytes took" << uploadTime.count()
|
||||
<< "ms, desired is" << targetDuration.count() << "ms, expected good chunk size is"
|
||||
<< predictedGoodSize << "bytes and nudged next chunk size to "
|
||||
<< propagator()->_chunkSize << "bytes";
|
||||
}
|
||||
|
||||
bool finished = _sent == _fileToUpload._size;
|
||||
_finished = _sent == _item->_size;
|
||||
|
||||
// Check if the file still exists
|
||||
/* Check if the file still exists,
|
||||
* but we could be operating in a temporary file, so check both if
|
||||
* the file to upload is different than the file on disk
|
||||
*/
|
||||
const QString fileToUploadPath = _fileToUpload._path;
|
||||
const QString fullFilePath(propagator()->getFilePath(_item->_file));
|
||||
bool fileExists = fileToUploadPath == fullFilePath ? FileSystem::fileExists(fullFilePath)
|
||||
: (FileSystem::fileExists(fileToUploadPath) && FileSystem::fileExists(fullFilePath));
|
||||
if (!fileExists) {
|
||||
if (!finished) {
|
||||
if (!FileSystem::fileExists(fullFilePath)) {
|
||||
if (!_finished) {
|
||||
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
|
||||
return;
|
||||
} else {
|
||||
|
@ -417,13 +410,13 @@ void PropagateUploadFileNG::slotPutFinished()
|
|||
// Check whether the file changed since discovery - this acts on the original file.
|
||||
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
|
||||
propagator()->_anotherSyncNeeded = true;
|
||||
if (!finished) {
|
||||
if (!_finished) {
|
||||
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!finished) {
|
||||
if (!_finished) {
|
||||
// Deletes an existing blacklist entry on successful chunk upload
|
||||
if (_item->_hasBlacklistEntry) {
|
||||
propagator()->_journal->wipeErrorBlacklistEntry(_item->_file);
|
||||
|
@ -494,23 +487,11 @@ void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 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();
|
||||
}
|
||||
}
|
||||
abortNetworkJobs(
|
||||
abortType,
|
||||
[abortType](AbstractNetworkJob *job) {
|
||||
return abortType != AbortType::Asynchronous || !qobject_cast<MoveJob *>(job);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ void PropagateUploadFileV1::doStartUpload()
|
|||
|
||||
const SyncJournalDb::UploadInfo progressInfo = propagator()->_journal->getUploadInfo(_item->_file);
|
||||
|
||||
if (progressInfo._valid && progressInfo._modtime == _item->_modtime
|
||||
if (progressInfo._valid && progressInfo.isChunked() && progressInfo._modtime == _item->_modtime
|
||||
&& (progressInfo._contentChecksum == _item->_checksumHeader || progressInfo._contentChecksum.isEmpty() || _item->_checksumHeader.isEmpty())) {
|
||||
_startChunk = progressInfo._chunk;
|
||||
_transferId = progressInfo._transferid;
|
||||
|
@ -55,7 +55,7 @@ void PropagateUploadFileV1::doStartUpload()
|
|||
SyncJournalDb::UploadInfo pi;
|
||||
pi._valid = true;
|
||||
pi._chunk = 0;
|
||||
pi._transferid = _transferId;
|
||||
pi._transferid = 0; // We set a null transfer id because it is not chunked.
|
||||
pi._modtime = _item->_modtime;
|
||||
pi._errorCount = 0;
|
||||
pi._contentChecksum = _item->_checksumHeader;
|
||||
|
@ -145,6 +145,8 @@ void PropagateUploadFileV1::startNextChunk()
|
|||
connect(job, &PUTFileJob::uploadProgress, this, &PropagateUploadFileV1::slotUploadProgress);
|
||||
connect(job, &PUTFileJob::uploadProgress, device, &UploadDevice::slotJobUploadProgress);
|
||||
connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
|
||||
if (isFinalChunk)
|
||||
adjustLastJobTimeout(job, fileSize);
|
||||
job->start();
|
||||
propagator()->_activeJobList.append(this);
|
||||
_currentChunk++;
|
||||
|
@ -206,12 +208,12 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
|
||||
// The server needs some time to process the request and provide us with a poll URL
|
||||
if (_item->_httpErrorCode == 202) {
|
||||
_finished = true;
|
||||
QString path = QString::fromUtf8(job->reply()->rawHeader("OC-Finish-Poll"));
|
||||
if (path.isEmpty()) {
|
||||
done(SyncFileItem::NormalError, tr("Poll URL missing"));
|
||||
return;
|
||||
}
|
||||
_finished = true;
|
||||
startPollJob(path);
|
||||
return;
|
||||
}
|
||||
|
@ -226,19 +228,15 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
// yet, the upload can be stopped and an error can be displayed, because
|
||||
// the server hasn't registered the new file yet.
|
||||
QByteArray etag = getEtagFromReply(job->reply());
|
||||
bool finished = etag.length() > 0;
|
||||
_finished = etag.length() > 0;
|
||||
|
||||
/* Check if the file still exists,
|
||||
* but we could be operating in a temporary file, so check both if
|
||||
* the file to upload is different than the file on disk
|
||||
*/
|
||||
const QString fileToUploadPath = _fileToUpload._path;
|
||||
const QString fullFilePath(propagator()->getFilePath(_item->_file));
|
||||
bool fileExists = fileToUploadPath == fullFilePath ? FileSystem::fileExists(fullFilePath)
|
||||
: (FileSystem::fileExists(fileToUploadPath) && FileSystem::fileExists(fullFilePath));
|
||||
|
||||
if (!fileExists) {
|
||||
if (!finished) {
|
||||
if (!FileSystem::fileExists(fullFilePath)) {
|
||||
if (!_finished) {
|
||||
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
|
||||
return;
|
||||
} else {
|
||||
|
@ -249,7 +247,7 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
// Check whether the file changed since discovery. the file check here is the original and not the temprary.
|
||||
if (!FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
|
||||
propagator()->_anotherSyncNeeded = true;
|
||||
if (!finished) {
|
||||
if (!_finished) {
|
||||
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
|
||||
// FIXME: the legacy code was retrying for a few seconds.
|
||||
// and also checking that after the last chunk, and removed the file in case of INSTRUCTION_NEW
|
||||
|
@ -257,14 +255,13 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
}
|
||||
}
|
||||
|
||||
if (!finished) {
|
||||
if (!_finished) {
|
||||
// Proceed to next chunk.
|
||||
if (_currentChunk >= _chunkCount) {
|
||||
if (!_jobs.empty()) {
|
||||
// just wait for the other job to finish.
|
||||
return;
|
||||
}
|
||||
_finished = true;
|
||||
done(SyncFileItem::NormalError, tr("The server did not acknowledge the last chunk. (No e-tag was present)"));
|
||||
return;
|
||||
}
|
||||
|
@ -294,9 +291,8 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
startNextChunk();
|
||||
return;
|
||||
}
|
||||
|
||||
// the following code only happens after all chunks were uploaded.
|
||||
_finished = true;
|
||||
|
||||
// the file id should only be empty for new files up- or downloaded
|
||||
QByteArray fid = job->reply()->rawHeader("OC-FileID");
|
||||
if (!fid.isEmpty()) {
|
||||
|
@ -316,6 +312,7 @@ void PropagateUploadFileV1::slotPutFinished()
|
|||
qCWarning(lcPropagateUpload) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime");
|
||||
// Well, the mtime was not set
|
||||
done(SyncFileItem::SoftError, "Server does not support X-OC-MTime");
|
||||
return;
|
||||
}
|
||||
|
||||
finalize();
|
||||
|
@ -357,28 +354,19 @@ void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total)
|
|||
|
||||
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
|
||||
abortNetworkJobs(
|
||||
abortType,
|
||||
[this, abortType](AbstractNetworkJob *job) {
|
||||
if (PUTFileJob *putJob = qobject_cast<PUTFileJob *>(job)){
|
||||
if (abortType == AbortType::Asynchronous
|
||||
&& _chunkCount > 0
|
||||
&& (((_currentChunk + _startChunk) % _chunkCount) == 0)
|
||||
&& putJob->device()->atEnd()) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Abort the job
|
||||
job->reply()->abort();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -120,35 +120,11 @@ QString SyncEngine::csyncErrorToString(CSYNC_STATUS err)
|
|||
errStr = tr("Success.");
|
||||
break;
|
||||
case CSYNC_STATUS_STATEDB_LOAD_ERROR:
|
||||
errStr = tr("CSync failed to load or create the journal file. "
|
||||
errStr = tr("Failed to load or create the journal file. "
|
||||
"Make sure you have read and write permissions in the local sync folder.");
|
||||
break;
|
||||
case CSYNC_STATUS_STATEDB_CORRUPTED:
|
||||
errStr = tr("CSync failed to load the journal file. The journal file is corrupted.");
|
||||
break;
|
||||
case CSYNC_STATUS_NO_MODULE:
|
||||
errStr = tr("<p>The %1 plugin for csync could not be loaded.<br/>Please verify the installation!</p>").arg(qApp->applicationName());
|
||||
break;
|
||||
case CSYNC_STATUS_PARAM_ERROR:
|
||||
errStr = tr("CSync fatal parameter error.");
|
||||
break;
|
||||
case CSYNC_STATUS_UPDATE_ERROR:
|
||||
errStr = tr("CSync processing step update failed.");
|
||||
break;
|
||||
case CSYNC_STATUS_RECONCILE_ERROR:
|
||||
errStr = tr("CSync processing step reconcile failed.");
|
||||
break;
|
||||
case CSYNC_STATUS_PROXY_AUTH_ERROR:
|
||||
errStr = tr("CSync could not authenticate at the proxy.");
|
||||
break;
|
||||
case CSYNC_STATUS_LOOKUP_ERROR:
|
||||
errStr = tr("CSync failed to lookup proxy or server.");
|
||||
break;
|
||||
case CSYNC_STATUS_SERVER_AUTH_ERROR:
|
||||
errStr = tr("CSync failed to authenticate at the %1 server.").arg(qApp->applicationName());
|
||||
break;
|
||||
case CSYNC_STATUS_CONNECT_ERROR:
|
||||
errStr = tr("CSync failed to connect to the network.");
|
||||
errStr = tr("Discovery step failed.");
|
||||
break;
|
||||
case CSYNC_STATUS_TIMEOUT:
|
||||
errStr = tr("A network connection timeout happened.");
|
||||
|
@ -157,16 +133,16 @@ QString SyncEngine::csyncErrorToString(CSYNC_STATUS err)
|
|||
errStr = tr("A HTTP transmission error happened.");
|
||||
break;
|
||||
case CSYNC_STATUS_PERMISSION_DENIED:
|
||||
errStr = tr("CSync failed due to unhandled permission denied.");
|
||||
errStr = tr("Permission denied.");
|
||||
break;
|
||||
case CSYNC_STATUS_NOT_FOUND:
|
||||
errStr = tr("CSync failed to access") + " "; // filename gets added.
|
||||
errStr = tr("File or directory not found:") + " "; // filename gets added.
|
||||
break;
|
||||
case CSYNC_STATUS_FILE_EXISTS:
|
||||
errStr = tr("CSync tried to create a folder that already exists.");
|
||||
errStr = tr("Tried to create a folder that already exists.");
|
||||
break;
|
||||
case CSYNC_STATUS_OUT_OF_SPACE:
|
||||
errStr = tr("CSync: No space on %1 server available.").arg(qApp->applicationName());
|
||||
errStr = tr("No space on %1 server available.").arg(qApp->applicationName());
|
||||
break;
|
||||
case CSYNC_STATUS_UNSUCCESSFUL:
|
||||
errStr = tr("CSync unspecified error.");
|
||||
|
@ -327,6 +303,8 @@ void SyncEngine::deleteStaleUploadInfos(const SyncFileItemVector &syncItems)
|
|||
// Delete the stales chunk on the server.
|
||||
if (account()->capabilities().chunkingNg()) {
|
||||
foreach (uint transferId, ids) {
|
||||
if (!transferId)
|
||||
continue; // Was not a chunked upload
|
||||
QUrl url = Utility::concatUrlPath(account()->url(), QLatin1String("remote.php/dav/uploads/") + account()->davUser() + QLatin1Char('/') + QString::number(transferId));
|
||||
(new DeleteJob(account(), url, this))->start();
|
||||
}
|
||||
|
@ -385,16 +363,6 @@ void SyncEngine::conflictRecordMaintenance()
|
|||
}
|
||||
}
|
||||
|
||||
int SyncEngine::treewalkLocal(csync_file_stat_t *file, csync_file_stat_t *other, void *data)
|
||||
{
|
||||
return static_cast<SyncEngine *>(data)->treewalkFile(file, other, false);
|
||||
}
|
||||
|
||||
int SyncEngine::treewalkRemote(csync_file_stat_t *file, csync_file_stat_t *other, void *data)
|
||||
{
|
||||
return static_cast<SyncEngine *>(data)->treewalkFile(file, other, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The main function in the post-reconcile phase.
|
||||
*
|
||||
|
@ -771,7 +739,7 @@ void SyncEngine::handleSyncError(CSYNC *ctx, const char *state)
|
|||
|
||||
if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_ABORTED)) {
|
||||
qCInfo(lcEngine) << "Update phase was aborted by user!";
|
||||
} else if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_SERVICE_UNAVAILABLE) || CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_CONNECT_ERROR)) {
|
||||
} else if (CSYNC_STATUS_IS_EQUAL(err, CSYNC_STATUS_SERVICE_UNAVAILABLE)) {
|
||||
emit csyncUnavailable();
|
||||
} else {
|
||||
csyncError(errStr);
|
||||
|
@ -1019,11 +987,11 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
|||
_temporarilyUnavailablePaths.clear();
|
||||
_renamedFolders.clear();
|
||||
|
||||
if (csync_walk_local_tree(_csync_ctx.data(), &treewalkLocal, 0) < 0) {
|
||||
if (csync_walk_local_tree(_csync_ctx.data(), [this](csync_file_stat_t *f, csync_file_stat_t *o) { return treewalkFile(f, o, false); } ) < 0) {
|
||||
qCWarning(lcEngine) << "Error in local treewalk.";
|
||||
walkOk = false;
|
||||
}
|
||||
if (walkOk && csync_walk_remote_tree(_csync_ctx.data(), &treewalkRemote, 0) < 0) {
|
||||
if (walkOk && csync_walk_remote_tree(_csync_ctx.data(), [this](csync_file_stat_t *f, csync_file_stat_t *o) { return treewalkFile(f, o, true); } ) < 0) {
|
||||
qCWarning(lcEngine) << "Error in remote treewalk.";
|
||||
}
|
||||
|
||||
|
|
|
@ -203,8 +203,6 @@ private:
|
|||
|
||||
QString journalDbFilePath() const;
|
||||
|
||||
static int treewalkLocal(csync_file_stat_t *file, csync_file_stat_t *other, void *);
|
||||
static int treewalkRemote(csync_file_stat_t *file, csync_file_stat_t *other, void *);
|
||||
int treewalkFile(csync_file_stat_t *file, csync_file_stat_t *other, bool);
|
||||
bool checkErrorBlacklisting(SyncFileItem &item);
|
||||
|
||||
|
|
|
@ -75,10 +75,9 @@ public:
|
|||
/** For files whose errors were blacklisted
|
||||
*
|
||||
* If an file is blacklisted due to an error it isn't even reattempted. These
|
||||
* errors should appear in the issues tab, but not on the account settings and
|
||||
* should not cause the sync run to fail.
|
||||
* errors should appear in the issues tab but should be silent otherwise.
|
||||
*
|
||||
* A DetailError that doesn't cause sync failure.
|
||||
* A SoftError caused by blacklisting.
|
||||
*/
|
||||
BlacklistedError
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "owncloudlib.h"
|
||||
#include <QString>
|
||||
#include <chrono>
|
||||
|
||||
|
||||
namespace OCC {
|
||||
|
@ -54,7 +55,7 @@ struct SyncOptions
|
|||
*
|
||||
* Set to 0 it will disable dynamic chunk sizing.
|
||||
*/
|
||||
quint64 _targetChunkUploadDuration = 60 * 1000; // 1 minute
|
||||
std::chrono::milliseconds _targetChunkUploadDuration = std::chrono::minutes(1);
|
||||
|
||||
/** Whether parallel network jobs are allowed. */
|
||||
bool _parallelNetworkJobs = true;
|
||||
|
|
|
@ -271,7 +271,8 @@ static void check_csync_excluded_traversal(void **)
|
|||
assert_int_equal(check_file_traversal("subdir/.sync_5bdd60bdfcfa.db"), CSYNC_FILE_SILENTLY_EXCLUDED);
|
||||
|
||||
/* Other builtin excludes */
|
||||
assert_int_equal(check_file_traversal("foo/Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED);
|
||||
assert_int_equal(check_file_traversal("foo/Desktop.ini"), CSYNC_NOT_EXCLUDED);
|
||||
assert_int_equal(check_file_traversal("Desktop.ini"), CSYNC_FILE_SILENTLY_EXCLUDED);
|
||||
|
||||
/* pattern ]*.directory - ignore and remove */
|
||||
assert_int_equal(check_file_traversal("my.~directory"), CSYNC_FILE_EXCLUDE_AND_REMOVE);
|
||||
|
|
|
@ -325,16 +325,6 @@ static void check_csync_detect_update_db_new(void **state)
|
|||
csync_set_status(csync, 0xFFFF);
|
||||
}
|
||||
|
||||
static void check_csync_detect_update_null(void **state)
|
||||
{
|
||||
CSYNC *csync = (CSYNC*)*state;
|
||||
std::unique_ptr<csync_file_stat_t> fs;
|
||||
int rc;
|
||||
|
||||
rc = _csync_detect_update(csync, NULL);
|
||||
assert_int_equal(rc, -1);
|
||||
}
|
||||
|
||||
static void check_csync_ftw(void **state)
|
||||
{
|
||||
CSYNC *csync = (CSYNC*)*state;
|
||||
|
@ -370,7 +360,6 @@ int torture_run_tests(void)
|
|||
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_eval, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_rename, setup, teardown),
|
||||
cmocka_unit_test_setup_teardown(check_csync_detect_update_db_new, setup, teardown_rm),
|
||||
cmocka_unit_test_setup_teardown(check_csync_detect_update_null, setup, teardown_rm),
|
||||
|
||||
cmocka_unit_test_setup_teardown(check_csync_ftw, setup_ftw, teardown_rm),
|
||||
cmocka_unit_test_setup_teardown(check_csync_ftw_empty_uri, setup_ftw, teardown_rm),
|
||||
|
|
|
@ -189,16 +189,11 @@ private slots:
|
|||
|
||||
// Make the MOVE never reply, but trigger a client-abort and apply the change remotely
|
||||
auto parent = new QObject;
|
||||
QByteArray moveChecksumHeader;
|
||||
int nGET = 0;
|
||||
int responseDelay = 2000; // smaller than abort-wait timeout
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *) -> QNetworkReply * {
|
||||
if (request.attribute(QNetworkRequest::CustomVerbAttribute) == "MOVE") {
|
||||
QTimer::singleShot(50, parent, [&]() { fakeFolder.syncEngine().abort(); });
|
||||
moveChecksumHeader = request.rawHeader("OC-Checksum");
|
||||
return new DelayedReply<FakeChunkMoveReply>(responseDelay, fakeFolder.uploadState(), fakeFolder.remoteModifier(), op, request, parent);
|
||||
} else if (op == QNetworkAccessManager::GetOperation) {
|
||||
nGET++;
|
||||
}
|
||||
return nullptr;
|
||||
});
|
||||
|
@ -389,6 +384,10 @@ private slots:
|
|||
int responseDelay = AbstractNetworkJob::httpTimeout * 1000 * 1000; // much bigger than http timeout (so a timeout will occur)
|
||||
// This will perform the operation on the server, but the reply will not come to the client
|
||||
fakeFolder.setServerOverride([&](QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) -> QNetworkReply * {
|
||||
if (!chunking) {
|
||||
Q_ASSERT(!request.url().path().contains("/uploads/")
|
||||
&& "Should not touch uploads endpoint when not chunking");
|
||||
}
|
||||
if (!chunking && op == QNetworkAccessManager::PutOperation) {
|
||||
checksumHeader = request.rawHeader("OC-Checksum");
|
||||
return new DelayedReply<FakePutReply>(responseDelay, fakeFolder.remoteModifier(), op, request, outgoingData->readAll(), &fakeFolder.syncEngine());
|
||||
|
|
Loading…
Reference in a new issue