Merge branch 'master' into upstream/owncloudsynclog

This commit is contained in:
Julius Härtl 2018-06-07 14:38:49 +02:00 committed by GitHub
commit 689ab5b8b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 10330 additions and 4928 deletions

View file

@ -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.

View file

@ -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)

View file

@ -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:

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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)

View file

@ -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

View file

@ -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``.

View file

@ -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:

View file

@ -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``

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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`.

View file

@ -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.

View file

@ -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
View 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

View file

@ -23,6 +23,7 @@
NSMutableSet *_registeredDirectories;
NSString *_shareMenuTitle;
NSMutableDictionary *_strings;
NSMutableArray *_menuItems;
}
@end

View file

@ -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];

View file

@ -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

View file

@ -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) {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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

View file

@ -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() {

View file

@ -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.

View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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 );
/**

View file

@ -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: */

View file

@ -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);
/**
* }@

View file

@ -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);
}

View file

@ -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());

View file

@ -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);

View file

@ -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;
}

View file

@ -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)

View file

@ -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);

View file

@ -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());
}

View file

@ -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);

View file

@ -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.

View file

@ -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.

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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.

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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();

View file

@ -14,6 +14,8 @@
#include "ocsshareejob.h"
#include <QJsonDocument>
namespace OCC {
OcsShareeJob::OcsShareeJob(AccountPtr account)

View file

@ -21,6 +21,8 @@
#include <QList>
#include <QPair>
class QJsonDocument;
namespace OCC {
/**

View file

@ -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;

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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;

View file

@ -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 &amp;&amp; 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>

View file

@ -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();
}

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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++;
}
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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

View file

@ -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)
{

View file

@ -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();

View file

@ -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; }

View file

@ -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();

View file

@ -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 &);

View file

@ -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;
}

View file

@ -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);
}
}
}
}

View file

@ -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

View file

@ -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
});

View file

@ -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;
}

View file

@ -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.

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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();
}
}

View file

@ -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:

View file

@ -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);
});
}
}

View file

@ -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;
});
}
}

View file

@ -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.";
}

View file

@ -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);

View file

@ -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
};

View file

@ -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;

View file

@ -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);

View file

@ -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),

View file

@ -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());