Comparar commits

..

1 Commits

Autor SHA1 Mensagem Data
Olivier Goffart 621f984dc3 SyncEngine: Fix renaming a folder should keep the selective sync state
Issue #5224

Two problems:

 - In the discovery phase, we need to check the selective sync entries of
   the source path in case of renames.

 - When the rename is done, we need to actually update the black list in the
   database.
2016-10-11 14:33:44 +02:00
125 arquivos alterados com 15576 adições e 19837 exclusões
+1 -1
Ver Arquivo
@@ -27,7 +27,7 @@ https://github.com/owncloud/client.
## Building the source code
[Building the Client](http://doc.owncloud.org/desktop/2.2/building.html)
[Building the Client](http://doc.owncloud.org/desktop/2.0/building.html)
in the ownCloud Desktop Client manual.
## Maintainers and Contributors
+2 -2
Ver Arquivo
@@ -9,7 +9,6 @@ StrCpy $PageReinstall_NEW_Field_3 "No instal·lar"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Ja instal·lat"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Trieu la manera com voleu instal·lar ${APPLICATION_NAME}."
StrCpy $PageReinstall_OLD_Field_1 "Una versió més recent de ${APPLICATION_NAME} ja està instal.lada!! No es recomana instal.lar una versió més antiga. Si realment voleu instal.lar una versió més antiga, és millor primer desinstal.lar la versió actual. Seleccioni l'operació que desitjeu realitzar i feu clic a Següent per a continuar."
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} ja està instal·lat.$\n$\nSeleccioneu la operació que voleu fer i feu clic a Següent per continuar."
StrCpy $PageReinstall_SAME_Field_2 "Afegir/Reinstal.lar components"
StrCpy $PageReinstall_SAME_Field_3 "Desinstal.lar ${APPLICATION_NAME}"
StrCpy $UNINSTALLER_APPDATA_TITLE "Desinstal.lar ${APPLICATION_NAME}"
@@ -38,6 +37,7 @@ StrCpy $UAC_ERROR_ELEVATE "No es pot elevar, error:"
StrCpy $UAC_INSTALLER_REQUIRE_ADMIN "Aquest instal·lador requereix accés d'administrador, intenteu-ho de nou"
StrCpy $INIT_INSTALLER_RUNNING "L'instal·lador ja s'està executant."
StrCpy $UAC_UNINSTALLER_REQUIRE_ADMIN "Aquest desinstal·lador requereix accés d'administrador, intenteu-ho de nou."
StrCpy $UAC_ERROR_LOGON_SERVICE "El servei de inici de sessió no s'està executant, s'està abortant!"
StrCpy $INIT_UNINSTALLER_RUNNING "El desinstal·lador ja s'està executant."
StrCpy $SectionGroup_Shortcuts "Dreceres"
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} is already installed.$\r$\nSelect the operation you want to perform and click Next to continue."
StrCpy $UAC_ERROR_LOGON_SERVICE "Logon service is not running, aborting!"
+1 -1
Ver Arquivo
@@ -2,7 +2,7 @@
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Mostrar las notas de la versión"
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "El/los proceso/s ${APPLICATION_EXECUTABLE} debe/n ser detenidos.$\n¿Quiere que el instalador lo haga por usted?"
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Deteniendo el/los proceso/s ${APPLICATION_EXECUTABLE}."
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "¡Proceso a finalizar no encontrado!"
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "¡Proceso para finalizar no encontrado!"
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} se encuentra instalada en el sistema. Se recomienda de instalar la versión actual antes de instalar la nueva. Seleccione la operacion deseada y haga click en Siguiente para continuar."
StrCpy $PageReinstall_NEW_Field_2 "Desinstalar antes de instalar"
StrCpy $PageReinstall_NEW_Field_3 "No desinstalar"
+1 -1
Ver Arquivo
@@ -32,7 +32,7 @@ find_library(CMOCKA_LIBRARY
NAMES
cmocka
PATHS
${CMOCKA_ROOT_DIR}/lib
${CMOCKA_ROOT_DIR}/include
)
if (CMOCKA_LIBRARY)
+1 -15
Ver Arquivo
@@ -65,21 +65,7 @@ static c_rbnode_t *_csync_check_ignored(c_rbtree_t *tree, const char *path, int
}
}
/**
* The main function in the reconcile pass.
*
* It's called for each entry in the local and remote rbtrees by
* csync_reconcile()
*
* Before the reconcile phase the trees already know about changes
* relative to the sync journal. This function's job is to spot conflicts
* between local and remote changes and adjust the nodes accordingly.
*
* See doc/dev/sync-algorithm.md for an overview.
*
*
* Older detail comment:
*
/*
* We merge replicas at the file level. The merged replica contains the
* superset of files that are on the local machine and server copies of
* the replica. In the case where the same file is in both the local
+1 -13
Ver Arquivo
@@ -158,19 +158,7 @@ static bool _csync_mtime_equal(time_t a, time_t b)
return false;
}
/**
* The main function of the discovery/update pass.
*
* It's called (indirectly) by csync_update(), once for each entity in the
* local filesystem and once for each entity in the server data.
*
* It has two main jobs:
* - figure out whether anything happened compared to the sync journal
* and set (primarily) the instruction flag accordingly
* - build the ctx->local.tree / ctx->remote.tree
*
* See doc/dev/sync-algorithm.md for an overview.
*/
static int _csync_detect_update(CSYNC *ctx, const char *file,
const csync_vio_file_stat_t *fs, const int type) {
uint64_t h = 0;
-31
Ver Arquivo
@@ -232,37 +232,6 @@ assertLocalAndRemoteDir( '', 0);
system("sqlite3 " . localDir().'.csync_journal.db .dump');
#######################################################################
printInfo( "multiple restores of a file create different conflict files" );
system("sleep 1"); #make sure changes have different mtime
system("echo 'modified_1' > ". localDir() . "readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN_.data");
#do the sync
csync();
assertCsyncJournalOk(localDir());
system("sleep 1"); #make sure changes have different mtime
system("echo 'modified_2' > ". localDir() . "readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN_.data");
#do the sync
csync();
assertCsyncJournalOk(localDir());
# there should be two conflict files
# TODO check that the conflict file has the right content
my @conflicts = glob(localDir().'readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN__conflict-*.data' );
assert( scalar @conflicts == 2 );
# remove the conflicts for the next assertLocalAndRemoteDir
system("rm " . localDir().'readonlyDirectory_PERM_M_/canotBeModified_PERM_DVN__conflict-*.data' );
### Both side should still be the same
assertLocalAndRemoteDir( '', 0);
cleanup();
+3 -18
Ver Arquivo
@@ -42,8 +42,7 @@ repositories`_ to see all the Linux client repos.
echo 'deb-src
http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Debian_8.0/ /' >> /etc/apt/sources.list.d/owncloud-client.list
2. Install the dependencies using the following commands for your specific Linux
distribution. Make sure the repositories for source packages are enabled.
2. Install the dependencies using the following commands for your specific Linux distribution:
* Debian/Ubuntu: ``apt-get update; apt-get build-dep owncloud-client``
* openSUSE/SLES: ``zypper ref; zypper si -d owncloud-client``
@@ -66,18 +65,11 @@ recipes.
To set up your build environment for development using HomeBrew_:
1. Install Xcode
2. Install Xcode command line tools::
xcode-select --install
3. Install homebrew::
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
4. Add the ownCloud repository using the following command::
1. Add the ownCloud repository using the following command::
brew tap owncloud/owncloud
5. Install any missing dependencies::
2. Install any missing dependencies::
brew install $(brew deps owncloud-client)
@@ -87,9 +79,6 @@ To set up your build environment for development using HomeBrew_:
Where ``x.z`` is the current version of Qt 5 that brew has installed
on your machine.
4. 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. -
``DCMAKE_INSTALL_PREFIX=/Path/to/client-install``
5. For compilation of the client, follow the :ref:`generic-build-instructions`.
@@ -244,10 +233,6 @@ To build the most up-to-date version of the client:
.. note:: On Mac OS X, 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/ -D_OPENSSL_LIBDIR=/usr/local/opt/openssl/lib/ -D_OPENSSL_INCLUDEDIR=/usr/local/opt/openssl/include/ -DOPENSSL_INCLUDE_DIR=/usr/local/opt/openssl/include/ -DNO_SHIBBOLETH=1
4. Call ``make``.
-84
Ver Arquivo
@@ -1,84 +0,0 @@
Sync Algorithm
==============
Overview
--------
This is a technical description of the synchronization (sync) algorithm used by the ownCloud client.
The sync algorithm is the thing that looks at the local and remote file system trees and the sync journal and decides which steps need to be taken to bring the two trees into synchronization. It's different from the propagator, whose job it is to actually execute these steps.
Definitions
-----------
- local tree: The files and directories on the local file system that shall be kept in sync with the remote tree.
- remote tree: The files and directories on the ownCloud server that shall be kept in sync with the local tree.
- sync journal (journal): A snapshot of file and directory metadata that the sync algorithm uses as a baseline to detect local or remote changes. Typically stored in a database.
- file and directory metadata:
- mtimes
- sizes
- inodes (journal and local only): Representation of filesystem object. Useful for rename detection.
- etags (journal and remote only): The server assigns a new etag when a file or directory changes.
- checksums (journal and remote only): Checksum algorithm applied to a file's contents.
- permissions (journal and remote only)
Phases
------
### Discovery (aka Update)
The discovery phase collects file and directory metadata from the local and remote trees, detecting differences between each tree and the journal.
Afterwards, we have two trees that tell us what happened relative to the journal. But there may still be conflicts if something happened to an entity both locally and on the remote.
- Input: file system, server data, journal
- Output: two c_rbtree_t*, representing the local and remote trees
- Note on remote discovery: Since a change to a file on the server causes the etags of all parent folders to change, folders with an unchanged etag can be read from the journal directly and don't need to be walked into.
- Details
- csync_update() uses csync_ftw() on the local and remote trees, one after the other.
- csync_ftw() iterates through the entities in a tree and calls csync_walker() for each.
- csync_walker() calls _csync_detect_update() on each.
- _csync_detect_update() compares the item to its journal entry (if any) to detect new, changed or renamed files. This is the main function of this pass.
### Reconcile
The reconcile phase compares and adjusts the local and remote trees (in both directions), detecting conflicts.
Afterwards, there are still two trees, but conflicts are marked in them.
- Input: c_rbtree_t* for the local and remote trees, journal (for some rename-related queries)
- Output: changes c_rbtree_t* in-place
- Details
- csync_reconcile() runs csync_reconcile_updates() for the local and remote trees, one after the other.
- csync_reconcile_updates() uses c_rbtree_walk() to iterate through the entries, calling _csync_merge_algorithm_visitor() for each.
- _csync_merge_algorithm_visitor() checks whether the other tree also has an entry for that node and merges the actions, detecting conflicts. This is the main function of this pass.
### Post-Reconcile
The post-reconcile phase merges the two trees into one set of SyncFileItems.
Afterwards, there is a list of items that can tell the propagator what needs to be done.
- Input: c_rbtree_t* for the local and remote trees
- Output: QMap<QString, SyncFileItemPtr>
- Note that some "propagations", specifically cheap metadata-only updates, are already done at this stage.
- Details
- csync_walk_local_tree() and csync_walk_remote_tree() are called.
- They use _csync_walk_tree() to run SyncEngine::treewalkFile() on each entry.
- treewalkFile() creates and fills SyncFileItems for each entry, ensuring that each file only has a single instance. This is the main function of this pass.
See Also
--------
An overview of the propagation steps is still missing. The sync protocol is documented at https://github.com/cernbox/smashbox/blob/master/protocol/protocol.md.
+4 -143
Ver Arquivo
@@ -355,144 +355,6 @@ X-GNOME-Autostart-Delay=3
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
Comment[oc]=@APPLICATION_NAME@ sincronizacion del client
GenericName[oc]=Dorsièr de Sincronizacion
@@ -583,10 +445,10 @@ Comment[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient
GenericName[cs_CZ]=Synchronizace adresáře
Name[cs_CZ]=@APPLICATION_NAME@ počítačový synchronizační klient
Icon[cs_CZ]=@APPLICATION_EXECUTABLE@
Comment[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
GenericName[ru]=Синхронизация каталогов
Name[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
Icon[ru]=@APPLICATION_EXECUTABLE@
Comment[ru]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
GenericName[ru]=Синхронизация папки
Name[ru]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
Icon[ru]=@ВЫПОЛНЯЕМОЕ_ПРИЛОЖЕНИЕ@
Comment[sl]=@APPLICATION_NAME@ Program za usklajevanje datotek z namizjem
GenericName[sl]=Usklajevanje map
Name[sl]=@APPLICATION_NAME@ Program za usklajevanje datotek z namizjem
@@ -628,7 +490,6 @@ Comment[th_TH]=@APPLICATION_NAME@ ไคลเอนต์ประสานข
GenericName[th_TH]=ประสานข้อมูลโฟลเดอร์
Name[th_TH]= @APPLICATION_NAME@ ไคลเอนต์ประสานข้อมูลเดสก์ท็อป
Icon[th_TH]=@APPLICATION_EXECUTABLE@
GenericName[es_MX]=Sincronización de Carpetas
Comment[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient
GenericName[nb_NO]=Mappesynkronisering
Name[nb_NO]=@APPLICATION_NAME@ skrivebordssynkroniseringsklient
-9
Ver Arquivo
@@ -4,7 +4,6 @@ if( UNIX AND NOT APPLE )
configure_file(syncstate.py syncstate.py COPYONLY)
configure_file(syncstate.py syncstate_nemo.py COPYONLY)
configure_file(syncstate.py syncstate_caja.py COPYONLY)
# Call the setupappname.sh script to set the custom app name.
set (cmd "${CMAKE_CURRENT_SOURCE_DIR}/setappname.sh")
@@ -19,17 +18,9 @@ if( UNIX AND NOT APPLE )
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ERROR_VARIABLE errors OUTPUT_VARIABLE out)
# Create a caja plugin script from the nautilus one.
# cajacmd copies the syncstate.py and performs string replacement.
set (cajacmd "${CMAKE_CURRENT_SOURCE_DIR}/createcajaplugin.sh")
execute_process(COMMAND ${cajacmd}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ERROR_VARIABLE errors OUTPUT_VARIABLE out)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate.py DESTINATION ${DATADIR}/nautilus-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate_nemo.py DESTINATION ${DATADIR}/nemo-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/syncstate_caja.py DESTINATION ${DATADIR}/caja-python/extensions RENAME syncstate-${APPLICATION_SHORTNAME}.py)
@@ -1,7 +0,0 @@
#!/bin/sh
# this script creates a plugin for caja, just by replacing
# all occurences of Nautilus with Caja (case sensitive).
sed -i.org -e 's/Nautilus/Caja/g' syncstate_caja.py
sed -i.org -e 's/nautilus/caja/g' syncstate_caja.py
+37 -66
Ver Arquivo
@@ -18,7 +18,6 @@
import os
import urllib
import socket
import tempfile
from gi.repository import GObject, Nautilus
@@ -44,7 +43,7 @@ def get_runtime_dir():
try:
return os.environ['XDG_RUNTIME_DIR']
except KeyError:
fallback = os.path.join(tempfile.gettempdir(), 'runtime-' + os.environ['USER'])
fallback = '/tmp/runtime-' + os.environ['USER']
return fallback
@@ -87,17 +86,22 @@ class SocketConnect(GObject.GObject):
def _connectToSocketServer(self):
try:
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
try:
print("Socket File: " + sock_file)
self._sock.connect(sock_file) # fails if sock_file doesn't exist
self.connected = True
print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: " + str(self._watch_id))
return False # Don't run again
except Exception as e:
print("Could not connect to unix socket. " + str(e))
postfix = "/" + appname + "/socket" # Should use os.path.join instead
sock_file = get_runtime_dir() + postfix
print ("Socket: " + sock_file + " <=> " + postfix)
if sock_file != postfix:
try:
print("Socket File: " + sock_file)
self._sock.connect(sock_file)
self.connected = True
print("Setting connected to %r." % self.connected )
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
print("Socket watch id: " + str(self._watch_id))
return False # Don't run again
except Exception as e:
print("Could not connect to unix socket. " + str(e))
else:
print("Sock-File not valid: " + sock_file)
except Exception as e: # Bad habbit
print("Connect could not be established, try again later.")
self._sock.close()
@@ -153,65 +157,32 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
def __init__(self):
GObject.GObject.__init__(self)
def check_registered_paths(self, filename):
topLevelFolder = False
internalFile = False
for reg_path in socketConnect.registered_paths:
if filename == reg_path:
topLevelFolder = True
break
if filename.startswith(reg_path):
internalFile = True
# you can't have a registered path below another so it is save to break here
break
return (topLevelFolder, internalFile)
def get_file_items(self, window, files):
# Show the menu extension to share a file or folder
#
# Show if file is OK.
# Ignore top level folders.
# Also show extension for folders
# if there is a OK or SYNC underneath.
# This is only
if len(files) != 1:
return
file = files[0]
items = []
filename = get_local_path(file.get_uri())
# Check if its a folder (ends with an /), if yes add a "/"
# otherwise it will not find the entry in the table
isDir = os.path.isdir(filename + os.sep)
if isDir:
filename += os.sep
# Internal or external file?!
syncedFile = False
for reg_path in socketConnect.registered_paths:
topLevelFolder = False
filename = get_local_path(file.get_uri())
# Check if its a folder (ends with an /), if yes add a "/"
# otherwise it will not find the entry in the table
if os.path.isdir(filename + "/"):
filename += "/"
# Check if toplevel folder, we need to ignore those as they cannot be shared
if filename == reg_path:
topLevelFolder=True
# Only show the menu extension if the file is synced and the sync
# status is ok. Not for ignored files etc.
# ignore top level folders
if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'].startswith('OK'):
syncedFile = True
# Check if toplevel folder, we need to ignore those as they cannot be shared
topLevelFolder, internalFile = self.check_registered_paths(filename)
if topLevelFolder or not internalFile:
return items
entry = socketConnect.nautilusVFSFile_table.get(filename)
if not entry:
return items
shareable = False
state = entry['state']
state_ok = state.startswith('OK')
state_sync = state.startswith('SYNC')
if state_ok:
shareable = True
elif state_sync and isDir:
# some file below is OK or SYNC
for key, value in socketConnect.nautilusVFSFile_table.items():
if key != filename and key.startswith(filename):
state = value['state']
if state.startswith('OK') or state.startswith('SYNC'):
shareable = True
break
if not shareable:
# If it is neither in a synced folder or is a directory
if not syncedFile:
return items
# Create a menu item
@@ -332,7 +303,7 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
filename = get_local_path(item.get_uri())
if item.is_directory():
filename += os.sep
filename += '/'
inScope = False
for reg_path in socketConnect.registered_paths:
+161 -17
Ver Arquivo
@@ -1,21 +1,165 @@
The MIT License (MIT)
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2011 Morgan Leborgne
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
+20 -23
Ver Arquivo
@@ -1,27 +1,24 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2011 Morgan Leborgne
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
*
* This file is part of QProgressIndicator,
* an open-source recent files menu widget
*
* Copyright (C) 2009 - 2010 Morgan Leborgne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with QRecentFilesMenu. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "QProgressIndicator.h"
#include <QPainter>
+24 -27
Ver Arquivo
@@ -1,27 +1,24 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2011 Morgan Leborgne
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
*
* This file is part of QProgressIndicator,
* an open-source recent files menu widget
*
* Copyright (C) 2009 - 2010 Morgan Leborgne
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with QRecentFilesMenu. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef QPROGRESSINDICATOR_H
#define QPROGRESSINDICATOR_H
@@ -67,8 +64,8 @@ public:
*/
const QColor & color() const { return m_color; }
virtual QSize sizeHint() const;
int heightForWidth(int w) const;
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
int heightForWidth(int w) const Q_DECL_OVERRIDE;
public slots:
/*! Starts the spin animation.
\sa stopAnimation isAnimated
@@ -98,8 +95,8 @@ public slots:
*/
void setColor(const QColor & color);
protected:
virtual void timerEvent(QTimerEvent * event);
virtual void paintEvent(QPaintEvent * event);
virtual void timerEvent(QTimerEvent * event) Q_DECL_OVERRIDE;
virtual void paintEvent(QPaintEvent * event) Q_DECL_OVERRIDE;
private:
int m_angle;
int m_timerId;
+1 -1
Ver Arquivo
@@ -11,4 +11,4 @@ Qt 4.4.x.
## License
MIT
LGPL
+2678 -3731
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+267 -421
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
-5
Ver Arquivo
@@ -99,7 +99,6 @@ set(client_SRCS
wizard/owncloudadvancedsetuppage.cpp
wizard/owncloudconnectionmethoddialog.cpp
wizard/owncloudhttpcredspage.cpp
wizard/owncloudbrowsercredspage.cpp
wizard/owncloudsetuppage.cpp
wizard/owncloudwizardcommon.cpp
wizard/owncloudwizard.cpp
@@ -200,9 +199,6 @@ IF( WIN32 )
${CMAKE_CURRENT_BINARY_DIR}/version.rc
@ONLY)
set(client_version ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
IF(NOT MSVC)
set(client_manifest ${CMAKE_CURRENT_SOURCE_DIR}/manifest-mingw.rc)
ENDIF()
ENDIF()
set( final_src
@@ -210,7 +206,6 @@ set( final_src
${client_SRCS}
${client_UI_SRCS}
${client_version}
${client_manifest}
${guiMoc}
${client_I18N}
${3rdparty_SRC}
+12 -6
Ver Arquivo
@@ -44,7 +44,7 @@ AccountManager *AccountManager::instance()
bool AccountManager::restore()
{
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
// If there are no accounts, check the old format.
if (settings->childGroups().isEmpty()
@@ -69,7 +69,9 @@ bool AccountManager::restore()
bool AccountManager::restoreFromLegacySettings()
{
// try to open the correctly themed settings
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
auto settings = Account::settingsWithGroup(Theme::instance()->appName());
bool migratedCreds = false;
// if the settings file could not be opened, the childKeys list is empty
// then try to load settings from a very old place
@@ -100,6 +102,7 @@ bool AccountManager::restoreFromLegacySettings()
qDebug() << "Migrate oC config if " << oCUrl << " == " << overrideUrl << ":"
<< (oCUrl == overrideUrl ? "Yes" : "No");
if( oCUrl == overrideUrl ) {
migratedCreds = true;
settings.reset( oCSettings );
} else {
delete oCSettings;
@@ -111,6 +114,9 @@ bool AccountManager::restoreFromLegacySettings()
// Try to load the single account.
if (!settings->childKeys().isEmpty()) {
if (auto acc = loadAccountHelper(*settings)) {
if (migratedCreds) {
acc->setMigrated(true);
}
addAccount(acc);
return true;
}
@@ -120,7 +126,7 @@ bool AccountManager::restoreFromLegacySettings()
void AccountManager::save(bool saveCredentials)
{
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
settings->setValue(QLatin1String(versionC), 2);
foreach (const auto &acc, _accounts) {
settings->beginGroup(acc->account()->id());
@@ -136,7 +142,7 @@ void AccountManager::save(bool saveCredentials)
void AccountManager::saveAccount(Account* a)
{
qDebug() << "Saving account" << a->url().toString();
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
settings->beginGroup(a->id());
saveAccountHelper(a, *settings, false); // don't save credentials they might not have been loaded yet
settings->endGroup();
@@ -148,7 +154,7 @@ void AccountManager::saveAccount(Account* a)
void AccountManager::saveAccountState(AccountState* a)
{
qDebug() << "Saving account state" << a->account()->url().toString();
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
settings->beginGroup(a->account()->id());
a->writeToSettings(*settings);
settings->endGroup();
@@ -274,7 +280,7 @@ void AccountManager::deleteAccount(AccountState* account)
auto copy = *it; // keep a reference to the shared pointer so it does not delete it just yet
_accounts.erase(it);
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
settings->remove(account->account()->id());
emit accountRemoved(account);
+3 -3
Ver Arquivo
@@ -321,7 +321,7 @@ void AccountSettings::slotFolderWizardAccepted()
// The user already accepted the selective sync dialog. everything is in the white list
f->journalDb()->setSelectiveSyncList(SyncJournalDb::SelectiveSyncWhiteList,
QStringList() << QLatin1String("/"));
folderMan->scheduleAllFolders();
folderMan->slotScheduleAllFolders();
emit folderChanged();
}
}
@@ -360,7 +360,7 @@ void AccountSettings::slotRemoveCurrentFolder()
return;
}
folderMan->removeFolder( folderMan->folder(alias) );
folderMan->slotRemoveFolder( folderMan->folder(alias) );
_model->removeRow(row);
// single folder fix to show add-button and hide remove-button
@@ -468,7 +468,7 @@ void AccountSettings::slotSyncCurrentFolderNow()
QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString();
FolderMan *folderMan = FolderMan::instance();
folderMan->scheduleFolder(folderMan->folder(alias));
folderMan->slotScheduleSync(folderMan->folder(alias));
}
void AccountSettings::slotOpenOC()
+1 -1
Ver Arquivo
@@ -306,7 +306,7 @@ void AccountState::slotCredentialsAsked(AbstractCredentials* credentials)
std::unique_ptr<QSettings> AccountState::settings()
{
auto s = Utility::settingsWithGroup(QLatin1String("Accounts"));
auto s = _account->settingsWithGroup(QLatin1String("Accounts"));
s->beginGroup(_account->id());
return s;
}
+3 -8
Ver Arquivo
@@ -336,14 +336,9 @@ void Application::slotownCloudWizardDone( int res )
_checkConnectionTimer.start();
slotCheckConnection();
// If one account is configured: enable autostart
bool shouldSetAutoStart = (accountMan->accounts().size() == 1);
#ifdef Q_OS_MAC
// Don't auto start when not being 'installed'
shouldSetAutoStart = shouldSetAutoStart
&& QCoreApplication::applicationDirPath().startsWith("/Applications/");
#endif
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), shouldSetAutoStart);
// The very first time an account is configured: enabled autostart
// TODO: Doing this every time the account wizard finishes will annoy users.
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
}
}
+3 -122
Ver Arquivo
@@ -15,14 +15,9 @@
#include <QInputDialog>
#include <QLabel>
#include <QDesktopServices>
#include <QNetworkReply>
#include <QTimer>
#include <QBuffer>
#include "creds/httpcredentialsgui.h"
#include "theme.h"
#include "account.h"
#include <json.h>
using namespace QKeychain;
@@ -31,31 +26,11 @@ namespace OCC
void HttpCredentialsGui::askFromUser()
{
_asyncAuth.reset(new AsyncAuth(_account, this));
connect(_asyncAuth.data(), SIGNAL(result(AsyncAuth::Result,QString)),
this, SLOT(asyncAuthResult(AsyncAuth::Result,QString)));
_asyncAuth->start();
// The rest of the code assumes that this will be done asynchronously
QMetaObject::invokeMethod(this, "askFromUserAsync", Qt::QueuedConnection);
}
void HttpCredentialsGui::asyncAuthResult(AsyncAuth::Result r, const QString& token)
{
if (r == AsyncAuth::Waiting) {
return;
}
if (r == AsyncAuth::NotSupported) {
// We will re-enter the event loop, so better wait the next iteration
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
return;
}
if (r == AsyncAuth::LoggedIn) {
_password = token;
_ready = true;
persist();
}
emit asked();
}
void HttpCredentialsGui::showDialog()
void HttpCredentialsGui::askFromUserAsync()
{
QString msg = tr("Please enter %1 password:<br>"
"<br>"
@@ -104,99 +79,5 @@ QString HttpCredentialsGui::requestAppPasswordText(const Account* account)
.arg(account->url().toString() + QLatin1String("/index.php/settings/personal?section=apppasswords"));
}
AsyncAuth::~AsyncAuth()
{
delete _reply;
}
void AsyncAuth::start()
{
QNetworkRequest req;
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
auto buffer = new QBuffer();
buffer->setData("name=" + Utility::userAgentString().toPercentEncoding());
QNetworkReply *reply = _account->davRequest("POST",
Utility::concatUrlPath(_account->url(), QLatin1String("/index.php/auth/start")),
req, buffer);
QObject::connect(reply, SIGNAL(finished()), this, SLOT(startFinished()));
buffer->setParent(reply);
QTimer::singleShot(30*1000, reply, SLOT(abort()));
_reply = reply;
// FIXME! timeout
// _state = AsyncAuth::QueryUrlState;
}
// We get a reply from /index.php/auth/start
void AsyncAuth::startFinished()
{
auto reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply);
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "auth/start returned error" << reply->errorString();
// FIXME! we should make a difference between network errors and file not found error.
emit result(NotSupported, QString());
return;
}
const QString replyData = reply->readAll();
bool success;
QVariantMap json = QtJson::parse(replyData, success).toMap();
if (!success) {
qDebug() << "could not parse json" << replyData;
emit result(NotSupported, QString());
return;
}
auto clientUrl = QUrl::fromEncoded(json[QLatin1String("clientUrl")].toByteArray());
_pollUrl = QUrl::fromEncoded(json[QLatin1String("pollUrl")].toByteArray());
if (!_pollUrl.isValid() || !clientUrl.isValid()) {
qWarning() << "invalid urls from server" << json;
emit result(NotSupported, QString());
return;
}
if (!QDesktopServices::openUrl(clientUrl)) {
// We cannot open the browser, then we claim we don't support it.
emit result(NotSupported, QString());
return;
}
emit result(Waiting, QString());
QTimer::singleShot(1000, this, SLOT(poll()));
}
void AsyncAuth::poll()
{
QNetworkReply *reply = _account->davRequest("POST", _pollUrl, QNetworkRequest());
QObject::connect(reply, SIGNAL(finished()), this, SLOT(pollFinished()));
QTimer::singleShot(5*60*1000, reply, SLOT(abort()));
_reply = reply;
}
void AsyncAuth::pollFinished()
{
auto reply = qobject_cast<QNetworkReply *>(sender());
Q_ASSERT(reply);
const QString replyData = reply->readAll();
reply->deleteLater();
bool success;
QVariantMap json = QtJson::parse(replyData, success).toMap();
if (!success || json["status"].toUInt() != 1) {
QTimer::singleShot(4*1000, this, SLOT(poll()));
return;
}
auto token = json["token"].toString();
emit result(LoggedIn, token);
}
} // namespace OCC
+1 -40
Ver Arquivo
@@ -15,45 +15,10 @@
#pragma once
#include "creds/httpcredentials.h"
#include <QPointer>
namespace OCC
{
class AsyncAuth : public QObject
{
Q_OBJECT
public:
AsyncAuth(Account *account, QObject *parent)
: QObject(parent), _account(account)
{ }
~AsyncAuth();
enum Result { Waiting, NotSupported, LoggedIn };
void start();
public slots:
void startFinished();
void poll();
void pollFinished();
signals:
/**
* The state has changed.
* when logged in, token has the value of the token.
*/
void result(AsyncAuth::Result result, const QString &token);
private:
Account *_account;
QUrl _pollUrl;
// Makes sure the reply is destroyed when this object is closed
QPointer<QNetworkReply> _reply;
// State _state;
};
/**
* @brief The HttpCredentialsGui class
* @ingroup gui
@@ -64,13 +29,9 @@ public:
explicit HttpCredentialsGui() : HttpCredentials() {}
HttpCredentialsGui(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd) : HttpCredentials(user, password, certificatePath, certificatePasswd) {}
void askFromUser() Q_DECL_OVERRIDE;
Q_INVOKABLE void askFromUserAsync();
static QString requestAppPasswordText(const Account *account);
private slots:
void asyncAuthResult(AsyncAuth::Result, const QString &token);
void showDialog();
private:
QScopedPointer<AsyncAuth> _asyncAuth;
};
} // namespace OCC
+19 -4
Ver Arquivo
@@ -79,6 +79,21 @@ void ShibbolethCredentials::setAccount(Account* account)
}
}
bool ShibbolethCredentials::changed(AbstractCredentials* credentials) const
{
ShibbolethCredentials* other(qobject_cast< ShibbolethCredentials* >(credentials));
if (!other) {
return true;
}
if (_shibCookie != other->_shibCookie || _user != other->_user) {
return true;
}
return false;
}
QString ShibbolethCredentials::authType() const
{
return QString::fromLatin1("shibboleth");
@@ -127,7 +142,7 @@ void ShibbolethCredentials::fetchFromKeychain()
} else {
_url = _account->url();
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
job->setInsecureFallback(false);
job->setKey(keychainKey(_account->url().toString(), "shibAssertion"));
connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadJobDone(QKeychain::Job*)));
@@ -246,7 +261,7 @@ void ShibbolethCredentials::slotReadJobDone(QKeychain::Job *job)
addToCookieJar(_shibCookie);
}
// access
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
_ready = true;
_stillValid = true;
@@ -305,7 +320,7 @@ QByteArray ShibbolethCredentials::shibCookieName()
void ShibbolethCredentials::storeShibCookie(const QNetworkCookie &cookie)
{
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
// we don't really care if it works...
//connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteJobDone(QKeychain::Job*)));
job->setKey(keychainKey(_account->url().toString(), "shibAssertion"));
@@ -316,7 +331,7 @@ void ShibbolethCredentials::storeShibCookie(const QNetworkCookie &cookie)
void ShibbolethCredentials::removeShibCookie()
{
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
job->setKey(keychainKey(_account->url().toString(), "shibAssertion"));
job->start();
}
+1
Ver Arquivo
@@ -48,6 +48,7 @@ public:
ShibbolethCredentials(const QNetworkCookie &cookie);
void setAccount(Account* account) Q_DECL_OVERRIDE;
bool changed(AbstractCredentials* credentials) const Q_DECL_OVERRIDE;
QString authType() const Q_DECL_OVERRIDE;
QString user() const Q_DECL_OVERRIDE;
QNetworkAccessManager* getQNAM() const Q_DECL_OVERRIDE;
+58 -35
Ver Arquivo
@@ -58,6 +58,7 @@ Folder::Folder(const FolderDefinition& definition,
, _wipeDb(false)
, _proxyDirty(true)
, _lastSyncDuration(0)
, _forceSyncOnPollTimeout(false)
, _consecutiveFailingSyncs(0)
, _consecutiveFollowUpSyncs(0)
, _journal(definition.localPath)
@@ -111,11 +112,6 @@ Folder::Folder(const FolderDefinition& definition,
connect(_engine.data(), SIGNAL(seenLockedFile(QString)), FolderMan::instance(), SLOT(slotSyncOnceFileUnlocks(QString)));
connect(_engine.data(), SIGNAL(aboutToPropagate(SyncFileItemVector&)),
SLOT(slotLogPropagationStart()));
_scheduleSelfTimer.setSingleShot(true);
_scheduleSelfTimer.setInterval(SyncEngine::minimumFileAgeForUpload);
connect(&_scheduleSelfTimer, SIGNAL(timeout()),
SLOT(slotScheduleThisFolder()));
}
Folder::~Folder()
@@ -225,7 +221,7 @@ QString Folder::remotePath() const
QUrl Folder::remoteUrl() const
{
return Utility::concatUrlPath(_accountState->account()->davUrl(), remotePath());
return Account::concatUrlPath(_accountState->account()->davUrl(), remotePath());
}
bool Folder::syncPaused() const
@@ -279,7 +275,7 @@ void Folder::slotRunEtagJob()
AccountPtr account = _accountState->account();
if (_requestEtagJob) {
if (!_requestEtagJob.isNull()) {
qDebug() << Q_FUNC_INFO << remoteUrl().toString() << "has ETag job queued, not trying to sync";
return;
}
@@ -289,15 +285,47 @@ void Folder::slotRunEtagJob()
return;
}
// Do the ordinary etag check for the root folder and schedule a
// sync if it's different.
bool forceSyncIntervalExpired =
quint64(_timeSinceLastSyncDone.elapsed()) > ConfigFile().forceSyncInterval();
bool syncAgainAfterFail = _consecutiveFailingSyncs > 0 && _consecutiveFailingSyncs < 3;
_requestEtagJob = new RequestEtagJob(account, remotePath(), this);
_requestEtagJob->setTimeout(60*1000);
// check if the etag is different when retrieved
QObject::connect(_requestEtagJob, SIGNAL(etagRetreived(QString)), this, SLOT(etagRetreived(QString)));
FolderMan::instance()->slotScheduleETagJob(alias(), _requestEtagJob);
// The _requestEtagJob is auto deleting itself on finish. Our guard pointer _requestEtagJob will then be null.
// There are several conditions under which we trigger a full-discovery sync:
// * When a suitably long time has passed since the last sync finished
// * When the last sync failed (only a couple of times)
// * When the last sync requested another sync to be done (only a couple of times)
//
// Note that the etag check (see below) and the file watcher may also trigger
// syncs.
if (forceSyncIntervalExpired
|| _forceSyncOnPollTimeout
|| syncAgainAfterFail) {
if (forceSyncIntervalExpired) {
qDebug() << "** Force Sync, because it has been " << _timeSinceLastSyncDone.elapsed() << "ms "
<< "since the last sync";
}
if (_forceSyncOnPollTimeout) {
qDebug() << "** Force Sync, because it was requested";
}
if (syncAgainAfterFail) {
qDebug() << "** Force Sync, because the last"
<< _consecutiveFailingSyncs << "syncs failed, last status:"
<< _syncResult.statusString();
}
_forceSyncOnPollTimeout = false;
emit scheduleToSync(this);
} else {
// Do the ordinary etag check for the root folder and only schedule a real
// sync if it's different.
_requestEtagJob = new RequestEtagJob(account, remotePath(), this);
_requestEtagJob->setTimeout(60*1000);
// check if the etag is different
QObject::connect(_requestEtagJob, SIGNAL(etagRetreived(QString)), this, SLOT(etagRetreived(QString)));
FolderMan::instance()->slotScheduleETagJob(alias(), _requestEtagJob);
// The _requestEtagJob is auto deleting itself on finish. Our guard pointer _requestEtagJob will then be null.
}
}
void Folder::etagRetreived(const QString& etag)
@@ -310,7 +338,7 @@ void Folder::etagRetreived(const QString& etag)
if (_lastEtag != etag) {
qDebug() << "* Compare etag with previous etag: last:" << _lastEtag << ", received:" << etag << "-> CHANGED";
_lastEtag = etag;
slotScheduleThisFolder();
emit scheduleToSync(this);
}
_accountState->tagLastSuccessfullETagRequest();
@@ -570,10 +598,7 @@ void Folder::slotWatchedPathChanged(const QString& path)
}
emit watchedFileChangedExternally(path);
// Also schedule this folder for a sync, but only after some delay:
// The sync will not upload files that were changed too recently.
scheduleThisFolderSoon();
emit scheduleToSync(this);
}
void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
@@ -733,6 +758,8 @@ void Folder::startSync(const QStringList &pathList)
QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
// disable events until syncing is done
// _watcher->setEventsEnabled(false);
emit syncStarted();
}
@@ -796,6 +823,9 @@ void Folder::slotSyncFinished(bool success)
bubbleUpSyncResult();
bool anotherSyncNeeded = _engine->isAnotherSyncNeeded();
// _watcher->setEventsEnabledDelayed(2000);
if (_csyncError) {
_syncResult.setStatus(SyncResult::Error);
@@ -854,10 +884,11 @@ void Folder::slotSyncFinished(bool success)
// Maybe force a follow-up sync to take place, but only a couple of times.
if (anotherSyncNeeded && _consecutiveFollowUpSyncs <= 3)
{
// Sometimes another sync is requested because a local file is still
// changing, so wait at least a small amount of time before syncing
// the folder again.
scheduleThisFolderSoon();
_forceSyncOnPollTimeout = true;
// We will make sure that the poll timer occurs soon enough.
// delay 1s, 4s, 9s
int c = _consecutiveFollowUpSyncs;
QTimer::singleShot(c*c * 1000, this, SLOT(slotRunEtagJob() ));
}
}
@@ -938,17 +969,7 @@ void Folder::slotLogPropagationStart()
_fileLog->logLap("Propagation starts");
}
void Folder::slotScheduleThisFolder()
{
FolderMan::instance()->scheduleFolder(this);
}
void Folder::scheduleThisFolderSoon()
{
if (!_scheduleSelfTimer.isActive()) {
_scheduleSelfTimer.start();
}
}
void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
{
@@ -972,8 +993,10 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
*cancel = msgBox.clickedButton() == keepBtn;
if (*cancel) {
wipe();
// speed up next sync
_lastEtag.clear();
slotScheduleThisFolder();
_forceSyncOnPollTimeout = true;
QTimer::singleShot(50, this, SLOT(slotRunEtagJob()));
}
}
+2 -19
Ver Arquivo
@@ -178,7 +178,6 @@ public:
qint64 msecSinceLastSync() const { return _timeSinceLastSyncDone.elapsed(); }
qint64 msecLastSyncDuration() const { return _lastSyncDuration; }
int consecutiveFollowUpSyncs() const { return _consecutiveFollowUpSyncs; }
int consecutiveFailingSyncs() const { return _consecutiveFailingSyncs; }
/// Saves the folder data in the account's settings.
void saveToSettings() const;
@@ -195,21 +194,11 @@ public:
*/
bool isFileExcludedRelative(const QString& relativePath) const;
/** Calls schedules this folder on the FolderMan after a short delay.
*
* This should be used in situations where a sync should be triggered
* because a local file was modified. Syncs don't upload files that were
* modified too recently, and this delay ensures the modification is
* far enough in the past.
*
* The delay doesn't reset with subsequent calls.
*/
void scheduleThisFolderSoon();
signals:
void syncStateChange();
void syncStarted();
void syncFinished(const SyncResult &result);
void scheduleToSync(Folder*);
void progressInfo(const ProgressInfo& progress);
void newBigFolderDiscovered(const QString &); // A new folder bigger than the threshold was discovered
void syncPausedChanged(Folder*, bool paused);
@@ -277,11 +266,6 @@ private slots:
void slotLogPropagationStart();
/** Adds this folder to the list of scheduled folders in the
* FolderMan.
*/
void slotScheduleThisFolder();
private:
bool setIgnoredFiles();
@@ -318,6 +302,7 @@ private:
QElapsedTimer _timeSinceLastSyncDone;
QElapsedTimer _timeSinceLastSyncStart;
qint64 _lastSyncDuration;
bool _forceSyncOnPollTimeout;
/// The number of syncs that failed in a row.
/// Reset when a sync is successful.
@@ -332,8 +317,6 @@ private:
ClientProxy _clientProxy;
QScopedPointer<SyncRunFileLog> _fileLog;
QTimer _scheduleSelfTimer;
};
}
+45 -79
Ver Arquivo
@@ -64,17 +64,11 @@ FolderMan::FolderMan(QObject *parent) :
connect(&_startScheduledSyncTimer, SIGNAL(timeout()),
SLOT(slotStartScheduledFolderSync()));
_timeScheduler.setInterval(5000);
_timeScheduler.setSingleShot(false);
connect(&_timeScheduler, SIGNAL(timeout()),
SLOT(slotScheduleFolderByTime()));
_timeScheduler.start();
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState*)),
SLOT(slotRemoveFoldersForAccount(AccountState*)));
connect(_lockWatcher.data(), SIGNAL(fileUnlocked(QString)),
SLOT(slotWatchedFileUnlocked(QString)));
SLOT(slotScheduleFolderOwningFile(QString)));
}
FolderMan *FolderMan::instance()
@@ -106,6 +100,8 @@ void FolderMan::unloadFolder( Folder *f )
}
_folderMap.remove( f->alias() );
disconnect(f, SIGNAL(scheduleToSync(Folder*)),
this, SLOT(slotScheduleSync(Folder*)));
disconnect(f, SIGNAL(syncStarted()),
this, SLOT(slotFolderSyncStarted()));
disconnect(f, SIGNAL(syncFinished(SyncResult)),
@@ -135,7 +131,7 @@ int FolderMan::unloadAndDeleteAllFolders()
}
_lastSyncFolder = 0;
_currentSyncFolder = 0;
_scheduledFolders.clear();
_scheduleQueue.clear();
emit scheduleQueueChanged();
Q_ASSERT(_folderMap.count() == 0);
@@ -191,7 +187,7 @@ int FolderMan::setupFolders()
{
unloadAndDeleteAllFolders();
auto settings = Utility::settingsWithGroup(QLatin1String("Accounts"));
auto settings = Account::settingsWithGroup(QLatin1String("Accounts"));
const auto accountsWithSettings = settings->childGroups();
if (accountsWithSettings.isEmpty()) {
int r = setupFoldersMigration();
@@ -215,7 +211,7 @@ int FolderMan::setupFolders()
if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) {
Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
if (f) {
scheduleFolder(f);
slotScheduleSync(f);
emit folderSyncStateChange(f);
}
}
@@ -248,7 +244,7 @@ int FolderMan::setupFoldersMigration()
foreach ( const QString& alias, list ) {
Folder *f = setupFolderFromOldConfigFile( alias, accountState );
if( f ) {
scheduleFolder(f);
slotScheduleSync(f);
emit folderSyncStateChange(f);
}
}
@@ -426,7 +422,7 @@ void FolderMan::slotFolderSyncPaused( Folder *f, bool paused )
if (!paused) {
_disabledFolders.remove(f);
scheduleFolder(f);
slotScheduleSync(f);
} else {
_disabledFolders.insert(f);
}
@@ -466,11 +462,11 @@ Folder *FolderMan::folder( const QString& alias )
return 0;
}
void FolderMan::scheduleAllFolders()
void FolderMan::slotScheduleAllFolders()
{
foreach( Folder *f, _folderMap.values() ) {
if (f && f->canSync()) {
scheduleFolder( f );
slotScheduleSync( f );
}
}
}
@@ -490,7 +486,7 @@ void FolderMan::slotSyncOnceFileUnlocks(const QString& path)
* if a folder wants to be synced, it calls this slot and is added
* to the queue. The slot to actually start a sync is called afterwards.
*/
void FolderMan::scheduleFolder( Folder *f )
void FolderMan::slotScheduleSync( Folder *f )
{
if( !f ) {
qWarning() << "slotScheduleSync called with null folder";
@@ -500,7 +496,7 @@ void FolderMan::scheduleFolder( Folder *f )
qDebug() << "Schedule folder " << alias << " to sync!";
if( ! _scheduledFolders.contains(f) ) {
if( ! _scheduleQueue.contains(f) ) {
if( !f->canSync() ) {
qDebug() << "Folder is not ready to sync, not scheduled!";
_socketApi->slotUpdateFolderView(f);
@@ -508,7 +504,7 @@ void FolderMan::scheduleFolder( Folder *f )
}
f->prepareToSync();
emit folderSyncStateChange(f);
_scheduledFolders.enqueue(f);
_scheduleQueue.enqueue(f);
emit scheduleQueueChanged();
} else {
qDebug() << " II> Sync for folder " << alias << " already scheduled, do not enqueue!";
@@ -572,7 +568,7 @@ void FolderMan::slotAccountStateChanged()
if (f
&& f->canSync()
&& f->accountState() == accountState) {
scheduleFolder(f);
slotScheduleSync(f);
}
}
} else {
@@ -584,7 +580,7 @@ void FolderMan::slotAccountStateChanged()
_currentSyncFolder->slotTerminateSync();
}
QMutableListIterator<Folder*> it(_scheduledFolders);
QMutableListIterator<Folder*> it(_scheduleQueue);
while (it.hasNext()) {
Folder* f = it.next();
if (f->accountState() == accountState) {
@@ -599,7 +595,7 @@ void FolderMan::slotAccountStateChanged()
// this is not the same as Pause and Resume of folders.
void FolderMan::setSyncEnabled( bool enabled )
{
if (!_syncEnabled && enabled && !_scheduledFolders.isEmpty()) {
if (!_syncEnabled && enabled && !_scheduleQueue.isEmpty()) {
// We have things in our queue that were waiting for the connection to come back on.
startScheduledSyncSoon();
}
@@ -608,19 +604,19 @@ void FolderMan::setSyncEnabled( bool enabled )
emit( folderSyncStateChange(0) );
}
void FolderMan::startScheduledSyncSoon()
void FolderMan::startScheduledSyncSoon(qint64 msMinimumDelay)
{
if (_startScheduledSyncTimer.isActive()) {
return;
}
if (_scheduledFolders.empty()) {
if (_scheduleQueue.empty()) {
return;
}
if (_currentSyncFolder) {
return;
}
qint64 msDelay = 100; // 100ms minimum delay
qint64 msDelay = msMinimumDelay;
qint64 msSinceLastSync = 0;
// Require a pause based on the duration of the last sync run.
@@ -635,6 +631,15 @@ void FolderMan::startScheduledSyncSoon()
msDelay = qMax(msDelay, pause);
}
// Punish consecutive follow-up syncs with longer delays.
if (Folder* nextFolder = _scheduleQueue.head()) {
int followUps = nextFolder->consecutiveFollowUpSyncs();
if (followUps >= 2) {
// This is okay due to the 1min maximum delay limit below.
msDelay *= qPow(followUps, 2);
}
}
// Delays beyond one minute seem too big, particularly since there
// could be things later in the queue that shouldn't be punished by a
// long delay!
@@ -643,7 +648,11 @@ void FolderMan::startScheduledSyncSoon()
// Time since the last sync run counts against the delay
msDelay = qMax(1ll, msDelay - msSinceLastSync);
qDebug() << "Starting the next scheduled sync in" << (msDelay/1000) << "seconds";
// A minimum of delay here is essential as the sync will not upload
// files that were changed too recently.
msDelay = qMax(SyncEngine::minimumFileAgeForUpload, msDelay);
qDebug() << "Scheduling a sync in" << (msDelay/1000) << "seconds";
_startScheduledSyncTimer.start(msDelay);
}
@@ -664,15 +673,15 @@ void FolderMan::slotStartScheduledFolderSync()
return;
}
qDebug() << "XX slotScheduleFolderSync: folderQueue size: " << _scheduledFolders.count();
if( _scheduledFolders.isEmpty() ) {
qDebug() << "XX slotScheduleFolderSync: folderQueue size: " << _scheduleQueue.count();
if( _scheduleQueue.isEmpty() ) {
return;
}
// Find the first folder in the queue that can be synced.
Folder* f = 0;
while( !_scheduledFolders.isEmpty() ) {
f = _scheduledFolders.dequeue();
while( !_scheduleQueue.isEmpty() ) {
f = _scheduleQueue.dequeue();
Q_ASSERT(f);
if( f->canSync() ) {
@@ -702,7 +711,7 @@ void FolderMan::slotEtagPollTimerTimeout()
if (_currentSyncFolder == f) {
continue;
}
if (_scheduledFolders.contains(f)) {
if (_scheduleQueue.contains(f)) {
continue;
}
if (_disabledFolders.contains(f)) {
@@ -731,7 +740,7 @@ void FolderMan::slotRemoveFoldersForAccount(AccountState* accountState)
}
foreach (const auto &f, foldersToRemove) {
removeFolder(f);
slotRemoveFolder(f);
}
}
@@ -757,54 +766,10 @@ void FolderMan::slotServerVersionChanged(Account *account)
}
}
void FolderMan::slotWatchedFileUnlocked(const QString& path)
void FolderMan::slotScheduleFolderOwningFile(const QString& path)
{
if (Folder* f = folderForPath(path)) {
f->scheduleThisFolderSoon();
}
}
void FolderMan::slotScheduleFolderByTime()
{
foreach (auto& f, _folderMap) {
// Never schedule if syncing is disabled or when we're currently
// querying the server for etags
if (!f->canSync() || f->etagJob()) {
continue;
}
auto msecsSinceSync = f->msecSinceLastSync();
// Possibly it's just time for a new sync run
bool forceSyncIntervalExpired =
quint64(msecsSinceSync) > ConfigFile().forceSyncInterval();
if (forceSyncIntervalExpired) {
qDebug() << "** scheduling folder" << f->alias()
<< "because it has been" << msecsSinceSync << "ms "
<< "since the last sync";
scheduleFolder(f);
continue;
}
// Retry a couple of times after failure
bool syncAgainAfterFail = f->consecutiveFailingSyncs() > 0 && f->consecutiveFailingSyncs() < 3;
qint64 syncAgainAfterFailDelay = 10 * 1000; // 10s for the first retry-after-fail
if (f->consecutiveFailingSyncs() > 1)
syncAgainAfterFailDelay = 60 * 1000; // 60s for each further attempt
if (syncAgainAfterFail
&& msecsSinceSync > syncAgainAfterFailDelay) {
qDebug() << "** scheduling folder" << f->alias()
<< "because the last"
<< f->consecutiveFailingSyncs() << "syncs failed, last status:"
<< f->syncResult().statusString()
<< "time since last sync:" << msecsSinceSync;
scheduleFolder(f);
continue;
}
// Do we want to retry failing syncs or another-sync-needed runs more often?
slotScheduleSync(f);
}
}
@@ -862,6 +827,7 @@ Folder* FolderMan::addFolderInternal(FolderDefinition folderDefinition, AccountS
}
// See matching disconnects in unloadFolder().
connect(folder, SIGNAL(scheduleToSync(Folder*)), SLOT(slotScheduleSync(Folder*)));
connect(folder, SIGNAL(syncStarted()), SLOT(slotFolderSyncStarted()));
connect(folder, SIGNAL(syncFinished(SyncResult)), SLOT(slotFolderSyncFinished(SyncResult)));
connect(folder, SIGNAL(syncStateChange()), SLOT(slotForwardFolderSyncStateChange()));
@@ -914,7 +880,7 @@ QStringList FolderMan::findFileInLocalFolders( const QString& relPath, const Acc
return re;
}
void FolderMan::removeFolder( Folder *f )
void FolderMan::slotRemoveFolder( Folder *f )
{
if( !f ) {
qWarning() << "!! Can not remove null folder";
@@ -929,7 +895,7 @@ void FolderMan::removeFolder( Folder *f )
terminateSyncProcess();
}
if (_scheduledFolders.removeAll(f) > 0) {
if (_scheduleQueue.removeAll(f) > 0) {
emit scheduleQueueChanged();
}
@@ -1296,7 +1262,7 @@ void FolderMan::setIgnoreHiddenFiles(bool ignore)
QQueue<Folder*> FolderMan::scheduleQueue() const
{
return _scheduledFolders;
return _scheduleQueue;
}
Folder *FolderMan::currentSyncFolder() const
+43 -94
Ver Arquivo
@@ -37,26 +37,6 @@ class LockWatcher;
/**
* @brief The FolderMan class
* @ingroup gui
*
* The FolderMan knows about all loaded folders and is responsible for
* scheduling them when necessary.
*
* A folder is scheduled if:
* - The configured force-sync-interval has expired
* (_timeScheduler and slotScheduleFolderByTime())
*
* - A folder watcher receives a notification about a file change
* (_folderWatchers and Folder::slotWatchedPathChanged())
*
* - The folder etag on the server has changed
* (_etagPollTimer)
*
* - The locks of a monitored file are released
* (_lockWatcher and slotWatchedFileUnlocked())
*
* - There was a sync error or a follow-up sync is requested
* (_timeScheduler and slotScheduleFolderByTime()
* and Folder::slotSyncFinished())
*/
class FolderMan : public QObject
{
@@ -74,9 +54,6 @@ public:
*/
Folder* addFolder(AccountState* accountState, const FolderDefinition& folderDefinition);
/** Removes a folder */
void removeFolder( Folder* );
/** Returns the folder which the file or directory stored in path is in */
Folder* folderForPath(const QString& path);
@@ -149,24 +126,6 @@ public:
*/
Folder* currentSyncFolder() const;
/** Removes all folders */
int unloadAndDeleteAllFolders();
/**
* If enabled is set to false, no new folders will start to sync.
* The current one will finish.
*/
void setSyncEnabled( bool );
/** Queues a folder for syncing. */
void scheduleFolder(Folder*);
/** Queues all folders for syncing. */
void scheduleAllFolders();
void setDirtyProxy(bool value = true);
void setDirtyNetworkLimits();
signals:
/**
* signal to indicate a folder has changed its sync state.
@@ -180,12 +139,41 @@ signals:
*/
void scheduleQueueChanged();
/**
* Emitted whenever the list of configured folders changes.
*/
void folderListChanged(const Folder::Map &);
public slots:
void slotRemoveFolder( Folder* );
void slotFolderSyncPaused(Folder *, bool paused);
void slotFolderCanSyncChanged();
void slotFolderSyncStarted();
void slotFolderSyncFinished( const SyncResult& );
/**
* Terminates the current folder sync.
*
* It does not switch the folder to paused state.
*/
void terminateSyncProcess();
/* delete all folder objects */
int unloadAndDeleteAllFolders();
// if enabled is set to false, no new folders will start to sync.
// the current one will finish.
void setSyncEnabled( bool );
void slotScheduleAllFolders();
void setDirtyProxy(bool value = true);
void setDirtyNetworkLimits();
// slot to add a folder to the syncing queue
void slotScheduleSync(Folder*);
// slot to schedule an ETag job
void slotScheduleETagJob ( const QString &alias, RequestEtagJob *job);
void slotEtagJobDestroyed (QObject*);
void slotRunOneEtagJob();
/**
* Schedules folders of newly connected accounts, terminates and
@@ -202,22 +190,10 @@ public slots:
* Triggers a sync run once the lock on the given file is removed.
*
* Automatically detemines the folder that's responsible for the file.
* See slotWatchedFileUnlocked().
*/
void slotSyncOnceFileUnlocks(const QString& path);
// slot to schedule an ETag job (from Folder only)
void slotScheduleETagJob ( const QString &alias, RequestEtagJob *job);
private slots:
void slotFolderSyncPaused(Folder *, bool paused);
void slotFolderCanSyncChanged();
void slotFolderSyncStarted();
void slotFolderSyncFinished( const SyncResult& );
void slotRunOneEtagJob();
void slotEtagJobDestroyed (QObject*);
// slot to take the next folder from queue and start syncing.
void slotStartScheduledFolderSync();
void slotEtagPollTimerTimeout();
@@ -231,29 +207,12 @@ private slots:
void slotServerVersionChanged(Account* account);
/**
* A file whose locks were being monitored has become unlocked.
*
* This schedules the folder for synchronization that contains
* Schedules the folder for synchronization that contains
* the file with the given path.
*/
void slotWatchedFileUnlocked(const QString& path);
/**
* Schedules folders whose time to sync has come.
*
* Either because a long time has passed since the last sync or
* because of previous failures.
*/
void slotScheduleFolderByTime();
void slotScheduleFolderOwningFile(const QString& path);
private:
/**
* Terminates the current folder sync.
*
* It does not switch the folder to paused state.
*/
void terminateSyncProcess();
/** Adds a new folder, does not add it to the account settings and
* does not set an account on the new folder.
*/
@@ -263,7 +222,7 @@ private:
void unloadFolder( Folder * );
/** Will start a sync after a bit of delay. */
void startScheduledSyncSoon();
void startScheduledSyncSoon(qint64 msMinimumDelay = 0);
// finds all folder configuration files
// and create the folders
@@ -279,29 +238,19 @@ private:
Folder *_currentSyncFolder;
QPointer<Folder> _lastSyncFolder;
bool _syncEnabled;
QTimer _etagPollTimer;
QPointer<RequestEtagJob> _currentEtagJob; // alias of Folder running the current RequestEtagJob
/// Watching for file changes in folders
QMap<QString, FolderWatcher*> _folderWatchers;
/// Starts regular etag query jobs
QTimer _etagPollTimer;
/// The currently running etag query
QPointer<RequestEtagJob> _currentEtagJob;
/// Watches files that couldn't be synced due to locks
QScopedPointer<LockWatcher> _lockWatcher;
/// Occasionally schedules folders
QTimer _timeScheduler;
/// Scheduled folders that should be synced as soon as possible
QQueue<Folder*> _scheduledFolders;
/// Picks the next scheduled folder and starts the sync
QTimer _startScheduledSyncTimer;
QScopedPointer<SocketApi> _socketApi;
/** The aliases of folders that shall be synced. */
QQueue<Folder*> _scheduleQueue;
/** When the timer expires one of the scheduled syncs will be started. */
QTimer _startScheduledSyncTimer;
bool _appRestartRequired;
static FolderMan *_instance;
-4
Ver Arquivo
@@ -295,11 +295,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
pBRect.setHeight(barHeight);
pBRect.setWidth( overallWidth - 2 * margin );
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
QStyleOptionProgressBarV2 pBarOpt;
#else
QStyleOptionProgressBar pBarOpt;
#endif
pBarOpt.state = option.state | QStyle::State_Horizontal;
pBarOpt.minimum = 0;
+13 -29
Ver Arquivo
@@ -38,12 +38,6 @@ FolderStatusModel::FolderStatusModel(QObject *parent)
FolderStatusModel::~FolderStatusModel()
{ }
static bool sortByFolderHeader(const FolderStatusModel::SubFolderInfo& lhs, const FolderStatusModel::SubFolderInfo& rhs)
{
return QString::compare(lhs._folder->shortGuiRemotePathOrAppName(),
rhs._folder->shortGuiRemotePathOrAppName(),
Qt::CaseInsensitive) < 0;
}
void FolderStatusModel::setAccountState(const AccountState* accountState)
{
@@ -64,6 +58,7 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
if (f->accountState() != accountState)
continue;
SubFolderInfo info;
info._pathIdx << _folders.size();
info._name = f->alias();
info._path = "/";
info._folder = f;
@@ -74,14 +69,6 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
connect(f, SIGNAL(newBigFolderDiscovered(QString)), this, SLOT(slotNewBigFolder()), Qt::UniqueConnection);
}
// Sort by header text
qSort(_folders.begin(), _folders.end(), sortByFolderHeader);
// Set the root _pathIdx after the sorting
for (int i = 0; i < _folders.size(); ++i) {
_folders[i]._pathIdx << i;
}
endResetModel();
emit dirtyChanged();
}
@@ -388,13 +375,12 @@ QModelIndex FolderStatusModel::indexForPath(Folder *f, const QString& path) cons
if (slashPos == -1) {
// first level folder
for (int i = 0; i < _folders.size(); ++i) {
auto& info = _folders.at(i);
if (info._folder == f) {
if (_folders.at(i)._folder == f) {
if( path.isEmpty() ) { // the folder object
return index(i, 0);
}
for (int j = 0; j < info._subs.size(); ++j) {
const QString subName = info._subs.at(j)._name;
for (int j = 0; j < _folders.at(i)._subs.size(); ++j) {
const QString subName = _folders.at(i)._subs.at(j)._name;
if (subName == path) {
return index(j, 0, index(i));
}
@@ -799,7 +785,7 @@ void FolderStatusModel::slotApplySelectiveSync()
foreach(const auto &it, changes) {
folder->journalDb()->avoidReadFromDbOnNextSync(it);
}
FolderMan::instance()->scheduleFolder(folder);
FolderMan::instance()->slotScheduleSync(folder);
}
}
@@ -1000,12 +986,10 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
}
if (folderIndex < 0) { return; }
auto& pi = _folders[folderIndex]._progress;
SyncResult::Status state = f->syncResult().status();
if (f->syncPaused()) {
// Reset progress info.
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
} else if (state == SyncResult::NotYetStarted) {
FolderMan* folderMan = FolderMan::instance();
int pos = folderMan->scheduleQueue().indexOf(f);
@@ -1019,16 +1003,16 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
} else {
message = tr("Waiting for %n other folder(s)...", "", pos);
}
pi = SubFolderInfo::Progress();
pi._overallSyncString = message;
_folders[folderIndex]._progress = SubFolderInfo::Progress();
_folders[folderIndex]._progress._overallSyncString = message;
} else if (state == SyncResult::SyncPrepare) {
pi = SubFolderInfo::Progress();
pi._overallSyncString = tr("Preparing to sync...");
_folders[folderIndex]._progress = SubFolderInfo::Progress();
_folders[folderIndex]._progress._overallSyncString = tr("Preparing to sync...");
} else if (state == SyncResult::Problem || state == SyncResult::Success) {
// Reset the progress info after a sync.
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
} else if (state == SyncResult::Error) {
pi = SubFolderInfo::Progress();
_folders[folderIndex]._progress = SubFolderInfo::Progress();
}
// update the icon etc. now
@@ -1115,7 +1099,7 @@ void FolderStatusModel::slotSyncAllPendingBigFolders()
foreach (const auto &it, undecidedList) {
folder->journalDb()->avoidReadFromDbOnNextSync(it);
}
FolderMan::instance()->scheduleFolder(folder);
FolderMan::instance()->slotScheduleSync(folder);
}
resetFolders();
+1 -1
Ver Arquivo
@@ -126,7 +126,7 @@ void IgnoreListEditor::slotUpdateLocalIgnoreList()
// ignored (because the remote etag did not change) (issue #3172)
foreach (Folder* folder, folderMan->map()) {
folder->journalDb()->forceRemoteDiscoveryNextSync();
folderMan->scheduleFolder(folder);
folderMan->slotScheduleSync(folder);
}
ExcludedFiles::instance().reloadExcludes();
-3
Ver Arquivo
@@ -1,3 +0,0 @@
#include "winuser.h"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "owncloud.exe.manifest-mingw"
+3 -3
Ver Arquivo
@@ -66,7 +66,7 @@ void OcsJob::start()
req.setRawHeader("Ocs-APIREQUEST", "true");
req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
QUrl url = Utility::concatUrlPath(account()->url(), path());
QUrl url = Account::concatUrlPath(account()->url(), path());
QBuffer *buffer = new QBuffer;
if (_verb == "GET") {
@@ -107,7 +107,7 @@ bool OcsJob::finished()
if (!success) {
qDebug() << "Could not parse reply to"
<< _verb
<< Utility::concatUrlPath(account()->url(), path())
<< Account::concatUrlPath(account()->url(), path())
<< _params
<< ":" << replyData;
}
@@ -117,7 +117,7 @@ bool OcsJob::finished()
if (!_passStatusCodes.contains(statusCode)) {
qDebug() << "Reply to"
<< _verb
<< Utility::concatUrlPath(account()->url(), path())
<< Account::concatUrlPath(account()->url(), path())
<< _params
<< "has unexpected status code:" << statusCode << replyData;
emit ocsError(statusCode, message);
-10
Ver Arquivo
@@ -1,10 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
-3
Ver Arquivo
@@ -903,9 +903,6 @@ void ownCloudGui::setPauseOnAllFoldersHelper(bool pause)
foreach (Folder* f, FolderMan::instance()->map()) {
if (accounts.contains(f->accountState())) {
f->setSyncPaused(pause);
if (pause) {
f->slotTerminateSync();
}
}
}
}
+5 -27
Ver Arquivo
@@ -35,8 +35,6 @@
#include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h"
#include "creds/dummycredentials.h"
#include "creds/httpcredentialsgui.h"
#include "wizard/owncloudbrowsercredspage.h"
namespace OCC {
@@ -195,31 +193,11 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl& url, const QVariantM
qDebug() << Q_FUNC_INFO << " was redirected to" << redirectedUrl.toString();
}
/* Start by trying a browser authentication if the server supports it.
* If it does not support it, asyncAuthResult will fallback to the legacy wizard page */
_asyncAuth.reset(new AsyncAuth(_ocWizard->account().data(), this));
connect(_asyncAuth.data(), SIGNAL(result(AsyncAuth::Result,QString)),
this, SLOT(asyncAuthResult(AsyncAuth::Result,QString)));
_asyncAuth->start();
}
void OwncloudSetupWizard::asyncAuthResult(AsyncAuth::Result r,const QString &token)
{
if (r == AsyncAuth::NotSupported) {
_asyncAuth.reset(0);
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
job->start();
} else if (r == AsyncAuth::Waiting) {
_ocWizard->setAuthType(WizardCommon::Browser);
} else if (r == AsyncAuth::LoggedIn) {
_ocWizard->_browserCredsPage->_token = token;
slotConnectToOCUrl(_ocWizard->account()->url().toString());
}
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
job->start();
}
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
-3
Ver Arquivo
@@ -26,7 +26,6 @@
#include "networkjobs.h"
#include "wizard/owncloudwizardcommon.h"
#include "creds/httpcredentialsgui.h"
namespace OCC {
@@ -81,7 +80,6 @@ private slots:
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError);
void slotAssistantFinished( int );
void slotSkipFolderConfiguration();
void asyncAuthResult(AsyncAuth::Result, const QString &);
private:
explicit OwncloudSetupWizard(QObject *parent = 0 );
@@ -97,7 +95,6 @@ private:
OwncloudWizard* _ocWizard;
QString _initLocalFolder;
QString _remoteFolder;
QScopedPointer<AsyncAuth> _asyncAuth;
};
}
+1 -1
Ver Arquivo
@@ -460,7 +460,7 @@ void SelectiveSyncDialog::accept()
_folder->journalDb()->avoidReadFromDbOnNextSync(it);
}
folderMan->scheduleFolder(_folder);
folderMan->slotScheduleSync(_folder);
}
QDialog::accept();
}
+2 -2
Ver Arquivo
@@ -344,12 +344,12 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QVariantMap &data)
url = QUrl(data.value("url").toString());
} else if (_account->serverVersionInt() >= (8 << 16)) {
// From ownCloud server version 8 on, a different share link scheme is used.
url = QUrl(Utility::concatUrlPath(_account->url(), QString("index.php/s/%1").arg(data.value("token").toString())).toString());
url = QUrl(Account::concatUrlPath(_account->url(), QString("index.php/s/%1").arg(data.value("token").toString())).toString());
} else {
QList<QPair<QString, QString>> queryArgs;
queryArgs.append(qMakePair(QString("service"), QString("files")));
queryArgs.append(qMakePair(QString("t"), data.value("token").toString()));
url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
url = QUrl(Account::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
}
QDate expireDate;
-81
Ver Arquivo
@@ -1,81 +0,0 @@
/*
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <QVariant>
#include "wizard/owncloudbrowsercredspage.h"
#include "theme.h"
#include "account.h"
#include "cookiejar.h"
#include "wizard/owncloudwizardcommon.h"
#include "wizard/owncloudwizard.h"
#include "creds/httpcredentialsgui.h"
namespace OCC
{
OwncloudBrowserCredsPage::OwncloudBrowserCredsPage()
: AbstractCredentialsWizardPage(),
_afterInitialSetup(false)
{}
void OwncloudBrowserCredsPage::setVisible(bool visible)
{
if (!_afterInitialSetup) {
QWizardPage::setVisible(visible);
return;
}
if (isVisible() == visible) {
return;
}
if (visible) {
wizard()->hide();
} else {
wizard()->show();
}
}
void OwncloudBrowserCredsPage::initializePage()
{
_afterInitialSetup = true;
}
int OwncloudBrowserCredsPage::nextId() const
{
return WizardCommon::Page_AdvancedSetup;
}
void OwncloudBrowserCredsPage::setConnected()
{
wizard()->show();
}
AbstractCredentials* OwncloudBrowserCredsPage::getCredentials() const
{
OwncloudWizard* ocWizard = qobject_cast< OwncloudWizard* >(wizard());
Q_ASSERT(ocWizard);
// FIXME TODO !!!!!!!
QString user = QLatin1String("owncloud"); // FIXME! username;
return new HttpCredentialsGui(user, _token, ocWizard->ownCloudCertificatePath, ocWizard->ownCloudCertificatePasswd);
}
void OwncloudBrowserCredsPage::slotBrowserRejected()
{
wizard()->back();
wizard()->show();
}
} // namespace OCC
-55
Ver Arquivo
@@ -1,55 +0,0 @@
/*
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#pragma once
#include <QList>
#include <QMap>
#include <QNetworkCookie>
#include <QUrl>
#include <QPointer>
#include "wizard/abstractcredswizardpage.h"
#include "accountfwd.h"
namespace OCC {
class ShibbolethWebView;
/**
* @brief The OwncloudBrowserCredsPage class
* @ingroup gui
*/
class OwncloudBrowserCredsPage : public AbstractCredentialsWizardPage
{
Q_OBJECT
public:
OwncloudBrowserCredsPage();
AbstractCredentials* getCredentials() const Q_DECL_OVERRIDE;
void initializePage() Q_DECL_OVERRIDE;
int nextId() const Q_DECL_OVERRIDE;
void setConnected();
public Q_SLOTS:
void setVisible(bool visible) Q_DECL_OVERRIDE;
void slotBrowserRejected();
private:
bool _afterInitialSetup;
public:
QString _token;
};
} // namespace OCC
-2
Ver Arquivo
@@ -205,8 +205,6 @@ int OwncloudSetupPage::nextId() const
{
if (_authType == WizardCommon::HttpCreds) {
return WizardCommon::Page_HttpCreds;
} else if (_authType == WizardCommon::Browser) {
return WizardCommon::Page_BrowserCreds;
} else {
return WizardCommon::Page_ShibbolethCreds;
}
+1 -10
Ver Arquivo
@@ -20,7 +20,6 @@
#include "wizard/owncloudwizard.h"
#include "wizard/owncloudsetuppage.h"
#include "wizard/owncloudhttpcredspage.h"
#include "wizard/owncloudbrowsercredspage.h"
#ifndef NO_SHIBBOLETH
#include "wizard/owncloudshibbolethcredspage.h"
#endif
@@ -42,7 +41,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
_account(0),
_setupPage(new OwncloudSetupPage(this)),
_httpCredsPage(new OwncloudHttpCredsPage(this)),
_browserCredsPage(new OwncloudBrowserCredsPage),
#ifndef NO_SHIBBOLETH
_shibbolethCredsPage(new OwncloudShibbolethCredsPage),
#endif
@@ -54,7 +52,6 @@ OwncloudWizard::OwncloudWizard(QWidget *parent)
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setPage(WizardCommon::Page_ServerSetup, _setupPage);
setPage(WizardCommon::Page_HttpCreds, _httpCredsPage);
setPage(WizardCommon::Page_BrowserCreds, _browserCredsPage);
#ifndef NO_SHIBBOLETH
setPage(WizardCommon::Page_ShibbolethCreds, _shibbolethCredsPage);
#endif
@@ -139,10 +136,6 @@ void OwncloudWizard::successfulStep()
_httpCredsPage->setConnected();
break;
case WizardCommon::Page_BrowserCreds:
_browserCredsPage->setConnected();
break;
#ifndef NO_SHIBBOLETH
case WizardCommon::Page_ShibbolethCreds:
_shibbolethCredsPage->setConnected();
@@ -170,9 +163,7 @@ void OwncloudWizard::setAuthType(WizardCommon::AuthType type)
_credentialsPage = _shibbolethCredsPage;
} else
#endif
if (type == WizardCommon::Browser) {
_credentialsPage = _browserCredsPage;
} else {
{
_credentialsPage = _httpCredsPage;
}
next();
-4
Ver Arquivo
@@ -25,7 +25,6 @@ namespace OCC {
class OwncloudSetupPage;
class OwncloudHttpCredsPage;
class OwncloudBrowserCredsPage;
#ifndef NO_SHIBBOLETH
class OwncloudShibbolethCredsPage;
#endif
@@ -91,7 +90,6 @@ private:
AccountPtr _account;
OwncloudSetupPage* _setupPage;
OwncloudHttpCredsPage* _httpCredsPage;
OwncloudBrowserCredsPage* _browserCredsPage;
#ifndef NO_SHIBBOLETH
OwncloudShibbolethCredsPage* _shibbolethCredsPage;
#endif
@@ -100,8 +98,6 @@ private:
AbstractCredentialsWizardPage* _credentialsPage;
QStringList _setupLog;
friend class OwncloudSetupWizard;
};
} // namespace OCC
+1 -3
Ver Arquivo
@@ -32,8 +32,7 @@ void initErrorLabel(QLabel* errorLabel);
enum AuthType {
HttpCreds,
Shibboleth,
Browser
Shibboleth
};
enum SyncMode {
@@ -45,7 +44,6 @@ enum Pages {
Page_ServerSetup,
Page_HttpCreds,
Page_ShibbolethCreds,
Page_BrowserCreds,
Page_AdvancedSetup,
Page_Result
};
-2
Ver Arquivo
@@ -50,8 +50,6 @@ set(libsync_SRCS
propagatorjobs.cpp
propagatedownload.cpp
propagateupload.cpp
propagateuploadv1.cpp
propagateuploadng.cpp
propagateremotedelete.cpp
propagateremotemove.cpp
propagateremotemkdir.cpp
-4
Ver Arquivo
@@ -63,10 +63,6 @@ QNetworkReply* AccessManager::createRequest(QNetworkAccessManager::Operation op,
}
newRequest.setRawHeader(QByteArray("User-Agent"), Utility::userAgentString());
// Some firewalls reject requests that have a "User-Agent" but no "Accept" header
newRequest.setRawHeader(QByteArray("Accept"), "*/*");
QByteArray verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
// For PROPFIND (assumed to be a WebDAV op), set xml/utf8 as content type/encoding
// This needs extension
+83 -14
Ver Arquivo
@@ -39,6 +39,7 @@ Account::Account(QObject *parent)
: QObject(parent)
, _capabilities(QVariantMap())
, _davPath( Theme::instance()->webDavPath() )
, _wasMigrated(false)
{
qRegisterMetaType<AccountPtr>("AccountPtr");
}
@@ -75,19 +76,11 @@ AccountPtr Account::sharedFromThis()
return _sharedThis.toStrongRef();
}
QString Account::user() const
{
return _user.isEmpty() ? _credentials->user() : _user;
}
void Account::setUser(const QString &user)
{
_user = user;
}
QString Account::displayName() const
{
QString dn = QString("%1@%2").arg(user(), _url.host());
auto user = _credentials->user();
QString dn = QString("%1@%2").arg(user, _url.host());
int port = url().port();
if (port > 0 && port != 80 && port != 443) {
dn.append(QLatin1Char(':'));
@@ -101,6 +94,30 @@ QString Account::id() const
return _id;
}
static bool isEqualExceptProtocol(const QUrl &url1, const QUrl &url2)
{
return (url1.host() != url2.host() ||
url1.port() != url2.port() ||
url1.path() != url2.path());
}
bool Account::changed(AccountPtr other, bool ignoreUrlProtocol) const
{
if (!other) {
return false;
}
bool changes = false;
if (ignoreUrlProtocol) {
changes = isEqualExceptProtocol(_url, other->_url);
} else {
changes = (_url == other->_url);
}
changes |= _credentials->changed(other->credentials());
return changes;
}
AbstractCredentials *Account::credentials() const
{
return _credentials.data();
@@ -140,7 +157,12 @@ void Account::setCredentials(AbstractCredentials *cred)
QUrl Account::davUrl() const
{
return Utility::concatUrlPath(url(), davPath());
return concatUrlPath(url(), davPath());
}
QList<QNetworkCookie> Account::lastAuthCookies() const
{
return _am->cookieJar()->cookiesForUrl(_url);
}
void Account::clearCookieJar()
@@ -187,7 +209,7 @@ QNetworkAccessManager *Account::networkAccessManager()
QNetworkReply *Account::headRequest(const QString &relPath)
{
return headRequest(Utility::concatUrlPath(url(), relPath));
return headRequest(concatUrlPath(url(), relPath));
}
QNetworkReply *Account::headRequest(const QUrl &url)
@@ -201,7 +223,7 @@ QNetworkReply *Account::headRequest(const QUrl &url)
QNetworkReply *Account::getRequest(const QString &relPath)
{
return getRequest(Utility::concatUrlPath(url(), relPath));
return getRequest(concatUrlPath(url(), relPath));
}
QNetworkReply *Account::getRequest(const QUrl &url)
@@ -224,7 +246,7 @@ QNetworkReply *Account::deleteRequest( const QUrl &url)
QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data)
{
return davRequest(verb, Utility::concatUrlPath(davUrl(), relPath), req, data);
return davRequest(verb, concatUrlPath(davUrl(), relPath), req, data);
}
QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
@@ -318,6 +340,43 @@ void Account::setUrl(const QUrl &url)
_url = url;
}
QUrl Account::concatUrlPath(const QUrl &url, const QString &concatPath,
const QList< QPair<QString, QString> > &queryItems)
{
QString path = url.path();
if (! concatPath.isEmpty()) {
// avoid '//'
if (path.endsWith('/') && concatPath.startsWith('/')) {
path.chop(1);
} // avoid missing '/'
else if (!path.endsWith('/') && !concatPath.startsWith('/')) {
path += QLatin1Char('/');
}
path += concatPath; // put the complete path together
}
QUrl tmpUrl = url;
tmpUrl.setPath(path);
if( queryItems.size() > 0 ) {
tmpUrl.setQueryItems(queryItems);
}
return tmpUrl;
}
QString Account::_configFileName;
std::unique_ptr<QSettings> Account::settingsWithGroup(const QString& group, QObject *parent)
{
if (_configFileName.isEmpty()) {
// cache file name
ConfigFile cfg;
_configFileName = cfg.configFile();
}
std::unique_ptr<QSettings> settings(new QSettings(_configFileName, QSettings::IniFormat, parent));
settings->beginGroup(group);
return settings;
}
QVariant Account::credentialSetting(const QString &key) const
{
if (_credentials) {
@@ -416,6 +475,16 @@ void Account::handleInvalidCredentials()
emit invalidCredentials();
}
bool Account::wasMigrated()
{
return _wasMigrated;
}
void Account::setMigrated(bool mig)
{
_wasMigrated = mig;
}
const Capabilities &Account::capabilities() const
{
return _capabilities;
+45 -41
Ver Arquivo
@@ -56,32 +56,10 @@ public:
/**
* @brief The Account class represents an account on an ownCloud Server
* @ingroup libsync
*
* The Account has a name and url. It also has information about credentials,
* SSL errors and certificates.
*/
class OWNCLOUDSYNC_EXPORT Account : public QObject {
Q_OBJECT
public:
static AccountPtr create();
~Account();
AccountPtr sharedFromThis();
/// The user that can be used in dav url
QString user() const;
void setUser(const QString &user);
/// The name of the account as shown in the toolbar
QString displayName() const;
/// The internal id of the account.
QString id() const;
/** Server url of the account */
void setUrl(const QUrl &url);
QUrl url() const { return _url; }
/**
* @brief The possibly themed dav path for the account. It has
* a trailing slash.
@@ -91,15 +69,44 @@ public:
void setDavPath(const QString&s) { _davPath = s; }
void setNonShib(bool nonShib);
/** Returns webdav entry URL, based on url() */
QUrl davUrl() const;
static AccountPtr create();
~Account();
void setSharedThis(AccountPtr sharedThis);
AccountPtr sharedFromThis();
/// The name of the account as shown in the toolbar
QString displayName() const;
/// The internal id of the account.
QString id() const;
/**
* @brief Checks the Account instance is different from @param other
*
* @returns true, if credentials or url have changed, false otherwise
*/
bool changed(AccountPtr other, bool ignoreUrlProtocol) const;
/** Holds the accounts credentials */
AbstractCredentials* credentials() const;
void setCredentials(AbstractCredentials *cred);
/** Server url of the account */
void setUrl(const QUrl &url);
QUrl url() const { return _url; }
/** Returns webdav entry URL, based on url() */
QUrl davUrl() const;
/** set and retrieve the migration flag: if an account of a branded
* client was migrated from a former ownCloud Account, this is true
*/
void setMigrated(bool mig);
bool wasMigrated();
QList<QNetworkCookie> lastAuthCookies() const;
// For creating various network requests
QNetworkReply* headRequest(const QString &relPath);
QNetworkReply* headRequest(const QUrl &url);
QNetworkReply* getRequest(const QString &relPath);
@@ -108,7 +115,6 @@ public:
QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0);
QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0);
/** The ssl configuration during the first connection */
QSslConfiguration getOrCreateSslConfig();
QSslConfiguration sslConfiguration() const { return _sslConfiguration; }
@@ -132,21 +138,25 @@ public:
// pluggable handler
void setSslErrorHandler(AbstractSslErrorHandler *handler);
// To be called by credentials only, for storing username and the like
// static helper function
static QUrl concatUrlPath(const QUrl &url, const QString &concatPath,
const QList< QPair<QString, QString> > &queryItems = (QList<QPair<QString, QString>>()));
/** Returns a new settings pre-set in a specific group. The Settings will be created
with the given parent. If no parent is specified, the caller must destroy the settings */
static std::unique_ptr<QSettings> settingsWithGroup(const QString& group, QObject* parent = 0);
// to be called by credentials only
QVariant credentialSetting(const QString& key) const;
void setCredentialSetting(const QString& key, const QVariant &value);
/** Assign a client certificate */
void setCertificate(const QByteArray certficate = QByteArray(), const QString privateKey = QString());
/** Access the server capabilities */
const Capabilities &capabilities() const;
void setCapabilities(const QVariantMap &caps);
/** Access the server version */
const Capabilities &capabilities() const;
void setServerVersion(const QString &version);
QString serverVersion() const;
int serverVersionInt() const;
void setServerVersion(const QString &version);
/** Whether the server is too old.
*
@@ -161,7 +171,6 @@ public:
bool serverVersionUnsupported() const;
// Fixed from 8.1 https://github.com/owncloud/client/issues/3730
/** Detects a specific bug in older server versions */
bool rootEtagChangesNotOnlySubFolderEtags();
void clearCookieJar();
@@ -170,16 +179,12 @@ public:
void resetNetworkAccessManager();
QNetworkAccessManager* networkAccessManager();
/// Called by network jobs on credential errors, emits invalidCredentials()
/// Called by network jobs on credential errors.
void handleInvalidCredentials();
signals:
/// Emitted whenever there's network activity
void propagatorNetworkActivity();
/// Triggered by handleInvalidCredentials()
void invalidCredentials();
void credentialsFetched(AbstractCredentials* credentials);
void credentialsAsked(AbstractCredentials* credentials);
@@ -198,11 +203,9 @@ protected Q_SLOTS:
private:
Account(QObject *parent = 0);
void setSharedThis(AccountPtr sharedThis);
QWeakPointer<Account> _sharedThis;
QString _id;
QString _user;
QMap<QString, QVariant> _settingsMap;
QUrl _url;
QList<QSslCertificate> _approvedCerts;
@@ -221,6 +224,7 @@ private:
QByteArray _pemCertificate;
QString _pemPrivateKey;
QString _davPath; // defaults to value from theme, might be overwritten in brandings
bool _wasMigrated;
friend class AccountManager;
};
-6
Ver Arquivo
@@ -107,10 +107,4 @@ QByteArray Capabilities::uploadChecksumType() const
return QByteArray();
}
bool Capabilities::chunkingNg() const
{
return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
}
}
-1
Ver Arquivo
@@ -39,7 +39,6 @@ public:
bool sharePublicLinkEnforceExpireDate() const;
int sharePublicLinkExpireDateDays() const;
bool shareResharing() const;
bool chunkingNg() const;
/// returns true if the capabilities report notifications
bool notificationsAvailable() const;
+1 -6
Ver Arquivo
@@ -611,12 +611,7 @@ void ConfigFile::setPromptDeleteFiles(bool promptDeleteFiles)
bool ConfigFile::monoIcons() const
{
QSettings settings(configFile(), QSettings::IniFormat);
bool monoDefault = false; // On Mac we want bw by default
#ifdef Q_OS_MAC
// OEM themes are not obliged to ship mono icons
monoDefault = (0 == (strcmp("ownCloud",APPLICATION_NAME)));
#endif
return settings.value(QLatin1String(monoIconsC), monoDefault).toBool();
return settings.value(QLatin1String(monoIconsC), false).toBool();
}
void ConfigFile::setMonoIcons(bool useMonoIcons)
+2 -18
Ver Arquivo
@@ -229,27 +229,11 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QVariantMap &json)
auto caps = json.value("ocs").toMap().value("data").toMap().value("capabilities");
qDebug() << "Server capabilities" << caps;
_account->setCapabilities(caps.toMap());
fetchUser();
}
void ConnectionValidator::fetchUser()
{
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/user"), this);
job->setTimeout(timeoutToUseMsec);
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), this, SLOT(slotUserFetched(QVariantMap)));
job->start();
}
void ConnectionValidator::slotUserFetched(const QVariantMap &json)
{
QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString();
if (!user.isEmpty()) {
_account->setUser(user);
}
reportResult(Connected);
return;
}
void ConnectionValidator::reportResult(Status status)
{
emit connectionResult(status, _errors);
+2 -12
Ver Arquivo
@@ -27,7 +27,7 @@ namespace OCC {
* This is a job-like class to check that the server is up and that we are connected.
* There are two entry points: checkServerAndAuth and checkAuthentication
* checkAuthentication is the quick version that only does the propfind
* while checkServerAndAuth is doing the 4 calls.
* while checkServerAndAuth is doing the 3 calls.
*
* We cannot use the capabilites call to test the login and the password because of
* https://github.com/owncloud/core/issues/12930
@@ -60,15 +60,7 @@ namespace OCC {
+-> checkServerCapabilities (cloud/capabilities)
JsonApiJob
|
+-> slotCapabilitiesRecieved -+
|
+-----------------------------------+
|
+-> fetchUser
PropfindJob
|
+-> slotUserFetched --> X
+-> slotCapabilitiesRecieved --> X
\endcode
*/
class OWNCLOUDSYNC_EXPORT ConnectionValidator : public QObject
@@ -117,12 +109,10 @@ protected slots:
void slotAuthSuccess();
void slotCapabilitiesRecieved(const QVariantMap&);
void slotUserFetched(const QVariantMap &);
private:
void reportResult(Status status);
void checkServerCapabilities();
void fetchUser();
QStringList _errors;
AccountPtr _account;
+1
Ver Arquivo
@@ -41,6 +41,7 @@ public:
*/
virtual void setAccount(Account* account);
virtual bool changed(AbstractCredentials* credentials) const = 0;
virtual QString authType() const = 0;
virtual QString user() const = 0;
virtual QNetworkAccessManager* getQNAM() const = 0;
+7
Ver Arquivo
@@ -17,6 +17,13 @@
namespace OCC
{
bool DummyCredentials::changed(AbstractCredentials* credentials) const
{
DummyCredentials* dummy(dynamic_cast< DummyCredentials* >(credentials));
return dummy == 0;
}
QString DummyCredentials::authType() const
{
return QString::fromLatin1("dummy");
+1
Ver Arquivo
@@ -27,6 +27,7 @@ public:
QString _user;
QString _password;
bool changed(AbstractCredentials* credentials) const Q_DECL_OVERRIDE;
QString authType() const Q_DECL_OVERRIDE;
QString user() const Q_DECL_OVERRIDE;
QNetworkAccessManager* getQNAM() const Q_DECL_OVERRIDE;
+18 -3
Ver Arquivo
@@ -71,6 +71,21 @@ HttpCredentials::HttpCredentials(const QString& user, const QString& password, c
{
}
bool HttpCredentials::changed(AbstractCredentials* credentials) const
{
HttpCredentials* other(qobject_cast< HttpCredentials* >(credentials));
if (!other) {
return true;
}
if (!other || (other->user() != this->user())) {
return true;
}
return false;
}
QString HttpCredentials::authType() const
{
return QString::fromLatin1("http");
@@ -132,7 +147,7 @@ void HttpCredentials::fetchFromKeychain()
_certificatePath = _account->credentialSetting(QLatin1String(certifPathC)).toString();
_certificatePasswd = _account->credentialSetting(QLatin1String(certifPasswdC)).toString();
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
const QString kck = keychainKey(_account->url().toString(), _user );
QString key = QString::fromLatin1( "%1/data" ).arg( kck );
@@ -214,7 +229,7 @@ void HttpCredentials::invalidateToken()
}
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
job->setInsecureFallback(true);
@@ -265,7 +280,7 @@ void HttpCredentials::persist()
_account->setCredentialSetting(QLatin1String(certifPathC), _certificatePath);
_account->setCredentialSetting(QLatin1String(certifPasswdC), _certificatePasswd);
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
settings->setParent(job); // make the job parent to make setting deleted properly
job->setSettings(settings.release());
+1 -2
Ver Arquivo
@@ -17,7 +17,6 @@
#define MIRALL_CREDS_HTTP_CREDENTIALS_H
#include <QMap>
#include <QUrl>
#include "creds/abstractcredentials.h"
@@ -39,6 +38,7 @@ public:
explicit HttpCredentials();
HttpCredentials(const QString& user, const QString& password, const QString& certificatePath, const QString& certificatePasswd);
bool changed(AbstractCredentials* credentials) const Q_DECL_OVERRIDE;
QString authType() const Q_DECL_OVERRIDE;
QNetworkAccessManager* getQNAM() const Q_DECL_OVERRIDE;
bool ready() const Q_DECL_OVERRIDE;
@@ -75,7 +75,6 @@ private:
QString _certificatePasswd;
};
} // namespace OCC
#endif
+11
Ver Arquivo
@@ -80,6 +80,17 @@ TokenCredentials::TokenCredentials(const QString& user, const QString& password,
{
}
bool TokenCredentials::changed(AbstractCredentials* credentials) const
{
TokenCredentials* other(dynamic_cast< TokenCredentials* >(credentials));
if (!other || (other->user() != this->user())) {
return true;
}
return false;
}
QString TokenCredentials::authType() const
{
return QString::fromLatin1("token");
+1
Ver Arquivo
@@ -40,6 +40,7 @@ public:
TokenCredentials();
TokenCredentials(const QString& user, const QString& password, const QString &token);
bool changed(AbstractCredentials* credentials) const Q_DECL_OVERRIDE;
QString authType() const Q_DECL_OVERRIDE;
QNetworkAccessManager* getQNAM() const Q_DECL_OVERRIDE;
bool ready() const Q_DECL_OVERRIDE;
+2 -20
Ver Arquivo
@@ -20,7 +20,6 @@
#include <QUrl>
#include "account.h"
#include <QFileInfo>
#include <cstring>
namespace OCC {
@@ -237,8 +236,7 @@ void DiscoverySingleDirectoryJob::start()
QList<QByteArray> props;
props << "resourcetype" << "getlastmodified" << "getcontentlength" << "getetag"
<< "http://owncloud.org/ns:id" << "http://owncloud.org/ns:downloadURL"
<< "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions"
<< "http://owncloud.org/ns:share-types";
<< "http://owncloud.org/ns:dDC" << "http://owncloud.org/ns:permissions";
if (_isRootPath)
props << "http://owncloud.org/ns:data-fingerprint";
@@ -310,25 +308,9 @@ static csync_vio_file_stat_t* propertyMapToFileStat(const QMap<QString,QString>
} else {
qWarning() << "permissions too large" << v;
}
} else if (property == "share-types" && !value.isEmpty()) {
// Since QMap is sorted, "share-types" is always "permissions".
if (file_stat->remotePerm[0] == '\0' || !(file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_PERM)) {
qWarning() << "Server returned a share type, but no permissions?";
} else {
// S means shared with me.
// But for our purpose, we want to know if the file is shared. It does not matter
// if we are the owner or not.
// Piggy back on the persmission field 'S'
if (!std::strchr(file_stat->remotePerm, 'S')) {
if (std::strlen(file_stat->remotePerm) < sizeof(file_stat->remotePerm)-1) {
std::strcat(file_stat->remotePerm, "S");
} else {
qWarning() << "permissions too large" << file_stat->remotePerm;
}
}
}
}
}
return file_stat;
}
+3 -19
Ver Arquivo
@@ -106,24 +106,14 @@ MkColJob::MkColJob(AccountPtr account, const QString &path, QObject *parent)
{
}
MkColJob::MkColJob(AccountPtr account, const QUrl &url,
const QMap<QByteArray, QByteArray> &extraHeaders, QObject *parent)
: AbstractNetworkJob(account, QString(), parent), _url(url), _extraHeaders(extraHeaders)
{
}
void MkColJob::start()
{
// add 'Content-Length: 0' header (see https://github.com/owncloud/client/issues/3256)
QNetworkRequest req;
req.setRawHeader("Content-Length", "0");
for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) {
req.setRawHeader(it.key(), it.value());
}
// assumes ownership
QNetworkReply *reply = _url.isValid() ? davRequest("MKCOL", _url, req)
: davRequest("MKCOL", path(), req);
QNetworkReply *reply = davRequest("MKCOL", path(), req);
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
@@ -274,11 +264,6 @@ LsColJob::LsColJob(AccountPtr account, const QString &path, QObject *parent)
{
}
LsColJob::LsColJob(AccountPtr account, const QUrl &url, QObject *parent)
: AbstractNetworkJob(account, QString(), parent), _url(url)
{
}
void LsColJob::setProperties(QList<QByteArray> properties)
{
_properties = properties;
@@ -322,8 +307,7 @@ void LsColJob::start()
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = _url.isValid() ? davRequest("PROPFIND", _url, req, buf)
: davRequest("PROPFIND", path(), req, buf);
QNetworkReply *reply = davRequest("PROPFIND", path(), req, buf);
buf->setParent(reply);
setReply(reply);
setupConnections(reply);
@@ -696,7 +680,7 @@ void JsonApiJob::start()
{
QNetworkRequest req;
req.setRawHeader("OCS-APIREQUEST", "true");
QUrl url = Utility::concatUrlPath(account()->url(), path());
QUrl url = Account::concatUrlPath(account()->url(), path());
QList<QPair<QString, QString> > params = _additionalParams;
params << qMakePair(QString::fromLatin1("format"), QString::fromLatin1("json"));
url.setQueryItems(params);
-6
Ver Arquivo
@@ -62,7 +62,6 @@ class OWNCLOUDSYNC_EXPORT LsColJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit LsColJob(AccountPtr account, const QString &path, QObject *parent = 0);
explicit LsColJob(AccountPtr account, const QUrl &url, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
QHash<QString, qint64> _sizes;
@@ -88,7 +87,6 @@ private slots:
private:
QList<QByteArray> _properties;
QUrl _url; // Used instead of path() if the url is specified in the constructor
};
/**
@@ -172,12 +170,8 @@ private:
*/
class OWNCLOUDSYNC_EXPORT MkColJob : public AbstractNetworkJob {
Q_OBJECT
QUrl _url; // Only used if the constructor taking a url is taken.
QMap<QByteArray, QByteArray> _extraHeaders;
public:
explicit MkColJob(AccountPtr account, const QString &path, QObject *parent = 0);
explicit MkColJob(AccountPtr account, const QUrl &url,
const QMap<QByteArray, QByteArray> &extraHeaders, QObject *parent = 0);
void start() Q_DECL_OVERRIDE;
signals:
+4 -17
Ver Arquivo
@@ -270,14 +270,7 @@ PropagateItemJob* OwncloudPropagator::createJob(const SyncFileItemPtr &item) {
job->setDeleteExistingFolder(deleteExisting);
return job;
} else {
PropagateUploadFileCommon *job = 0;
static const auto chunkng = qgetenv("OWNCLOUD_CHUNKING_NG");
if (item->_size > chunkSize()
&& (account()->capabilities().chunkingNg() || chunkng == "1") && chunkng != "0") {
job = new PropagateUploadFileNG(this, item);
} else {
job = new PropagateUploadFileV1(this, item);
}
auto job = new PropagateUploadFile(this, item);
job->setDeleteExisting(deleteExisting);
return job;
}
@@ -536,7 +529,7 @@ void OwncloudPropagator::scheduleNextJob()
}
}
if (_activeJobList.count() < maximumActiveJob() + likelyFinishedQuicklyCount) {
qDebug() << "Can pump in another request! activeJobs =" << _activeJobList.count();
qDebug() << "Can pump in another request!";
if (_rootJob->scheduleNextJob()) {
QTimer::singleShot(0, this, SLOT(scheduleNextJob()));
}
@@ -613,15 +606,9 @@ bool PropagateDirectory::scheduleNextJob()
return false;
}
// cache the value of first unfinished subjob
bool stopAtDirectory = false;
int i = _firstUnfinishedSubJob;
int subJobsCount = _subJobs.count();
while (i < subJobsCount && _subJobs.at(i)->_state == Finished) {
_firstUnfinishedSubJob = ++i;
}
for (int i = _firstUnfinishedSubJob; i < subJobsCount; ++i) {
// FIXME: use the cached value of finished job
for (int i = 0; i < _subJobs.count(); ++i) {
if (_subJobs.at(i)->_state == Finished) {
continue;
}
+2 -4
Ver Arquivo
@@ -195,11 +195,10 @@ public:
int _jobsFinished; // number of jobs that have completed
int _runningNow; // number of subJobs running right now
SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error
int _firstUnfinishedSubJob;
explicit PropagateDirectory(OwncloudPropagator *propagator, const SyncFileItemPtr &item = SyncFileItemPtr(new SyncFileItem))
: PropagatorJob(propagator)
, _firstJob(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus), _firstUnfinishedSubJob(0)
, _firstJob(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus)
{ }
virtual ~PropagateDirectory() {
@@ -380,11 +379,10 @@ private:
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
// access to signals which are protected in Qt4
friend class PropagateDownloadFile;
friend class PropagateUploadFile;
friend class PropagateLocalMkdir;
friend class PropagateLocalRename;
friend class PropagateRemoteMove;
friend class PropagateUploadFileV1;
friend class PropagateUploadFileNG;
#endif
};
+3 -4
Ver Arquivo
@@ -44,10 +44,9 @@ QString ownCloudTheme::configFileName() const
QString ownCloudTheme::about() const
{
QString devString;
devString = trUtf8("<p>Version %2. For more information visit <a href=\"%3\">https://%4</a></p>"
"<p>For known issues and help, please visit: <a href=\"https://central.owncloud.org/c/help/desktop-file-sync\">https://central.owncloud.org</a></p>"
"<p><small>By Klaas Freitag, Daniel Molkentin, Olivier Goffart, Markus Götz, "
" Jan-Christoph Borchardt, and others.</small></p>"
devString = trUtf8("<p>Version %2. For more information visit <a href=\"%3\">%4</a></p>"
"<p><small>By Klaas Freitag, Daniel Molkentin, Jan-Christoph Borchardt, "
"Olivier Goffart, Markus Götz and others.</small></p>"
"<p>Copyright ownCloud GmbH</p>"
"<p>Licensed under the GNU General Public License (GPL) Version 2.0<br/>"
"ownCloud and the ownCloud Logo are registered trademarks of ownCloud GmbH "
-1
Ver Arquivo
@@ -147,7 +147,6 @@ void ProgressInfo::reset()
_maxBytesPerSecond = 100000.0;
_maxFilesPerSecond = 2.0;
_updateEstimatesTimer.stop();
_lastCompletedItem = SyncFileItem();
}
void ProgressInfo::startEstimateUpdates()
+2 -3
Ver Arquivo
@@ -593,7 +593,7 @@ void PropagateDownloadFile::deleteExistingFolder()
}
QString conflictDir = FileSystem::makeConflictFileName(
existingDir, Utility::qDateTimeFromTime_t(FileSystem::getModTime(existingDir)));
existingDir, Utility::qDateTimeFromTime_t(_item->_modtime));
emit _propagator->touchedFile(existingDir);
emit _propagator->touchedFile(conflictDir);
@@ -722,8 +722,7 @@ void PropagateDownloadFile::downloadFinished()
&& !FileSystem::fileEquals(fn, _tmpFile.fileName());
if (isConflict) {
QString renameError;
QString conflictFileName = FileSystem::makeConflictFileName(
fn, Utility::qDateTimeFromTime_t(FileSystem::getModTime(fn)));
QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime));
if (!FileSystem::rename(fn, conflictFileName, &renameError)) {
// If the rename fails, don't replace it.
+1 -4
Ver Arquivo
@@ -22,14 +22,11 @@ DeleteJob::DeleteJob(AccountPtr account, const QString& path, QObject* parent)
: AbstractNetworkJob(account, path, parent)
{ }
DeleteJob::DeleteJob(AccountPtr account, const QUrl& url, QObject* parent)
: AbstractNetworkJob(account, QString(), parent), _url(url)
{ }
void DeleteJob::start()
{
QNetworkRequest req;
setReply(_url.isValid() ? davRequest("DELETE", _url, req) : davRequest("DELETE", path(), req));
setReply(davRequest("DELETE", path(), req));
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
-2
Ver Arquivo
@@ -24,10 +24,8 @@ namespace OCC {
*/
class DeleteJob : public AbstractNetworkJob {
Q_OBJECT
QUrl _url; // Only used if the constructor taking a url is taken.
public:
explicit DeleteJob(AccountPtr account, const QString& path, QObject* parent = 0);
explicit DeleteJob(AccountPtr account, const QUrl& url, QObject* parent = 0);
void start() Q_DECL_OVERRIDE;
bool finished() Q_DECL_OVERRIDE;
+1 -9
Ver Arquivo
@@ -28,20 +28,12 @@ MoveJob::MoveJob(AccountPtr account, const QString& path,
: AbstractNetworkJob(account, path, parent), _destination(destination)
{ }
MoveJob::MoveJob(AccountPtr account, const QUrl& url, const QString &destination,
QMap<QByteArray, QByteArray> extraHeaders, QObject* parent)
: AbstractNetworkJob(account, QString(), parent), _destination(destination), _url(url)
, _extraHeaders(extraHeaders)
{ }
void MoveJob::start()
{
QNetworkRequest req;
req.setRawHeader("Destination", QUrl::toPercentEncoding(_destination, "/"));
for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) {
req.setRawHeader(it.key(), it.value());
}
setReply(_url.isValid() ? davRequest("MOVE", _url, req) : davRequest("MOVE", path(), req));
setReply(davRequest("MOVE", path(), req));
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
-4
Ver Arquivo
@@ -25,12 +25,8 @@ namespace OCC {
class MoveJob : public AbstractNetworkJob {
Q_OBJECT
const QString _destination;
const QUrl _url; // Only used (instead of path) when the constructor taking an URL is used
QMap<QByteArray, QByteArray> _extraHeaders;
public:
explicit MoveJob(AccountPtr account, const QString& path, const QString &destination, QObject* parent = 0);
explicit MoveJob(AccountPtr account, const QUrl& url, const QString &destination,
QMap<QByteArray, QByteArray> _extraHeaders, QObject* parent = 0);
void start() Q_DECL_OVERRIDE;
bool finished() Q_DECL_OVERRIDE;
+403 -71
Ver Arquivo
@@ -72,8 +72,7 @@ void PUTFileJob::start() {
req.setRawHeader(it.key(), it.value());
}
setReply(_url.isValid() ? davRequest("PUT", _url, req, _device.data())
: davRequest("PUT", path(), req, _device.data()));
setReply(davRequest("PUT", path(), req, _device.data()));
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
@@ -185,13 +184,7 @@ bool PollJob::finished()
return true;
}
void PropagateUploadFileCommon::setDeleteExisting(bool enabled)
{
_deleteExisting = enabled;
}
void PropagateUploadFileCommon::start()
void PropagateUploadFile::start()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) {
return;
@@ -212,7 +205,7 @@ void PropagateUploadFileCommon::start()
job->start();
}
void PropagateUploadFileCommon::slotComputeContentChecksum()
void PropagateUploadFile::slotComputeContentChecksum()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0)) {
return;
@@ -246,7 +239,12 @@ void PropagateUploadFileCommon::slotComputeContentChecksum()
computeChecksum->start(filePath);
}
void PropagateUploadFileCommon::slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum)
void PropagateUploadFile::setDeleteExisting(bool enabled)
{
_deleteExisting = enabled;
}
void PropagateUploadFile::slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum)
{
_item->_contentChecksum = contentChecksum;
_item->_contentChecksumType = contentChecksumType;
@@ -278,7 +276,7 @@ void PropagateUploadFileCommon::slotComputeTransmissionChecksum(const QByteArray
computeChecksum->start(filePath);
}
void PropagateUploadFileCommon::slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum)
void PropagateUploadFile::slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum)
{
// Remove ourselfs from the list of active job, before any posible call to done()
// When we start chunks, we will add it again, once for every chunks.
@@ -324,7 +322,23 @@ void PropagateUploadFileCommon::slotStartUpload(const QByteArray& transmissionCh
return;
}
doStartUpload();
_chunkCount = std::ceil(fileSize/double(chunkSize()));
_startChunk = 0;
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16);
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
_startChunk = progressInfo._chunk;
_transferId = progressInfo._transferid;
qDebug() << Q_FUNC_INFO << _item->_file << ": Resuming from chunk " << _startChunk;
}
_currentChunk = 0;
_duration.start();
emit progress(*_item, 0);
this->startNextChunk();
}
UploadDevice::UploadDevice(BandwidthManager *bwm)
@@ -462,64 +476,24 @@ void UploadDevice::setChoked(bool b) {
}
}
void PropagateUploadFileCommon::startPollJob(const QString& path)
void PropagateUploadFile::startNextChunk()
{
PollJob* job = new PollJob(_propagator->account(), path, _item,
_propagator->_journal, _propagator->_localDir, this);
connect(job, SIGNAL(finishedSignal()), SLOT(slotPollFinished()));
SyncJournalDb::PollInfo info;
info._file = _item->_file;
info._url = path;
info._modtime = _item->_modtime;
_propagator->_journal->setPollInfo(info);
_propagator->_journal->commit("add poll info");
_propagator->_activeJobList.append(this);
job->start();
}
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return;
void PropagateUploadFileCommon::slotPollFinished()
{
PollJob *job = qobject_cast<PollJob *>(sender());
Q_ASSERT(job);
_propagator->_activeJobList.removeOne(this);
if (job->_item->_status != SyncFileItem::Success) {
_finished = true;
done(job->_item->_status, job->_item->_errorString);
if (! _jobs.isEmpty() && _currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
// We return now and when the _jobs are finished we will proceed with the last chunk
// NOTE: Some other parts of the code such as slotUploadProgress also assume that the last chunk
// is sent last.
return;
}
finalize();
}
void PropagateUploadFileCommon::slotJobDestroyed(QObject* job)
{
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job) , _jobs.end());
}
void PropagateUploadFileCommon::abort()
{
foreach(auto *job, _jobs) {
if (job->reply()) {
qDebug() << Q_FUNC_INFO << job << this->_item->_file;
job->reply()->abort();
}
}
}
// 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();
done(status, error);
}
QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
{
quint64 fileSize = _item->_size;
QMap<QByteArray, QByteArray> headers;
headers["OC-Total-Length"] = QByteArray::number(fileSize);
headers["OC-Async"] = "1";
headers["OC-Chunk-Size"]= QByteArray::number(quint64(chunkSize()));
headers["Content-Type"] = "application/octet-stream";
headers["X-OC-Mtime"] = QByteArray::number(qint64(_item->_modtime));
@@ -535,20 +509,291 @@ QMap<QByteArray, QByteArray> PropagateUploadFileCommon::headers()
}
if (!_item->_etag.isEmpty() && _item->_etag != "empty_etag"
&& _item->_instruction != CSYNC_INSTRUCTION_NEW // On new files never send a If-Match
&& _item->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE
&& !_deleteExisting
) {
&& _item->_instruction != CSYNC_INSTRUCTION_NEW // On new files never send a If-Match
&& _item->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE
&& !_deleteExisting
) {
// We add quotes because the owncloud server always adds quotes around the etag, and
// csync_owncloud.c's owncloud_file_id always strips the quotes.
headers["If-Match"] = '"' + _item->_etag + '"';
}
return headers;
QString path = _item->_file;
UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager);
qint64 chunkStart = 0;
qint64 currentChunkSize = fileSize;
bool isFinalChunk = false;
if (_chunkCount > 1) {
int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
// XOR with chunk size to make sure everything goes well if chunk size changes between runs
uint transid = _transferId ^ chunkSize();
qDebug() << "Upload chunk" << sendingChunk << "of" << _chunkCount << "transferid(remote)=" << transid;
path += QString("-chunking-%1-%2-%3").arg(transid).arg(_chunkCount).arg(sendingChunk);
headers["OC-Chunked"] = "1";
chunkStart = chunkSize() * quint64(sendingChunk);
currentChunkSize = chunkSize();
if (sendingChunk == _chunkCount - 1) { // last chunk
currentChunkSize = (fileSize % chunkSize());
if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size.
currentChunkSize = chunkSize();
}
isFinalChunk = true;
}
} else {
// if there's only one chunk, it's the final one
isFinalChunk = true;
}
if (isFinalChunk && !_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_transmissionChecksumType, _transmissionChecksum);
}
const QString fileName = _propagator->getFilePath(_item->_file);
if (! device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
// If the file is currently locked, we want to retry the sync
// when it becomes available again.
if (FileSystem::isFileLocked(fileName)) {
emit _propagator->seenLockedFile(fileName);
}
// Soft error because this is likely caused by the user modifying his files while syncing
abortWithError( SyncFileItem::SoftError, device->errorString() );
delete device;
return;
}
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
PUTFileJob* job = new PUTFileJob(_propagator->account(), _propagator->_remoteFolder + path, device, headers, _currentChunk);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(slotUploadProgress(qint64,qint64)));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), device, SLOT(slotJobUploadProgress(qint64,qint64)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
_propagator->_activeJobList.append(this);
_currentChunk++;
bool parallelChunkUpload = true;
QByteArray env = qgetenv("OWNCLOUD_PARALLEL_CHUNK");
if (!env.isEmpty()) {
parallelChunkUpload = env != "false" && env != "0";
} else {
int versionNum = _propagator->account()->serverVersionInt();
if (versionNum < 0x080003) {
// Disable parallel chunk upload severs older than 8.0.3 to avoid too many
// internal sever errors (#2743, #2938)
parallelChunkUpload = false;
}
}
if (_currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
parallelChunkUpload = false;
}
if (parallelChunkUpload && (_propagator->_activeJobList.count() < _propagator->maximumActiveJob())
&& _currentChunk < _chunkCount ) {
startNextChunk();
}
if (!parallelChunkUpload || _chunkCount - _currentChunk <= 0) {
emit ready();
}
}
void PropagateUploadFileCommon::finalize()
void PropagateUploadFile::slotPutFinished()
{
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
Q_ASSERT(job);
slotJobDestroyed(job); // remove it from the _jobs list
qDebug() << Q_FUNC_INFO << job->reply()->request().url() << "FINISHED WITH STATUS"
<< job->reply()->error()
<< (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString())
<< job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
<< job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
_propagator->_activeJobList.removeOne(this);
if (_finished) {
// We have sent the finished signal already. We don't need to handle any remaining jobs
return;
}
QNetworkReply::NetworkError err = job->reply()->error();
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
if (err == QNetworkReply::OperationCanceledError && job->reply()->property(owncloudShouldSoftCancelPropertyName).isValid()) {
// Abort the job and try again later.
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
_propagator->_anotherSyncNeeded = true;
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
return;
}
#endif
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(checkForProblemsWithShared(_item->_httpErrorCode,
tr("The file was edited locally but is part of a read only share. "
"It is restored and your edit is in the conflict file."))) {
return;
}
QByteArray replyContent = job->reply()->readAll();
qDebug() << replyContent; // display the XML error in the debug
QString errorString = errorMessage(job->errorString(), replyContent);
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
if (_item->_httpErrorCode == 412) {
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
_propagator->_anotherSyncNeeded = true;
}
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
abortWithError(status, errorString);
return;
}
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// 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;
}
startPollJob(path);
return;
}
// Check the file again post upload.
// Two cases must be considered separately: If the upload is finished,
// the file is on the server and has a changed ETag. In that case,
// the etag has to be properly updated in the client journal, and because
// of that we can bail out here with an error. But we can reschedule a
// sync ASAP.
// But if the upload is ongoing, because not all chunks were uploaded
// 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;
// Check if the file still exists
const QString fullFilePath(_propagator->getFilePath(_item->_file));
if( !FileSystem::fileExists(fullFilePath) ) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
_propagator->_anotherSyncNeeded = true;
}
}
// Check whether the file changed since discovery.
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
_propagator->_anotherSyncNeeded = true;
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
return;
}
}
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;
}
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
_item->_hasBlacklistEntry = false;
}
SyncJournalDb::UploadInfo pi;
pi._valid = true;
auto currentChunk = job->_chunk;
foreach (auto *job, _jobs) {
// Take the minimum finished one
if (auto putJob = qobject_cast<PUTFileJob*>(job)) {
currentChunk = qMin(currentChunk, putJob->_chunk - 1);
}
}
pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount ; // next chunk to start with
pi._transferid = _transferId;
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
_propagator->_journal->setUploadInfo(_item->_file, pi);
_propagator->_journal->commit("Upload info");
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() ) {
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
}
_item->_fileId = fid;
}
_item->_etag = etag;
_item->_responseTimeStamp = job->responseTimestamp();
if (job->reply()->rawHeader("X-OC-MTime") != "accepted") {
// X-OC-MTime is supported since owncloud 5.0. But not when chunking.
// Normally Owncloud 6 always puts X-OC-MTime
qWarning() << "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");
}
// performance logging
_item->_requestDuration = _stopWatch.stop();
qDebug() << "*==* duration UPLOAD" << _item->_size
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
<< _item->_requestDuration;
// The job might stay alive for the whole sync, release this tiny bit of memory.
_stopWatch.reset();
finalize(*_item);
}
void PropagateUploadFile::finalize(const SyncFileItem &copy)
{
// Normally, copy == _item, but when it comes from the UpdateMTimeAndETagJob, we need to do
// some updates
_item->_etag = copy._etag;
_item->_fileId = copy._fileId;
_item->_requestDuration = _duration.elapsed();
_finished = true;
if (!_propagator->_journal->setFileRecord(SyncJournalFileRecord(*_item, _propagator->getFilePath(_item->_file)))) {
@@ -562,5 +807,92 @@ void PropagateUploadFileCommon::finalize()
done(SyncFileItem::Success);
}
void PropagateUploadFile::slotUploadProgress(qint64 sent, qint64 total)
{
// Completion is signaled with sent=0, total=0; avoid accidentally
// resetting progress due to the sent being zero by ignoring it.
// finishedSignal() is bound to be emitted soon anyway.
// See https://bugreports.qt.io/browse/QTBUG-44782.
if (sent == 0 && total == 0) {
return;
}
int progressChunk = _currentChunk + _startChunk - 1;
if (progressChunk >= _chunkCount)
progressChunk = _currentChunk - 1;
// amount is the number of bytes already sent by all the other chunks that were sent
// not including this one.
// FIXME: this assumes all chunks have the same size, which is true only if the last chunk
// has not been finished (which should not happen because the last chunk is sent sequentially)
quint64 amount = progressChunk * chunkSize();
sender()->setProperty("byteWritten", sent);
if (_jobs.count() > 1) {
amount -= (_jobs.count() -1) * chunkSize();
foreach (QObject *j, _jobs) {
amount += j->property("byteWritten").toULongLong();
}
} else {
// sender() is the only current job, no need to look at the byteWritten properties
amount += sent;
}
emit progress(*_item, amount);
}
void PropagateUploadFile::startPollJob(const QString& path)
{
PollJob* job = new PollJob(_propagator->account(), path, _item,
_propagator->_journal, _propagator->_localDir, this);
connect(job, SIGNAL(finishedSignal()), SLOT(slotPollFinished()));
SyncJournalDb::PollInfo info;
info._file = _item->_file;
info._url = path;
info._modtime = _item->_modtime;
_propagator->_journal->setPollInfo(info);
_propagator->_journal->commit("add poll info");
_propagator->_activeJobList.append(this);
job->start();
}
void PropagateUploadFile::slotPollFinished()
{
PollJob *job = qobject_cast<PollJob *>(sender());
Q_ASSERT(job);
_propagator->_activeJobList.removeOne(this);
if (job->_item->_status != SyncFileItem::Success) {
_finished = true;
done(job->_item->_status, job->_item->_errorString);
return;
}
finalize(*job->_item);
}
void PropagateUploadFile::slotJobDestroyed(QObject* job)
{
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job) , _jobs.end());
}
void PropagateUploadFile::abort()
{
foreach(auto *job, _jobs) {
if (job->reply()) {
qDebug() << Q_FUNC_INFO << job << this->_item->_file;
job->reply()->abort();
}
}
}
// This function is used whenever there is an error occuring and jobs might be in progress
void PropagateUploadFile::abortWithError(SyncFileItem::Status status, const QString &error)
{
_finished = true;
abort();
done(status, error);
}
}
+33 -135
Ver Arquivo
@@ -89,17 +89,12 @@ private:
QScopedPointer<QIODevice> _device;
QMap<QByteArray, QByteArray> _headers;
QString _errorString;
QUrl _url;
public:
// Takes ownership of the device
explicit PUTFileJob(AccountPtr account, const QString& path, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, path, parent), _device(device), _headers(headers), _chunk(chunk) {}
explicit PUTFileJob(AccountPtr account, const QUrl& url, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, QString(), parent), _device(device), _headers(headers)
, _url(url), _chunk(chunk) {}
~PUTFileJob();
int _chunk;
@@ -160,90 +155,10 @@ signals:
};
/**
* @brief The PropagateUploadFileCommon class is the code common between all chunking algorithms
* @brief The PropagateUploadFile class
* @ingroup libsync
*
* State Machine:
*
* +---> start() --> (delete job) -------+
* | |
* +--> slotComputeContentChecksum() <---+
* |
* v
* slotComputeTransmissionChecksum()
* |
* v
* slotStartUpload() -> doStartUpload()
* .
* .
* v
* finalize() or abortWithError() or startPollJob()
*/
class PropagateUploadFileCommon : public PropagateItemJob {
Q_OBJECT
protected:
QElapsedTimer _duration;
QVector<AbstractNetworkJob*> _jobs; /// network jobs that are currently in transit
bool _finished; /// Tells that all the jobs have been finished
bool _deleteExisting;
// measure the performance of checksum calc and upload
Utility::StopWatch _stopWatch;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
public:
PropagateUploadFileCommon(OwncloudPropagator* propagator,const SyncFileItemPtr& item)
: PropagateItemJob(propagator, item), _finished(false), _deleteExisting(false) {}
/**
* Whether an existing entity with the same name may be deleted before
* the upload.
*
* Default: false.
*/
void setDeleteExisting(bool enabled);
void start() Q_DECL_OVERRIDE;
bool isLikelyFinishedQuickly() Q_DECL_OVERRIDE { return _item->_size < 100*1024; }
private slots:
void slotComputeContentChecksum();
// Content checksum computed, compute the transmission checksum
void slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum);
// transmission checksum computed, prepare the upload
void slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum);
public:
virtual void doStartUpload() = 0;
void startPollJob(const QString& path);
void finalize();
void abortWithError(SyncFileItem::Status status, const QString &error);
public slots:
void abort() Q_DECL_OVERRIDE;
void slotJobDestroyed(QObject *job);
private slots:
void slotPollFinished();
protected:
// Bases headers that need to be sent with every chunk
QMap<QByteArray, QByteArray> headers();
};
/**
* @ingroup libsync
*
* Propagation job, impementing the old chunking agorithm
*
*/
class PropagateUploadFileV1 : public PropagateUploadFileCommon {
class PropagateUploadFile : public PropagateItemJob {
Q_OBJECT
private:
@@ -261,66 +176,49 @@ private:
int _currentChunk;
int _chunkCount; /// Total number of chunks for this file
int _transferId; /// transfer id (part of the url)
QElapsedTimer _duration;
QVector<AbstractNetworkJob*> _jobs; /// network jobs that are currently in transit
bool _finished; // Tells that all the jobs have been finished
// measure the performance of checksum calc and upload
Utility::StopWatch _stopWatch;
QByteArray _transmissionChecksum;
QByteArray _transmissionChecksumType;
bool _deleteExisting;
quint64 chunkSize() const { return _propagator->chunkSize(); }
public:
PropagateUploadFileV1(OwncloudPropagator* propagator,const SyncFileItemPtr& item) :
PropagateUploadFileCommon(propagator,item) {}
PropagateUploadFile(OwncloudPropagator* propagator,const SyncFileItemPtr& item)
: PropagateItemJob(propagator, item), _startChunk(0), _currentChunk(0), _chunkCount(0), _transferId(0), _finished(false), _deleteExisting(false) {}
void start() Q_DECL_OVERRIDE;
void doStartUpload() Q_DECL_OVERRIDE;
private slots:
void startNextChunk();
void slotPutFinished();
void slotUploadProgress(qint64,qint64);
};
/**
* @ingroup libsync
*
* Propagation job, impementing the new chunking agorithm
*
*/
class PropagateUploadFileNG : public PropagateUploadFileCommon {
Q_OBJECT
private:
quint64 _sent; /// amount of data (bytes) that was already sent
uint _transferId; /// transfer id (part of the url)
int _currentChunk; /// Id of the next chunk that will be sent
bool _removeJobError; /// If not null, there was an error removing the job
// Map chunk number with its size from the PROPFIND on resume.
// (Only used from slotPropfindIterate/slotPropfindFinished because the LsColJob use signals to report data.)
QMap<int, quint64> _serverChunks;
quint64 chunkSize() const { return _propagator->chunkSize(); }
/**
* Return the URL of a chunk.
* If chunk == -1, returns the URL of the parent folder containing the chunks
* Whether an existing entity with the same name may be deleted before
* the upload.
*
* Default: false.
*/
QUrl chunkUrl(int chunk = -1);
void setDeleteExisting(bool enabled);
public:
PropagateUploadFileNG(OwncloudPropagator* propagator,const SyncFileItemPtr& item) :
PropagateUploadFileCommon(propagator,item) {}
void doStartUpload() Q_DECL_OVERRIDE;
private:
void startNewUpload();
void startNextChunk();
private slots:
void slotPropfindFinished();
void slotPropfindFinishedWithError();
void slotPropfindIterate(const QString &name, const QMap<QString,QString> &properties);
void slotDeleteJobFinished();
void slotMkColFinished(QNetworkReply::NetworkError);
void slotPutFinished();
void slotMoveJobFinished();
void slotPollFinished();
void slotUploadProgress(qint64,qint64);
};
void abort() Q_DECL_OVERRIDE;
void startNextChunk();
void finalize(const SyncFileItem&);
void slotJobDestroyed(QObject *job);
void slotStartUpload(const QByteArray& transmissionChecksumType, const QByteArray& transmissionChecksum);
void slotComputeTransmissionChecksum(const QByteArray& contentChecksumType, const QByteArray& contentChecksum);
void slotComputeContentChecksum();
private:
void startPollJob(const QString& path);
void abortWithError(SyncFileItem::Status status, const QString &error);
};
}
-488
Ver Arquivo
@@ -1,488 +0,0 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "config.h"
#include "propagateupload.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "utility.h"
#include "filesystem.h"
#include "propagatorjobs.h"
#include "syncengine.h"
#include "propagateremotemove.h"
#include "propagateremotedelete.h"
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>
#include <cstring>
namespace OCC {
QUrl PropagateUploadFileNG::chunkUrl(int chunk)
{
QString path = QLatin1String("remote.php/dav/uploads/")
+ _propagator->account()->user()
+ QLatin1Char('/') + QString::number(_transferId);
if (chunk >= 0) {
path += QLatin1Char('/') + QString::number(chunk);
}
return Utility::concatUrlPath(_propagator->account()->url(), path);
}
/*
State machine:
*----> doStartUpload()
Check the db: is there an entry?
/ \
no yes
/ \
/ PROPFIND
startNewUpload() <-+ +----------------------------\
| | | \
MKCOL + slotPropfindFinishedWithError() slotPropfindFinished()
| Is there stale files to remove?
slotMkColFinished() | |
| no yes
| | |
| | DeleteJob
| | |
+-----+<------------------------------------------------------+<--- slotDeleteJobFinished()
|
+----> startNextChunk() ---finished? --+
^ | |
+---------------+ |
|
+----------------------------------------+
|
+-> MOVE ------> moveJobFinished() ---> finalize()
*/
void PropagateUploadFileNG::doStartUpload()
{
_duration.start();
_propagator->_activeJobList.append(this);
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
_transferId = progressInfo._transferid;
auto url = chunkUrl();
auto job = new LsColJob(_propagator->account(), url, this);
_jobs.append(job);
job->setProperties(QList<QByteArray>() << "resourcetype" << "getcontentlength");
connect(job, SIGNAL(finishedWithoutError()), this, SLOT(slotPropfindFinished()));
connect(job, SIGNAL(finishedWithError(QNetworkReply*)),
this, SLOT(slotPropfindFinishedWithError()));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
connect(job, SIGNAL(directoryListingIterated(QString,QMap<QString,QString>)),
this, SLOT(slotPropfindIterate(QString,QMap<QString,QString>)));
job->start();
return;
}
startNewUpload();
}
void PropagateUploadFileNG::slotPropfindIterate(const QString &name, const QMap<QString,QString> &properties)
{
if (name == chunkUrl().path()) {
return; // skip the info about the path itself
}
bool ok = false;
auto chunkId = name.mid(name.lastIndexOf('/')+1).toUInt(&ok);
if (ok) {
_serverChunks[chunkId] = properties["getcontentlength"].toULongLong();
}
}
void PropagateUploadFileNG::slotPropfindFinished()
{
auto job = qobject_cast<LsColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
_propagator->_activeJobList.removeOne(this);
_currentChunk = 0;
_sent = 0;
while (_serverChunks.contains(_currentChunk)) {
_sent += _serverChunks[_currentChunk];
_serverChunks.remove(_currentChunk);
++_currentChunk;
}
if (_sent > _item->_size) {
// Normally this can't happen because the size is xor'ed with the transfer id, and it is
// therefore impossible that there is more data on the server than on the file.
qWarning() << "Inconsistency while resuming " << _item->_file
<< ": the size on the server (" << _sent << ") is bigger than the size of the file ("
<< _item->_size << ")";
startNewUpload();
return;
}
qDebug() << "Resuming "<< _item->_file << " from chunk " << _currentChunk << "; sent ="<< _sent;
if (!_serverChunks.isEmpty()) {
qDebug() << "To Delete" << _serverChunks;
_propagator->_activeJobList.append(this);
_removeJobError = false;
// Make sure that if there is a "hole" and then a few more chunks, on the server
// we should remove the later chunks. Otherwise when we do dynamic chunk sizing, we may end up
// with corruptions if there are too many chunks, or if we abort and there are still stale chunks.
for (auto it = _serverChunks.begin(); it != _serverChunks.end(); ++it) {
auto job = new DeleteJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), QString::number(it.key())), this);
QObject::connect(job, SIGNAL(finishedSignal()), this, SLOT(slotDeleteJobFinished()));
_jobs.append(job);
job->start();
}
return;
}
startNextChunk();
}
void PropagateUploadFileNG::slotPropfindFinishedWithError()
{
auto job = qobject_cast<LsColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
auto httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
auto status = classifyError(err, httpErrorCode, &_propagator->_anotherSyncNeeded);
if (status == SyncFileItem::FatalError) {
_propagator->_activeJobList.removeOne(this);
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
abortWithError(status, errorString);
return;
}
startNewUpload();
}
void PropagateUploadFileNG::slotDeleteJobFinished()
{
auto job = qobject_cast<DeleteJob *>(sender());
Q_ASSERT(job);
_jobs.remove(_jobs.indexOf(job));
QNetworkReply::NetworkError err = job->reply()->error();
if (err != QNetworkReply::NoError && err != QNetworkReply::ContentNotFoundError) {
const int httpStatus = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
SyncFileItem::Status status = classifyError(err, httpStatus);
if (status == SyncFileItem::FatalError) {
abortWithError(status, job->errorString());
return;
} else {
qWarning() << "DeleteJob errored out" << job->errorString() << job->reply()->url();
_removeJobError = true;
// Let the other jobs finish
}
}
if (_jobs.isEmpty()) {
_propagator->_activeJobList.removeOne(this);
if (_removeJobError) {
// There was an error removing some files, just start over
startNewUpload();
} else {
startNextChunk();
}
}
}
void PropagateUploadFileNG::startNewUpload()
{
Q_ASSERT(_propagator->_activeJobList.count(this) == 1);
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16) ^ qHash(_item->_file);
_sent = 0;
_currentChunk = 0;
emit progress(*_item, 0);
SyncJournalDb::UploadInfo pi;
pi._valid = true;
pi._transferid = _transferId;
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
_propagator->_journal->setUploadInfo(_item->_file, pi);
_propagator->_journal->commit("Upload info");
QMap<QByteArray, QByteArray> headers;
headers["OC-Total-Length"] = QByteArray::number(_item->_size);
auto job = new MkColJob(_propagator->account(), chunkUrl(), headers, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
this, SLOT(slotMkColFinished(QNetworkReply::NetworkError)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
}
void PropagateUploadFileNG::slotMkColFinished(QNetworkReply::NetworkError)
{
_propagator->_activeJobList.removeOne(this);
auto job = qobject_cast<MkColJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (err != QNetworkReply::NoError || _item->_httpErrorCode != 201) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
QString errorString = errorMessage(job->reply()->errorString(), job->reply()->readAll());
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
abortWithError(status, errorString);
return;
}
startNextChunk();
}
void PropagateUploadFileNG::startNextChunk()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return;
quint64 fileSize = _item->_size;
Q_ASSERT(fileSize >= _sent);
quint64 currentChunkSize = qMin(chunkSize(), fileSize - _sent);
if (currentChunkSize == 0) {
Q_ASSERT(_jobs.isEmpty()); // There should be no running job anymore
_finished = true;
// Finish with a MOVE
QString destination = _propagator->account()->url().path()
+ QLatin1String("/remote.php/dav/files/") + _propagator->account()->user()
+ _propagator->_remoteFolder + _item->_file;
auto headers = PropagateUploadFileCommon::headers();
// "If-Match applies to the source, but we are interested in comparing the etag of the destination
auto ifMatch = headers.take("If-Match");
if (!ifMatch.isEmpty()) {
headers["If"] = "<" + destination.toUtf8() + "> ([" + ifMatch + "])";
}
if (!_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_transmissionChecksumType, _transmissionChecksum);
}
auto job = new MoveJob(_propagator->account(), Utility::concatUrlPath(chunkUrl(), "/.file"),
destination, headers, this);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotMoveJobFinished()));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
_propagator->_activeJobList.append(this);
job->start();
return;
}
auto device = new UploadDevice(&_propagator->_bandwidthManager);
const QString fileName = _propagator->getFilePath(_item->_file);
if (! device->prepareAndOpen(fileName, _sent, currentChunkSize)) {
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
// If the file is currently locked, we want to retry the sync
// when it becomes available again.
if (FileSystem::isFileLocked(fileName)) {
emit _propagator->seenLockedFile(fileName);
}
// Soft error because this is likely caused by the user modifying his files while syncing
abortWithError( SyncFileItem::SoftError, device->errorString() );
return;
}
QMap<QByteArray, QByteArray> headers;
headers["OC-Chunk-Offset"] = QByteArray::number(_sent);
_sent += currentChunkSize;
QUrl url = chunkUrl(_currentChunk);
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
PUTFileJob* job = new PUTFileJob(_propagator->account(), url, device, headers, _currentChunk);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
this, SLOT(slotUploadProgress(qint64,qint64)));
connect(job, SIGNAL(uploadProgress(qint64,qint64)),
device, SLOT(slotJobUploadProgress(qint64,qint64)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
_propagator->_activeJobList.append(this);
_currentChunk++;
// FIXME! parallel chunk?
}
void PropagateUploadFileNG::slotPutFinished()
{
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
Q_ASSERT(job);
slotJobDestroyed(job); // remove it from the _jobs list
qDebug() << job->reply()->request().url() << "FINISHED WITH STATUS"
<< job->reply()->error()
<< (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString())
<< job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
<< job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
_propagator->_activeJobList.removeOne(this);
if (_finished) {
// We have sent the finished signal already. We don't need to handle any remaining jobs
return;
}
QNetworkReply::NetworkError err = job->reply()->error();
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
if (err == QNetworkReply::OperationCanceledError && job->reply()->property("owncloud-should-soft-cancel").isValid()) {
// Abort the job and try again later.
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
_propagator->_anotherSyncNeeded = true;
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
return;
}
#endif
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray replyContent = job->reply()->readAll();
qDebug() << replyContent; // display the XML error in the debug
QString errorString = errorMessage(job->errorString(), replyContent);
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
// FIXME! can this happen for the chunks?
if (_item->_httpErrorCode == 412) {
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
_propagator->_anotherSyncNeeded = true;
}
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
abortWithError(status, errorString);
return;
}
Q_ASSERT(_sent <= _item->_size);
bool finished = _sent == _item->_size;
// Check if the file still exists
const QString fullFilePath(_propagator->getFilePath(_item->_file));
if( !FileSystem::fileExists(fullFilePath) ) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
_propagator->_anotherSyncNeeded = true;
}
}
// Check whether the file changed since discovery.
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
_propagator->_anotherSyncNeeded = true;
if( !finished ) {
abortWithError(SyncFileItem::SoftError, tr("Local file changed during sync."));
return;
}
}
if (!finished) {
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
_item->_hasBlacklistEntry = false;
}
}
startNextChunk();
}
void PropagateUploadFileNG::slotMoveJobFinished()
{
_propagator->_activeJobList.removeOne(this);
auto job = qobject_cast<MoveJob *>(sender());
slotJobDestroyed(job); // remove it from the _jobs list
QNetworkReply::NetworkError err = job->reply()->error();
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (err != QNetworkReply::NoError) {
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
QString errorString = errorMessage(job->errorString(), job->reply()->readAll());
abortWithError(status, errorString);
return;
}
if (_item->_httpErrorCode != 201 && _item->_httpErrorCode != 204) {
abortWithError(SyncFileItem::NormalError, tr("Unexpected return code from server (%1)").arg(_item->_httpErrorCode));
return;
}
QByteArray fid = job->reply()->rawHeader("OC-FileID");
if(fid.isEmpty()) {
qWarning() << "Server did not return a OC-FileID" << _item->_file;
abortWithError(SyncFileItem::NormalError, tr("Missing File ID from server"));
return;
} else {
// the old file id should only be empty for new files uploaded
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
}
_item->_fileId = fid;
}
_item->_etag = getEtagFromReply(job->reply());;
if (_item->_etag.isEmpty()) {
qWarning() << "Server did not return an ETAG" << _item->_file;
abortWithError(SyncFileItem::NormalError, tr("Missing ETag from server"));
return;
}
_item->_responseTimeStamp = job->responseTimestamp();
// performance logging
_item->_requestDuration = _stopWatch.stop();
qDebug() << "*==* duration UPLOAD" << _item->_size
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
<< _item->_requestDuration;
// The job might stay alive for the whole sync, release this tiny bit of memory.
_stopWatch.reset();
finalize();
}
void PropagateUploadFileNG::slotUploadProgress(qint64 sent, qint64 total)
{
// Completion is signaled with sent=0, total=0; avoid accidentally
// resetting progress due to the sent being zero by ignoring it.
// finishedSignal() is bound to be emitted soon anyway.
// See https://bugreports.qt.io/browse/QTBUG-44782.
if (sent == 0 && total == 0) {
return;
}
emit progress(*_item, _sent + sent - total);
}
}
-375
Ver Arquivo
@@ -1,375 +0,0 @@
/*
* Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include "config.h"
#include "propagateupload.h"
#include "owncloudpropagator_p.h"
#include "networkjobs.h"
#include "account.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "utility.h"
#include "filesystem.h"
#include "propagatorjobs.h"
#include "checksums.h"
#include "syncengine.h"
#include "propagateremotedelete.h"
#include <json.h>
#include <QNetworkAccessManager>
#include <QFileInfo>
#include <QDir>
#include <cmath>
#include <cstring>
namespace OCC {
void PropagateUploadFileV1::doStartUpload()
{
_chunkCount = std::ceil(_item->_size / double(chunkSize()));
_startChunk = 0;
_transferId = qrand() ^ _item->_modtime ^ (_item->_size << 16);
const SyncJournalDb::UploadInfo progressInfo = _propagator->_journal->getUploadInfo(_item->_file);
if (progressInfo._valid && Utility::qDateTimeToTime_t(progressInfo._modtime) == _item->_modtime ) {
_startChunk = progressInfo._chunk;
_transferId = progressInfo._transferid;
qDebug() << Q_FUNC_INFO << _item->_file << ": Resuming from chunk " << _startChunk;
}
_currentChunk = 0;
_duration.start();
emit progress(*_item, 0);
startNextChunk();
}
void PropagateUploadFileV1::startNextChunk()
{
if (_propagator->_abortRequested.fetchAndAddRelaxed(0))
return;
if (! _jobs.isEmpty() && _currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
// We return now and when the _jobs are finished we will proceed with the last chunk
// NOTE: Some other parts of the code such as slotUploadProgress also assume that the last chunk
// is sent last.
return;
}
quint64 fileSize = _item->_size;
auto headers = PropagateUploadFileCommon::headers();
headers["OC-Total-Length"] = QByteArray::number(fileSize);
headers["OC-Chunk-Size"]= QByteArray::number(quint64(chunkSize()));
QString path = _item->_file;
UploadDevice *device = new UploadDevice(&_propagator->_bandwidthManager);
qint64 chunkStart = 0;
qint64 currentChunkSize = fileSize;
bool isFinalChunk = false;
if (_chunkCount > 1) {
int sendingChunk = (_currentChunk + _startChunk) % _chunkCount;
// XOR with chunk size to make sure everything goes well if chunk size changes between runs
uint transid = _transferId ^ chunkSize();
qDebug() << "Upload chunk" << sendingChunk << "of" << _chunkCount << "transferid(remote)=" << transid;
path += QString("-chunking-%1-%2-%3").arg(transid).arg(_chunkCount).arg(sendingChunk);
headers["OC-Chunked"] = "1";
chunkStart = chunkSize() * quint64(sendingChunk);
currentChunkSize = chunkSize();
if (sendingChunk == _chunkCount - 1) { // last chunk
currentChunkSize = (fileSize % chunkSize());
if( currentChunkSize == 0 ) { // if the last chunk pretends to be 0, its actually the full chunk size.
currentChunkSize = chunkSize();
}
isFinalChunk = true;
}
} else {
// if there's only one chunk, it's the final one
isFinalChunk = true;
}
if (isFinalChunk && !_transmissionChecksumType.isEmpty()) {
headers[checkSumHeaderC] = makeChecksumHeader(
_transmissionChecksumType, _transmissionChecksum);
}
const QString fileName = _propagator->getFilePath(_item->_file);
if (! device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
// If the file is currently locked, we want to retry the sync
// when it becomes available again.
if (FileSystem::isFileLocked(fileName)) {
emit _propagator->seenLockedFile(fileName);
}
// Soft error because this is likely caused by the user modifying his files while syncing
abortWithError( SyncFileItem::SoftError, device->errorString() );
delete device;
return;
}
// job takes ownership of device via a QScopedPointer. Job deletes itself when finishing
PUTFileJob* job = new PUTFileJob(_propagator->account(), _propagator->_remoteFolder + path, device, headers, _currentChunk);
_jobs.append(job);
connect(job, SIGNAL(finishedSignal()), this, SLOT(slotPutFinished()));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(slotUploadProgress(qint64,qint64)));
connect(job, SIGNAL(uploadProgress(qint64,qint64)), device, SLOT(slotJobUploadProgress(qint64,qint64)));
connect(job, SIGNAL(destroyed(QObject*)), this, SLOT(slotJobDestroyed(QObject*)));
job->start();
_propagator->_activeJobList.append(this);
_currentChunk++;
bool parallelChunkUpload = true;
QByteArray env = qgetenv("OWNCLOUD_PARALLEL_CHUNK");
if (!env.isEmpty()) {
parallelChunkUpload = env != "false" && env != "0";
} else {
int versionNum = _propagator->account()->serverVersionInt();
if (versionNum < 0x080003) {
// Disable parallel chunk upload severs older than 8.0.3 to avoid too many
// internal sever errors (#2743, #2938)
parallelChunkUpload = false;
}
}
if (_currentChunk + _startChunk >= _chunkCount - 1) {
// Don't do parallel upload of chunk if this might be the last chunk because the server cannot handle that
// https://github.com/owncloud/core/issues/11106
parallelChunkUpload = false;
}
if (parallelChunkUpload && (_propagator->_activeJobList.count() < _propagator->maximumActiveJob())
&& _currentChunk < _chunkCount ) {
startNextChunk();
}
if (!parallelChunkUpload || _chunkCount - _currentChunk <= 0) {
emit ready();
}
}
void PropagateUploadFileV1::slotPutFinished()
{
PUTFileJob *job = qobject_cast<PUTFileJob *>(sender());
Q_ASSERT(job);
slotJobDestroyed(job); // remove it from the _jobs list
qDebug() << Q_FUNC_INFO << job->reply()->request().url() << "FINISHED WITH STATUS"
<< job->reply()->error()
<< (job->reply()->error() == QNetworkReply::NoError ? QLatin1String("") : job->reply()->errorString())
<< job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute)
<< job->reply()->attribute(QNetworkRequest::HttpReasonPhraseAttribute);
_propagator->_activeJobList.removeOne(this);
if (_finished) {
// We have sent the finished signal already. We don't need to handle any remaining jobs
return;
}
QNetworkReply::NetworkError err = job->reply()->error();
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 2)
if (err == QNetworkReply::OperationCanceledError && job->reply()->property("owncloud-should-soft-cancel").isValid()) { // Abort the job and try again later.
// This works around a bug in QNAM wich might reuse a non-empty buffer for the next request.
qDebug() << "Forcing job abort on HTTP connection reset with Qt < 5.4.2.";
_propagator->_anotherSyncNeeded = true;
abortWithError(SyncFileItem::SoftError, tr("Forcing job abort on HTTP connection reset with Qt < 5.4.2."));
return;
}
#endif
if (err != QNetworkReply::NoError) {
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if(checkForProblemsWithShared(_item->_httpErrorCode,
tr("The file was edited locally but is part of a read only share. "
"It is restored and your edit is in the conflict file."))) {
return;
}
QByteArray replyContent = job->reply()->readAll();
qDebug() << replyContent; // display the XML error in the debug
QString errorString = errorMessage(job->errorString(), replyContent);
if (job->reply()->hasRawHeader("OC-ErrorString")) {
errorString = job->reply()->rawHeader("OC-ErrorString");
}
if (_item->_httpErrorCode == 412) {
// Precondition Failed: Maybe the bad etag is in the database, we need to clear the
// parent folder etag so we won't read from DB next sync.
_propagator->_journal->avoidReadFromDbOnNextSync(_item->_file);
_propagator->_anotherSyncNeeded = true;
}
SyncFileItem::Status status = classifyError(err, _item->_httpErrorCode,
&_propagator->_anotherSyncNeeded);
abortWithError(status, errorString);
return;
}
_item->_httpErrorCode = job->reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// 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;
}
startPollJob(path);
return;
}
// Check the file again post upload.
// Two cases must be considered separately: If the upload is finished,
// the file is on the server and has a changed ETag. In that case,
// the etag has to be properly updated in the client journal, and because
// of that we can bail out here with an error. But we can reschedule a
// sync ASAP.
// But if the upload is ongoing, because not all chunks were uploaded
// 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;
// Check if the file still exists
const QString fullFilePath(_propagator->getFilePath(_item->_file));
if( !FileSystem::fileExists(fullFilePath) ) {
if (!finished) {
abortWithError(SyncFileItem::SoftError, tr("The local file was removed during sync."));
return;
} else {
_propagator->_anotherSyncNeeded = true;
}
}
// Check whether the file changed since discovery.
if (! FileSystem::verifyFileUnchanged(fullFilePath, _item->_size, _item->_modtime)) {
_propagator->_anotherSyncNeeded = true;
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
return;
}
}
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;
}
// Deletes an existing blacklist entry on successful chunk upload
if (_item->_hasBlacklistEntry) {
_propagator->_journal->wipeErrorBlacklistEntry(_item->_file);
_item->_hasBlacklistEntry = false;
}
SyncJournalDb::UploadInfo pi;
pi._valid = true;
auto currentChunk = job->_chunk;
foreach (auto *job, _jobs) {
// Take the minimum finished one
if (auto putJob = qobject_cast<PUTFileJob*>(job)) {
currentChunk = qMin(currentChunk, putJob->_chunk - 1);
}
}
pi._chunk = (currentChunk + _startChunk + 1) % _chunkCount ; // next chunk to start with
pi._transferid = _transferId;
pi._modtime = Utility::qDateTimeFromTime_t(_item->_modtime);
_propagator->_journal->setUploadInfo(_item->_file, pi);
_propagator->_journal->commit("Upload info");
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() ) {
if( !_item->_fileId.isEmpty() && _item->_fileId != fid ) {
qDebug() << "WARN: File ID changed!" << _item->_fileId << fid;
}
_item->_fileId = fid;
}
_item->_etag = etag;
_item->_responseTimeStamp = job->responseTimestamp();
if (job->reply()->rawHeader("X-OC-MTime") != "accepted") {
// X-OC-MTime is supported since owncloud 5.0. But not when chunking.
// Normally Owncloud 6 always puts X-OC-MTime
qWarning() << "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");
}
// performance logging
_item->_requestDuration = _stopWatch.stop();
qDebug() << "*==* duration UPLOAD" << _item->_size
<< _stopWatch.durationOfLap(QLatin1String("ContentChecksum"))
<< _stopWatch.durationOfLap(QLatin1String("TransmissionChecksum"))
<< _item->_requestDuration;
// The job might stay alive for the whole sync, release this tiny bit of memory.
_stopWatch.reset();
finalize();
}
void PropagateUploadFileV1::slotUploadProgress(qint64 sent, qint64 total)
{
// Completion is signaled with sent=0, total=0; avoid accidentally
// resetting progress due to the sent being zero by ignoring it.
// finishedSignal() is bound to be emitted soon anyway.
// See https://bugreports.qt.io/browse/QTBUG-44782.
if (sent == 0 && total == 0) {
return;
}
int progressChunk = _currentChunk + _startChunk - 1;
if (progressChunk >= _chunkCount)
progressChunk = _currentChunk - 1;
// amount is the number of bytes already sent by all the other chunks that were sent
// not including this one.
// FIXME: this assumes all chunks have the same size, which is true only if the last chunk
// has not been finished (which should not happen because the last chunk is sent sequentially)
quint64 amount = progressChunk * chunkSize();
sender()->setProperty("byteWritten", sent);
if (_jobs.count() > 1) {
amount -= (_jobs.count() -1) * chunkSize();
foreach (QObject *j, _jobs) {
amount += j->property("byteWritten").toULongLong();
}
} else {
// sender() is the only current job, no need to look at the byteWritten properties
amount += sent;
}
emit progress(*_item, amount);
}
}
+4 -21
Ver Arquivo
@@ -334,16 +334,6 @@ int SyncEngine::treewalkRemote( TREE_WALK_FILE* file, void *data )
return static_cast<SyncEngine*>(data)->treewalkFile( file, true );
}
/**
* The main function in the post-reconcile phase.
*
* Called on each entry in the local and remote trees by
* csync_walk_local_tree()/csync_walk_remote_tree().
*
* It merges the two csync rbtrees into a single map of SyncFileItems.
*
* See doc/dev/sync-algorithm.md for an overview.
*/
int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
{
if( ! file ) return -1;
@@ -599,15 +589,11 @@ int SyncEngine::treewalkFile( TREE_WALK_FILE *file, bool remote )
// This counts as a NONE for detecting if all the files on the server were changed
_hasNoneFiles = true;
} else if (!isDirectory) {
auto difftime = std::difftime(file->modtime, file->other.modtime);
if (difftime < -3600*2) {
if (std::difftime(file->modtime, file->other.modtime) < 0) {
// We are going back on time
// We only increment if the difference is more than two hours to avoid clock skew
// issues or DST changes. (We simply ignore files that goes in the past less than
// two hours for the backup detection heuristics.)
_backInTimeFiles++;
qDebug() << file->path << "has a timestamp earlier than the local file";
} else if (difftime > 0) {
} else {
_hasForwardInTimeFiles = true;
}
}
@@ -942,10 +928,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
&& _discoveryMainThread->_dataFingerprint != databaseFingerprint) {
qDebug() << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint;
restoreOldFiles();
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2 && _account->serverVersionInt() < 0x090100) {
// The server before ownCloud 9.1 did not have the data-fingerprint property. So in that
// case we use heuristics to detect restored backup. This is disabled with newer version
// because this causes troubles to the user and is not as reliable as the data-fingerprint.
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2) {
qDebug() << "All the changes are bringing files in the past, asking the user";
// this typically happen when a backup is restored on the server
bool restore = false;
@@ -1180,7 +1163,7 @@ void SyncEngine::checkForPermission()
qDebug() << "Moving of " << (*it)->_file << " canceled because no permission to add parent folder";
}
(*it)->_instruction = CSYNC_INSTRUCTION_ERROR;
(*it)->_status = SyncFileItem::SoftError;
(*it)->_status = SyncFileItem::NormalError;
(*it)->_errorString = tr("Not allowed because you don't have permission to add parent folder");
}
+7 -7
Ver Arquivo
@@ -17,12 +17,12 @@
namespace OCC {
SyncFileStatus::SyncFileStatus()
:_tag(StatusNone), _shared(false)
:_tag(StatusNone), _sharedWithMe(false)
{
}
SyncFileStatus::SyncFileStatus(SyncFileStatusTag tag)
:_tag(tag), _shared(false)
:_tag(tag), _sharedWithMe(false)
{
}
@@ -37,14 +37,14 @@ SyncFileStatus::SyncFileStatusTag SyncFileStatus::tag() const
return _tag;
}
void SyncFileStatus::setShared(bool isShared)
void SyncFileStatus::setSharedWithMe(bool isShared)
{
_shared = isShared;
_sharedWithMe = isShared;
}
bool SyncFileStatus::shared() const
bool SyncFileStatus::sharedWithMe() const
{
return _shared;
return _sharedWithMe;
}
QString SyncFileStatus::toSocketAPIString() const
@@ -72,7 +72,7 @@ QString SyncFileStatus::toSocketAPIString() const
statusString = QLatin1String("ERROR");
break;
}
if(canBeShared && _shared) {
if(canBeShared && _sharedWithMe) {
statusString += QLatin1String("+SWM");
}
+4 -4
Ver Arquivo
@@ -43,18 +43,18 @@ public:
void set(SyncFileStatusTag tag);
SyncFileStatusTag tag() const;
void setShared( bool isShared );
bool shared() const;
void setSharedWithMe( bool isShared );
bool sharedWithMe() const;
QString toSocketAPIString() const;
private:
SyncFileStatusTag _tag;
bool _shared;
bool _sharedWithMe;
};
inline bool operator==(const SyncFileStatus &a, const SyncFileStatus &b) {
return a.tag() == b.tag() && a.shared() == b.shared();
return a.tag() == b.tag() && a.sharedWithMe() == b.sharedWithMe();
}
inline bool operator!=(const SyncFileStatus &a, const SyncFileStatus &b) {
+1 -1
Ver Arquivo
@@ -272,7 +272,7 @@ SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &r
}
if (isShared)
status.setShared(true);
status.setSharedWithMe(true);
return status;
}
-9
Ver Arquivo
@@ -331,15 +331,6 @@ bool SyncJournalDb::checkConnect()
qDebug() << Q_FUNC_INFO << "possibleUpgradeFromMirall_1_8_0_or_1 detected!";
forceRemoteDiscovery = true;
}
// There was a bug in versions <2.3.0 that could lead to stale
// local files and a remote discovery will fix them.
// See #5190 #5242.
if( major == 2 && minor < 3) {
qDebug() << Q_FUNC_INFO << "upgrade form client < 2.3.0 detected! forcing remote discovery";
forceRemoteDiscovery = true;
}
// Not comparing the BUILD id here, correct?
if( !(major == MIRALL_VERSION_MAJOR && minor == MIRALL_VERSION_MINOR && patch == MIRALL_VERSION_PATCH) ) {
createQuery.prepare("UPDATE version SET major=?1, minor=?2, patch =?3, custom=?4 "
-38
Ver Arquivo
@@ -16,7 +16,6 @@
#include "utility.h"
#include "version.h"
#include "configfile.h"
// Note: This file must compile without QtGui
#include <QCoreApplication>
@@ -587,41 +586,4 @@ void Utility::sortFilenames(QStringList& fileNames)
#endif
}
QUrl Utility::concatUrlPath(const QUrl &url, const QString &concatPath,
const QList< QPair<QString, QString> > &queryItems)
{
QString path = url.path();
if (! concatPath.isEmpty()) {
// avoid '//'
if (path.endsWith('/') && concatPath.startsWith('/')) {
path.chop(1);
} // avoid missing '/'
else if (!path.endsWith('/') && !concatPath.startsWith('/')) {
path += QLatin1Char('/');
}
path += concatPath; // put the complete path together
}
QUrl tmpUrl = url;
tmpUrl.setPath(path);
if( queryItems.size() > 0 ) {
tmpUrl.setQueryItems(queryItems);
}
return tmpUrl;
}
Q_GLOBAL_STATIC(QString, g_configFileName)
std::unique_ptr<QSettings> Utility::settingsWithGroup(const QString& group, QObject *parent)
{
if (g_configFileName()->isEmpty()) {
// cache file name
ConfigFile cfg;
*g_configFileName() = cfg.configFile();
}
std::unique_ptr<QSettings> settings(new QSettings(*g_configFileName(), QSettings::IniFormat, parent));
settings->beginGroup(group);
return settings;
}
} // namespace OCC
-12
Ver Arquivo
@@ -21,10 +21,6 @@
#include <QDateTime>
#include <QElapsedTimer>
#include <QMap>
#include <QUrl>
#include <memory>
class QSettings;
namespace OCC {
@@ -146,14 +142,6 @@ namespace Utility
*/
OWNCLOUDSYNC_EXPORT void sortFilenames(QStringList& fileNames);
/** Appends concatPath and queryItems to the url */
OWNCLOUDSYNC_EXPORT QUrl concatUrlPath(
const QUrl &url, const QString &concatPath,
const QList< QPair<QString, QString> > &queryItems = (QList<QPair<QString, QString>>()));
/** Returns a new settings pre-set in a specific group. The Settings will be created
with the given parent. If no parent is specified, the caller must destroy the settings */
OWNCLOUDSYNC_EXPORT std::unique_ptr<QSettings> settingsWithGroup(const QString& group, QObject* parent = 0);
}
/** @} */ // \addtogroup
-3
Ver Arquivo
@@ -27,9 +27,7 @@ ENDIF()
owncloud_add_test(FolderWatcher "${FolderWatcher_SRC}")
if( UNIX AND NOT APPLE )
if(HAVE_QT5 AND NOT BUILD_WITH_QT4)
owncloud_add_test(InotifyWatcher "${FolderWatcher_SRC}")
endif(HAVE_QT5 AND NOT BUILD_WITH_QT4)
endif(UNIX AND NOT APPLE)
owncloud_add_test(CSyncSqlite "")
@@ -47,7 +45,6 @@ owncloud_add_test(ExcludedFiles "")
if(HAVE_QT5 AND NOT BUILD_WITH_QT4)
owncloud_add_test(SyncEngine "syncenginetestutils.h")
owncloud_add_test(SyncFileStatusTracker "syncenginetestutils.h")
owncloud_add_test(ChunkingNg "syncenginetestutils.h")
endif(HAVE_QT5 AND NOT BUILD_WITH_QT4)
SET(FolderMan_SRC ../src/gui/folderman.cpp)
+34 -139
Ver Arquivo
@@ -18,20 +18,6 @@
#include <QtTest>
static const QUrl sRootUrl("owncloud://somehost/owncloud/remote.php/webdav/");
static const QUrl sRootUrl2("owncloud://somehost/owncloud/remote.php/dav/files/admin/");
static const QUrl sUploadUrl("owncloud://somehost/owncloud/remote.php/dav/uploads/admin/");
inline QString getFilePathFromUrl(const QUrl &url) {
QString path = url.path();
if (path.startsWith(sRootUrl.path()))
return path.mid(sRootUrl.path().length());
if (path.startsWith(sRootUrl2.path()))
return path.mid(sRootUrl2.path().length());
if (path.startsWith(sUploadUrl.path()))
return path.mid(sUploadUrl.path().length());
return {};
}
inline QString generateEtag() {
return QString::number(QDateTime::currentDateTime().toMSecsSinceEpoch(), 16);
@@ -82,15 +68,10 @@ public:
QFile file{_rootDir.filePath(relativePath)};
QVERIFY(!file.exists());
file.open(QFile::WriteOnly);
QByteArray buf(1024, contentChar);
for (int x = 0; x < size/buf.size(); ++x) {
file.write(buf);
}
file.write(buf.data(), size % buf.size());
file.write(QByteArray{}.fill(contentChar, size));
file.close();
// Set the mtime 30 seconds in the past, for some tests that need to make sure that the mtime differs.
OCC::FileSystem::setModTime(file.fileName(), OCC::Utility::qDateTimeToTime_t(QDateTime::currentDateTime().addSecs(-30)));
QCOMPARE(file.size(), size);
}
void setContents(const QString &relativePath, char contentChar) override {
QFile file{_rootDir.filePath(relativePath)};
@@ -263,7 +244,6 @@ public:
QDateTime lastModified = QDateTime::currentDateTime().addDays(-7);
QString etag = generateEtag();
QByteArray fileId = generateFileId();
QByteArray extraDavProperties;
qint64 size = 0;
char contentChar = 'W';
@@ -303,12 +283,6 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isNull()); // for root, it should be empty
const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
Q_ASSERT(fileInfo);
QString prefix = request.url().path().left(request.url().path().size() - fileName.size());
// Don't care about the request and just return a full propfind
const QString davUri{QStringLiteral("DAV:")};
const QString ocUri{QStringLiteral("http://owncloud.org/ns")};
@@ -322,7 +296,7 @@ public:
auto writeFileResponse = [&](const FileInfo &fileInfo) {
xml.writeStartElement(davUri, QStringLiteral("response"));
xml.writeTextElement(davUri, QStringLiteral("href"), prefix + fileInfo.path());
xml.writeTextElement(davUri, QStringLiteral("href"), "/owncloud/remote.php/webdav/" + fileInfo.path());
xml.writeStartElement(davUri, QStringLiteral("propstat"));
xml.writeStartElement(davUri, QStringLiteral("prop"));
@@ -340,13 +314,17 @@ public:
xml.writeTextElement(davUri, QStringLiteral("getetag"), fileInfo.etag);
xml.writeTextElement(ocUri, QStringLiteral("permissions"), fileInfo.isShared ? QStringLiteral("SRDNVCKW") : QStringLiteral("RDNVCKW"));
xml.writeTextElement(ocUri, QStringLiteral("id"), fileInfo.fileId);
buffer.write(fileInfo.extraDavProperties);
xml.writeEndElement(); // prop
xml.writeTextElement(davUri, QStringLiteral("status"), "HTTP/1.1 200 OK");
xml.writeEndElement(); // propstat
xml.writeEndElement(); // response
};
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
const FileInfo *fileInfo = remoteRootFileInfo.find(fileName);
Q_ASSERT(fileInfo);
writeFileResponse(*fileInfo);
foreach(const FileInfo &childFileInfo, fileInfo->children)
writeFileResponse(childFileInfo);
@@ -390,8 +368,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
if ((fileInfo = remoteRootFileInfo.find(fileName))) {
fileInfo->size = putPayload.size();
fileInfo->contentChar = putPayload.at(0);
@@ -408,7 +386,6 @@ public:
}
Q_INVOKABLE void respond() {
emit uploadProgress(fileInfo->size, fileInfo->size);
setRawHeader("OC-ETag", fileInfo->etag.toLatin1());
setRawHeader("ETag", fileInfo->etag.toLatin1());
setRawHeader("X-OC-MTime", "accepted"); // Prevents Q_ASSERT(!_runningNow) since we'll call PropagateItemJob::done twice in that case.
@@ -433,8 +410,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
fileInfo = remoteRootFileInfo.createDir(fileName);
if (!fileInfo) {
@@ -466,8 +443,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
remoteRootFileInfo.remove(fileName);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -493,10 +470,11 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
QString dest = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
Q_ASSERT(!dest.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
QString destPath = request.rawHeader("Destination");
Q_ASSERT(destPath.startsWith(sRootUrl.path()));
QString dest = destPath.mid(sRootUrl.path().length());
remoteRootFileInfo.rename(fileName, dest);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -525,8 +503,8 @@ public:
setOperation(op);
open(QIODevice::ReadOnly);
QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isEmpty());
Q_ASSERT(request.url().path().startsWith(sRootUrl.path()));
QString fileName = request.url().path().mid(sRootUrl.path().length());
fileInfo = remoteRootFileInfo.find(fileName);
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
@@ -555,79 +533,6 @@ public:
}
};
class FakeChunkMoveReply : public QNetworkReply
{
Q_OBJECT
FileInfo *fileInfo;
public:
FakeChunkMoveReply(FileInfo &uploadsFileInfo, FileInfo &remoteRootFileInfo,
QNetworkAccessManager::Operation op, const QNetworkRequest &request,
QObject *parent) : QNetworkReply{parent} {
setRequest(request);
setUrl(request.url());
setOperation(op);
open(QIODevice::ReadOnly);
QString source = getFilePathFromUrl(request.url());
Q_ASSERT(!source.isEmpty());
Q_ASSERT(source.endsWith("/.file"));
source = source.left(source.length() - qstrlen("/.file"));
auto sourceFolder = uploadsFileInfo.find(source);
Q_ASSERT(sourceFolder);
Q_ASSERT(sourceFolder->isDir);
int count = 0;
int size = 0;
char payload = '*';
do {
if (!sourceFolder->children.contains(QString::number(count)))
break;
auto &x = sourceFolder->children[QString::number(count)];
Q_ASSERT(!x.isDir);
Q_ASSERT(x.size > 0); // There should not be empty chunks
size += x.size;
payload = x.contentChar;
++count;
} while(true);
Q_ASSERT(count > 1); // There should be at least two chunks, otherwise why would we use chunking?
QCOMPARE(sourceFolder->children.count(), count); // There should not be holes or extra files
QString fileName = getFilePathFromUrl(QUrl::fromEncoded(request.rawHeader("Destination")));
Q_ASSERT(!fileName.isEmpty());
if ((fileInfo = remoteRootFileInfo.find(fileName))) {
QCOMPARE(request.rawHeader("If"), QByteArray("<" + request.rawHeader("Destination") + "> ([\"" + fileInfo->etag.toLatin1() + "\"])"));
fileInfo->size = size;
fileInfo->contentChar = payload;
} else {
Q_ASSERT(!request.hasRawHeader("If"));
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(fileName, size, payload);
}
if (!fileInfo) {
abort();
return;
}
QMetaObject::invokeMethod(this, "respond", Qt::QueuedConnection);
}
Q_INVOKABLE void respond() {
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 201);
setRawHeader("OC-ETag", fileInfo->etag.toLatin1());
setRawHeader("ETag", fileInfo->etag.toLatin1());
setRawHeader("OC-FileId", fileInfo->fileId);
emit metaDataChanged();
emit finished();
}
void abort() override { }
qint64 readData(char *, qint64) override { return 0; }
};
class FakeErrorReply : public QNetworkReply
{
Q_OBJECT
@@ -654,41 +559,33 @@ public:
class FakeQNAM : public QNetworkAccessManager
{
FileInfo _remoteRootFileInfo;
FileInfo _uploadFileInfo;
QStringList _errorPaths;
public:
FakeQNAM(FileInfo initialRoot) : _remoteRootFileInfo{std::move(initialRoot)} { }
FileInfo &currentRemoteState() { return _remoteRootFileInfo; }
FileInfo &uploadState() { return _uploadFileInfo; }
QStringList &errorPaths() { return _errorPaths; }
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
QIODevice *outgoingData = 0) {
const QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isNull());
const QString fileName = request.url().path().mid(sRootUrl.path().length());
if (_errorPaths.contains(fileName))
return new FakeErrorReply{op, request, this};
bool isUpload = request.url().path().startsWith(sUploadUrl.path());
FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo;
auto verb = request.attribute(QNetworkRequest::CustomVerbAttribute);
if (verb == QLatin1String("PROPFIND"))
// Ignore outgoingData always returning somethign good enough, works for now.
return new FakePropfindReply{info, op, request, this};
return new FakePropfindReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("GET"))
return new FakeGetReply{info, op, request, this};
return new FakeGetReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("PUT"))
return new FakePutReply{info, op, request, outgoingData->readAll(), this};
return new FakePutReply{_remoteRootFileInfo, op, request, outgoingData->readAll(), this};
else if (verb == QLatin1String("MKCOL"))
return new FakeMkcolReply{info, op, request, this};
return new FakeMkcolReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("DELETE"))
return new FakeDeleteReply{info, op, request, this};
else if (verb == QLatin1String("MOVE") && !isUpload)
return new FakeMoveReply{info, op, request, this};
else if (verb == QLatin1String("MOVE") && isUpload)
return new FakeChunkMoveReply{info, _remoteRootFileInfo, op, request, this};
return new FakeDeleteReply{_remoteRootFileInfo, op, request, this};
else if (verb == QLatin1String("MOVE"))
return new FakeMoveReply{_remoteRootFileInfo, op, request, this};
else {
qDebug() << verb << outgoingData;
Q_UNREACHABLE();
@@ -701,6 +598,7 @@ class FakeCredentials : public OCC::AbstractCredentials
QNetworkAccessManager *_qnam;
public:
FakeCredentials(QNetworkAccessManager *qnam) : _qnam{qnam} { }
virtual bool changed(AbstractCredentials *) const { return false; }
virtual QString authType() const { return "test"; }
virtual QString user() const { return "admin"; }
virtual QNetworkAccessManager* getQNAM() const { return _qnam; }
@@ -751,7 +649,7 @@ public:
OCC::SyncEngine &syncEngine() const { return *_syncEngine; }
FileModifier &localModifier() { return _localModifier; }
FileInfo &remoteModifier() { return _fakeQnam->currentRemoteState(); }
FileModifier &remoteModifier() { return _fakeQnam->currentRemoteState(); }
FileInfo currentLocalState() {
QDir rootDir{_tempDir.path()};
FileInfo rootTemplate;
@@ -760,7 +658,6 @@ public:
}
FileInfo currentRemoteState() { return _fakeQnam->currentRemoteState(); }
FileInfo &uploadState() { return _fakeQnam->uploadState(); }
QStringList &serverErrorPaths() { return _fakeQnam->errorPaths(); }
@@ -797,16 +694,14 @@ public:
QVERIFY(false);
}
bool execUntilFinished() {
void execUntilFinished() {
QSignalSpy spy(_syncEngine.get(), SIGNAL(finished(bool)));
bool ok = spy.wait(60000);
Q_ASSERT(ok && "Sync timed out");
return spy[0][0].toBool();
QVERIFY(spy.wait());
}
bool syncOnce() {
void syncOnce() {
scheduleSync();
return execUntilFinished();
execUntilFinished();
}
private:
-78
Ver Arquivo
@@ -1,78 +0,0 @@
/*
* This software is in the public domain, furnished "as is", without technical
* support, and with no warranty, express or implied, as to its usefulness for
* any purpose.
*
*/
#include <QtTest>
#include "syncenginetestutils.h"
#include <syncengine.h>
using namespace OCC;
class TestChunkingNG : public QObject
{
Q_OBJECT
private slots:
void testFileUpload() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
const int size = 300 * 1000 * 1000; // 300 MB
fakeFolder.localModifier().insert("A/a0", size);
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
// Check that another upload of the same file also work.
fakeFolder.localModifier().appendByte("A/a0");
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 2); // the transfer was done with chunking
}
void testResume () {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"chunking", "1.0"} } } });
const int size = 300 * 1000 * 1000; // 300 MB
fakeFolder.localModifier().insert("A/a0", size);
// Abort when the upload is at 1/3
int sizeWhenAbort = -1;
auto con = QObject::connect(&fakeFolder.syncEngine(), &SyncEngine::transmissionProgress,
[&](const ProgressInfo &progress) {
if (progress.completedSize() > (progress.totalSize() /3 )) {
sizeWhenAbort = progress.completedSize();
fakeFolder.syncEngine().abort();
}
});
QVERIFY(!fakeFolder.syncOnce()); // there should have been an error
QObject::disconnect(con);
QVERIFY(sizeWhenAbort > 0);
QVERIFY(sizeWhenAbort < size);
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // the transfer was done with chunking
auto upStateChildren = fakeFolder.uploadState().children.first().children;
QCOMPARE(sizeWhenAbort, std::accumulate(upStateChildren.cbegin(), upStateChildren.cend(), 0,
[](int s, const FileInfo &i) { return s + i.size; }));
// Add a fake file to make sure it gets deleted
fakeFolder.uploadState().children.first().insert("10000", size);
QVERIFY(fakeFolder.syncOnce());
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
QCOMPARE(fakeFolder.uploadState().children.count(), 1); // The same chunk id was re-used
QCOMPARE(fakeFolder.currentRemoteState().find("A/a0")->size, size);
}
};
QTEST_GUILESS_MAIN(TestChunkingNG)
#include "testchunkingng.moc"
+1 -1
Ver Arquivo
@@ -50,7 +50,7 @@ private slots:
QFETCH(QueryItems, query);
QFETCH(QString, expected);
QUrl baseUrl("http://example.com" + base);
QUrl resultUrl = Utility::concatUrlPath(baseUrl, concat, query);
QUrl resultUrl = Account::concatUrlPath(baseUrl, concat, query);
QString result = QString::fromUtf8(resultUrl.toEncoded());
QString expectedFull = "http://example.com" + expected;
QCOMPARE(result, expectedFull);
+1 -7
Ver Arquivo
@@ -371,15 +371,11 @@ private slots:
void sharedStatus() {
SyncFileStatus sharedUpToDateStatus(SyncFileStatus::StatusUpToDate);
sharedUpToDateStatus.setShared(true);
sharedUpToDateStatus.setSharedWithMe(true);
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
fakeFolder.remoteModifier().insert("S/s0");
fakeFolder.remoteModifier().appendByte("S/s1");
fakeFolder.remoteModifier().insert("B/b3");
fakeFolder.remoteModifier().find("B/b3")->extraDavProperties
= "<oc:share-types><oc:share-type>0</oc:share-type></oc:share-types>";
StatusPushSpy statusSpy(fakeFolder.syncEngine());
fakeFolder.scheduleSync();
@@ -399,8 +395,6 @@ private slots:
QEXPECT_FAIL("", "We currently only know if a new file is shared on the second sync, after a PROPFIND.", Continue);
QCOMPARE(statusSpy.statusOf("S/s0"), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf("S/s1"), sharedUpToDateStatus);
QCOMPARE(statusSpy.statusOf("B/b1").shared(), false);
QCOMPARE(statusSpy.statusOf("B/b3"), sharedUpToDateStatus);
QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
}
+1 -1
Ver Arquivo
@@ -80,7 +80,7 @@ private slots:
quint64 sec = 1000;
quint64 hour = 3600 * sec;
QDateTime current = QDateTime::currentDateTimeUtc();
QDateTime current = QDateTime::currentDateTime();
QCOMPARE(durationToDescriptiveString2(0), QString("0 second(s)") );
QCOMPARE(durationToDescriptiveString2(5), QString("0 second(s)") );
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais