Comparar commits
62 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 0e36a25b31 | |||
| acd151102c | |||
| 77e790d2ee | |||
| 899c675f0a | |||
| fb76487e76 | |||
| 0c0a3ca0a5 | |||
| 676ad530e7 | |||
| fbcb01ed27 | |||
| c2b006e857 | |||
| 0e7ccea588 | |||
| c16deb3e44 | |||
| 019544fcfd | |||
| 5f0167b19e | |||
| 29d3c33eb2 | |||
| 469eca1f5f | |||
| 0864d67a9a | |||
| e9503664f5 | |||
| 58cee61624 | |||
| d23d07f99b | |||
| d70db810dd | |||
| 842129f99d | |||
| 36d61ef3a9 | |||
| f1f27221a7 | |||
| eb012d26ee | |||
| 9ee8187083 | |||
| b0c45cfc89 | |||
| 90cea69692 | |||
| 155bdfbffe | |||
| 0e2782d369 | |||
| 6b3d0e69aa | |||
| f30aee6d4e | |||
| 82f86eb019 | |||
| 9573c5e64d | |||
| f5aff70398 | |||
| e3a4c3989a | |||
| 3e954bc17f | |||
| 2527569bb8 | |||
| 1e197531cb | |||
| 8b876576eb | |||
| a2287c9657 | |||
| 34c59ba9ed | |||
| c110745330 | |||
| 19521863a0 | |||
| c256896165 | |||
| ef922f60fa | |||
| 733ea90e6b | |||
| a4e1fa9dcf | |||
| 1a1684ca2a | |||
| 9c8572e335 | |||
| e675a34fbb | |||
| 5f47c01346 | |||
| 10644d3568 | |||
| 9ee3144358 | |||
| cf48ea2e00 | |||
| 6026148692 | |||
| a2222228c9 | |||
| 9e2d3f5bc7 | |||
| e6be670e49 | |||
| 46ce2f4722 | |||
| 166a0f60ca | |||
| a9019ccbad | |||
| 0a4806af44 |
+1
-1
@@ -27,7 +27,7 @@ https://github.com/owncloud/client.
|
||||
|
||||
## Building the source code
|
||||
|
||||
[Building the Client](http://doc.owncloud.org/desktop/2.0/building.html)
|
||||
[Building the Client](http://doc.owncloud.org/desktop/2.2/building.html)
|
||||
in the ownCloud Desktop Client manual.
|
||||
|
||||
## Maintainers and Contributors
|
||||
|
||||
@@ -9,6 +9,7 @@ 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}"
|
||||
@@ -37,7 +38,6 @@ 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!"
|
||||
|
||||
@@ -32,7 +32,7 @@ find_library(CMOCKA_LIBRARY
|
||||
NAMES
|
||||
cmocka
|
||||
PATHS
|
||||
${CMOCKA_ROOT_DIR}/include
|
||||
${CMOCKA_ROOT_DIR}/lib
|
||||
)
|
||||
|
||||
if (CMOCKA_LIBRARY)
|
||||
|
||||
@@ -34,6 +34,8 @@ if( Qt5Core_FOUND )
|
||||
message(FATAL_ERROR "Qt5WebKit required for Shibboleth. Use -DNO_SHIBBOLETH=1 to disable it.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(Qt5WebSockets)
|
||||
endif()
|
||||
|
||||
else( Qt5Core_FOUND )
|
||||
@@ -67,6 +69,12 @@ if( Qt5Core_FOUND )
|
||||
set (QT_LIBRARIES ${QT_LIBRARIES} ${Qt5MacExtras_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(Qt5WebSockets_FOUND)
|
||||
message(STATUS "Building with websocket notify support")
|
||||
include_directories(${Qt5WebSockets_INCLUDE_DIRS})
|
||||
set (QT_LIBRARIES ${QT_LIBRARIES} ${Qt5WebSockets_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(NOT BUILD_LIBRARIES_ONLY)
|
||||
macro(qt_wrap_ui)
|
||||
qt5_wrap_ui(${ARGN})
|
||||
|
||||
@@ -65,7 +65,21 @@ 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
|
||||
|
||||
@@ -158,7 +158,19 @@ 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;
|
||||
|
||||
@@ -232,6 +232,37 @@ 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();
|
||||
|
||||
|
||||
|
||||
+16
-2
@@ -66,11 +66,18 @@ recipes.
|
||||
|
||||
To set up your build environment for development using HomeBrew_:
|
||||
|
||||
1. Add the ownCloud repository using the following command::
|
||||
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::
|
||||
|
||||
brew tap owncloud/owncloud
|
||||
|
||||
2. Install any missing dependencies::
|
||||
5. Install any missing dependencies::
|
||||
|
||||
brew install $(brew deps owncloud-client)
|
||||
|
||||
@@ -80,6 +87,9 @@ 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`.
|
||||
|
||||
@@ -234,6 +244,10 @@ 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``.
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
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.
|
||||
+83
-4
@@ -415,6 +415,84 @@ 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
|
||||
Comment[oc]=@APPLICATION_NAME@ sincronizacion del client
|
||||
GenericName[oc]=Dorsièr de Sincronizacion
|
||||
@@ -505,10 +583,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]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
|
||||
GenericName[ru]=Синхронизация папки
|
||||
Name[ru]=Настольный клиент синхронизации @НАЗВАНИЕ_ПРИЛОЖЕНИЯ@
|
||||
Icon[ru]=@ВЫПОЛНЯЕМОЕ_ПРИЛОЖЕНИЕ@
|
||||
Comment[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
|
||||
GenericName[ru]=Синхронизация каталогов
|
||||
Name[ru]=Настольный клиент синхронизации @APPLICATION_NAME@
|
||||
Icon[ru]=@APPLICATION_EXECUTABLE@
|
||||
Comment[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem
|
||||
GenericName[sl]=Usklajevanje map
|
||||
Name[sl]=@APPLICATION_NAME@ ‒ Program za usklajevanje datotek z namizjem
|
||||
@@ -550,6 +628,7 @@ 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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import os
|
||||
import urllib
|
||||
import socket
|
||||
import tempfile
|
||||
|
||||
from gi.repository import GObject, Nautilus
|
||||
|
||||
@@ -43,7 +44,7 @@ def get_runtime_dir():
|
||||
try:
|
||||
return os.environ['XDG_RUNTIME_DIR']
|
||||
except KeyError:
|
||||
fallback = '/tmp/runtime-' + os.environ['USER']
|
||||
fallback = os.path.join(tempfile.gettempdir(), 'runtime-' + os.environ['USER'])
|
||||
return fallback
|
||||
|
||||
|
||||
@@ -86,22 +87,17 @@ class SocketConnect(GObject.GObject):
|
||||
def _connectToSocketServer(self):
|
||||
try:
|
||||
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
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)
|
||||
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))
|
||||
except Exception as e: # Bad habbit
|
||||
print("Connect could not be established, try again later.")
|
||||
self._sock.close()
|
||||
@@ -157,32 +153,65 @@ 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 = []
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# If it is neither in a synced folder or is a directory
|
||||
if not syncedFile:
|
||||
# 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:
|
||||
return items
|
||||
|
||||
# Create a menu item
|
||||
@@ -303,7 +332,7 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
|
||||
|
||||
filename = get_local_path(item.get_uri())
|
||||
if item.is_directory():
|
||||
filename += '/'
|
||||
filename += os.sep
|
||||
|
||||
inScope = False
|
||||
for reg_path in socketConnect.registered_paths:
|
||||
|
||||
@@ -199,6 +199,9 @@ 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
|
||||
@@ -206,6 +209,7 @@ set( final_src
|
||||
${client_SRCS}
|
||||
${client_UI_SRCS}
|
||||
${client_version}
|
||||
${client_manifest}
|
||||
${guiMoc}
|
||||
${client_I18N}
|
||||
${3rdparty_SRC}
|
||||
|
||||
@@ -44,7 +44,7 @@ AccountManager *AccountManager::instance()
|
||||
|
||||
bool AccountManager::restore()
|
||||
{
|
||||
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
|
||||
|
||||
// If there are no accounts, check the old format.
|
||||
if (settings->childGroups().isEmpty()
|
||||
@@ -69,9 +69,7 @@ bool AccountManager::restore()
|
||||
bool AccountManager::restoreFromLegacySettings()
|
||||
{
|
||||
// try to open the correctly themed settings
|
||||
auto settings = Account::settingsWithGroup(Theme::instance()->appName());
|
||||
|
||||
bool migratedCreds = false;
|
||||
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
|
||||
|
||||
// if the settings file could not be opened, the childKeys list is empty
|
||||
// then try to load settings from a very old place
|
||||
@@ -102,7 +100,6 @@ 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;
|
||||
@@ -114,9 +111,6 @@ 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;
|
||||
}
|
||||
@@ -126,7 +120,7 @@ bool AccountManager::restoreFromLegacySettings()
|
||||
|
||||
void AccountManager::save(bool saveCredentials)
|
||||
{
|
||||
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
|
||||
settings->setValue(QLatin1String(versionC), 2);
|
||||
foreach (const auto &acc, _accounts) {
|
||||
settings->beginGroup(acc->account()->id());
|
||||
@@ -142,7 +136,7 @@ void AccountManager::save(bool saveCredentials)
|
||||
void AccountManager::saveAccount(Account* a)
|
||||
{
|
||||
qDebug() << "Saving account" << a->url().toString();
|
||||
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
|
||||
settings->beginGroup(a->id());
|
||||
saveAccountHelper(a, *settings, false); // don't save credentials they might not have been loaded yet
|
||||
settings->endGroup();
|
||||
@@ -154,7 +148,7 @@ void AccountManager::saveAccount(Account* a)
|
||||
void AccountManager::saveAccountState(AccountState* a)
|
||||
{
|
||||
qDebug() << "Saving account state" << a->account()->url().toString();
|
||||
auto settings = Account::settingsWithGroup(QLatin1String(accountsC));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
|
||||
settings->beginGroup(a->account()->id());
|
||||
a->writeToSettings(*settings);
|
||||
settings->endGroup();
|
||||
@@ -280,7 +274,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 = Account::settingsWithGroup(QLatin1String(accountsC));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String(accountsC));
|
||||
settings->remove(account->account()->id());
|
||||
|
||||
emit accountRemoved(account);
|
||||
|
||||
@@ -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->slotScheduleAllFolders();
|
||||
folderMan->scheduleAllFolders();
|
||||
emit folderChanged();
|
||||
}
|
||||
}
|
||||
@@ -360,7 +360,7 @@ void AccountSettings::slotRemoveCurrentFolder()
|
||||
return;
|
||||
}
|
||||
|
||||
folderMan->slotRemoveFolder( folderMan->folder(alias) );
|
||||
folderMan->removeFolder( 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->slotScheduleSync(folderMan->folder(alias));
|
||||
folderMan->scheduleFolder(folderMan->folder(alias));
|
||||
}
|
||||
|
||||
void AccountSettings::slotOpenOC()
|
||||
|
||||
@@ -306,7 +306,7 @@ void AccountState::slotCredentialsAsked(AbstractCredentials* credentials)
|
||||
|
||||
std::unique_ptr<QSettings> AccountState::settings()
|
||||
{
|
||||
auto s = _account->settingsWithGroup(QLatin1String("Accounts"));
|
||||
auto s = Utility::settingsWithGroup(QLatin1String("Accounts"));
|
||||
s->beginGroup(_account->id());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -79,21 +79,6 @@ 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");
|
||||
@@ -142,7 +127,7 @@ void ShibbolethCredentials::fetchFromKeychain()
|
||||
} else {
|
||||
_url = _account->url();
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
job->setSettings(Utility::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*)));
|
||||
@@ -261,7 +246,7 @@ void ShibbolethCredentials::slotReadJobDone(QKeychain::Job *job)
|
||||
addToCookieJar(_shibCookie);
|
||||
}
|
||||
// access
|
||||
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
|
||||
_ready = true;
|
||||
_stillValid = true;
|
||||
@@ -320,7 +305,7 @@ QByteArray ShibbolethCredentials::shibCookieName()
|
||||
void ShibbolethCredentials::storeShibCookie(const QNetworkCookie &cookie)
|
||||
{
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
job->setSettings(Utility::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"));
|
||||
@@ -331,7 +316,7 @@ void ShibbolethCredentials::storeShibCookie(const QNetworkCookie &cookie)
|
||||
void ShibbolethCredentials::removeShibCookie()
|
||||
{
|
||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||
job->setSettings(_account->settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
job->setSettings(Utility::settingsWithGroup(Theme::instance()->appName(), job).release());
|
||||
job->setKey(keychainKey(_account->url().toString(), "shibAssertion"));
|
||||
job->start();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ 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;
|
||||
|
||||
+80
-50
@@ -33,6 +33,10 @@
|
||||
#include "filesystem.h"
|
||||
#include "excludedfiles.h"
|
||||
|
||||
// FIXME ifdef
|
||||
#include "json.h"
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
||||
#include <QDebug>
|
||||
@@ -58,7 +62,6 @@ Folder::Folder(const FolderDefinition& definition,
|
||||
, _wipeDb(false)
|
||||
, _proxyDirty(true)
|
||||
, _lastSyncDuration(0)
|
||||
, _forceSyncOnPollTimeout(false)
|
||||
, _consecutiveFailingSyncs(0)
|
||||
, _consecutiveFollowUpSyncs(0)
|
||||
, _journal(definition.localPath)
|
||||
@@ -112,6 +115,28 @@ 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()));
|
||||
|
||||
_webSocket.reset(new QWebSocket());
|
||||
// FIXME: Also handle re-connect
|
||||
QObject::connect(_webSocket.data(), &QWebSocket::connected, this, &Folder::slotWebSocketConnected);
|
||||
QObject::connect(_webSocket.data(), &QWebSocket::textMessageReceived,
|
||||
this, &Folder::slotWebSocketTextMessageReceived);
|
||||
// FIXME: Accept same ssl errors as normal server?
|
||||
// typedef void (QWebSocket:: *sslErrorsSignal)(const QList<QSslError> &);
|
||||
// connect(&m_webSocket, static_cast<sslErrorsSignal>(&QWebSocket::sslErrors),
|
||||
// this, &SslEchoClient::onSslErrors);
|
||||
// FIXME something with _accountState->account()->davUrl()
|
||||
// FIXME: Check env / capabilities
|
||||
QByteArray wsUrl = qgetenv("OWNCLOUD_WEBSOCKET_URL");
|
||||
if (wsUrl.length() > 0) {
|
||||
qDebug() << "Connecting to websocket " << wsUrl;
|
||||
_webSocket->open(QUrl::fromEncoded(wsUrl));
|
||||
}
|
||||
}
|
||||
|
||||
Folder::~Folder()
|
||||
@@ -221,7 +246,7 @@ QString Folder::remotePath() const
|
||||
|
||||
QUrl Folder::remoteUrl() const
|
||||
{
|
||||
return Account::concatUrlPath(_accountState->account()->davUrl(), remotePath());
|
||||
return Utility::concatUrlPath(_accountState->account()->davUrl(), remotePath());
|
||||
}
|
||||
|
||||
bool Folder::syncPaused() const
|
||||
@@ -275,7 +300,7 @@ void Folder::slotRunEtagJob()
|
||||
|
||||
AccountPtr account = _accountState->account();
|
||||
|
||||
if (!_requestEtagJob.isNull()) {
|
||||
if (_requestEtagJob) {
|
||||
qDebug() << Q_FUNC_INFO << remoteUrl().toString() << "has ETag job queued, not trying to sync";
|
||||
return;
|
||||
}
|
||||
@@ -285,46 +310,41 @@ void Folder::slotRunEtagJob()
|
||||
return;
|
||||
}
|
||||
|
||||
bool forceSyncIntervalExpired =
|
||||
quint64(_timeSinceLastSyncDone.elapsed()) > ConfigFile().forceSyncInterval();
|
||||
bool syncAgainAfterFail = _consecutiveFailingSyncs > 0 && _consecutiveFailingSyncs < 3;
|
||||
// Do the ordinary etag check for the root folder and schedule a
|
||||
// sync if it's different.
|
||||
|
||||
// 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) {
|
||||
_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.
|
||||
}
|
||||
|
||||
if (forceSyncIntervalExpired) {
|
||||
qDebug() << "** Force Sync, because it has been " << _timeSinceLastSyncDone.elapsed() << "ms "
|
||||
<< "since the last sync";
|
||||
void Folder::slotWebSocketConnected()
|
||||
{
|
||||
qDebug() << "CONNECTED";
|
||||
}
|
||||
|
||||
// FIXME: Can we always assume it's a message? the docs are unclear
|
||||
void Folder::slotWebSocketTextMessageReceived(QString message)
|
||||
{
|
||||
qDebug() << message;
|
||||
if (!canSync() || isBusy()) {
|
||||
return;
|
||||
}
|
||||
bool success;
|
||||
QVariantMap json = QtJson::parse(message, success).toMap();
|
||||
if (success) {
|
||||
QString notificationEtag = json["etag"].toString();
|
||||
QString notificationUser = json["user"].toString();
|
||||
QString user = _accountState->account()->credentials()->user();
|
||||
if (user == notificationUser && _lastEtag != notificationEtag) {
|
||||
qDebug() << "* [WebSocket] Compare etag with previous etag: last:" << _lastEtag << ", received:" << notificationEtag << "-> CHANGED";
|
||||
_lastEtag = notificationEtag;
|
||||
slotScheduleThisFolder();
|
||||
_accountState->tagLastSuccessfullETagRequest();
|
||||
}
|
||||
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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +358,7 @@ void Folder::etagRetreived(const QString& etag)
|
||||
if (_lastEtag != etag) {
|
||||
qDebug() << "* Compare etag with previous etag: last:" << _lastEtag << ", received:" << etag << "-> CHANGED";
|
||||
_lastEtag = etag;
|
||||
emit scheduleToSync(this);
|
||||
slotScheduleThisFolder();
|
||||
}
|
||||
|
||||
_accountState->tagLastSuccessfullETagRequest();
|
||||
@@ -598,7 +618,10 @@ void Folder::slotWatchedPathChanged(const QString& path)
|
||||
}
|
||||
|
||||
emit watchedFileChangedExternally(path);
|
||||
emit scheduleToSync(this);
|
||||
|
||||
// Also schedule this folder for a sync, but only after some delay:
|
||||
// The sync will not upload files that were changed too recently.
|
||||
scheduleThisFolderSoon();
|
||||
}
|
||||
|
||||
void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
|
||||
@@ -879,11 +902,10 @@ void Folder::slotSyncFinished(bool success)
|
||||
// Maybe force a follow-up sync to take place, but only a couple of times.
|
||||
if (anotherSyncNeeded && _consecutiveFollowUpSyncs <= 3)
|
||||
{
|
||||
_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() ));
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -964,7 +986,17 @@ 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)
|
||||
{
|
||||
@@ -988,10 +1020,8 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
|
||||
*cancel = msgBox.clickedButton() == keepBtn;
|
||||
if (*cancel) {
|
||||
wipe();
|
||||
// speed up next sync
|
||||
_lastEtag.clear();
|
||||
_forceSyncOnPollTimeout = true;
|
||||
QTimer::singleShot(50, this, SLOT(slotRunEtagJob()));
|
||||
slotScheduleThisFolder();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-2
@@ -27,6 +27,8 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
|
||||
|
||||
class QThread;
|
||||
class QSettings;
|
||||
@@ -178,6 +180,7 @@ 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;
|
||||
@@ -194,11 +197,21 @@ 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);
|
||||
@@ -258,6 +271,9 @@ private slots:
|
||||
void etagRetreived(const QString &);
|
||||
void etagRetreivedFromSyncEngine(const QString &);
|
||||
|
||||
void slotWebSocketConnected();
|
||||
void slotWebSocketTextMessageReceived(QString message);
|
||||
|
||||
void slotThreadTreeWalkResult(const SyncFileItemVector& ); // after sync is done
|
||||
|
||||
void slotEmitFinishedDelayed();
|
||||
@@ -266,6 +282,11 @@ private slots:
|
||||
|
||||
void slotLogPropagationStart();
|
||||
|
||||
/** Adds this folder to the list of scheduled folders in the
|
||||
* FolderMan.
|
||||
*/
|
||||
void slotScheduleThisFolder();
|
||||
|
||||
private:
|
||||
bool setIgnoredFiles();
|
||||
|
||||
@@ -298,11 +319,12 @@ private:
|
||||
bool _wipeDb;
|
||||
bool _proxyDirty;
|
||||
QPointer<RequestEtagJob> _requestEtagJob;
|
||||
// FIXME: ifdef etc
|
||||
QScopedPointer<QWebSocket> _webSocket;
|
||||
QString _lastEtag;
|
||||
QElapsedTimer _timeSinceLastSyncDone;
|
||||
QElapsedTimer _timeSinceLastSyncStart;
|
||||
qint64 _lastSyncDuration;
|
||||
bool _forceSyncOnPollTimeout;
|
||||
|
||||
/// The number of syncs that failed in a row.
|
||||
/// Reset when a sync is successful.
|
||||
@@ -317,6 +339,8 @@ private:
|
||||
ClientProxy _clientProxy;
|
||||
|
||||
QScopedPointer<SyncRunFileLog> _fileLog;
|
||||
|
||||
QTimer _scheduleSelfTimer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+79
-45
@@ -64,11 +64,17 @@ 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(slotScheduleFolderOwningFile(QString)));
|
||||
SLOT(slotWatchedFileUnlocked(QString)));
|
||||
}
|
||||
|
||||
FolderMan *FolderMan::instance()
|
||||
@@ -100,8 +106,6 @@ 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)),
|
||||
@@ -131,7 +135,7 @@ int FolderMan::unloadAndDeleteAllFolders()
|
||||
}
|
||||
_lastSyncFolder = 0;
|
||||
_currentSyncFolder = 0;
|
||||
_scheduleQueue.clear();
|
||||
_scheduledFolders.clear();
|
||||
emit scheduleQueueChanged();
|
||||
|
||||
Q_ASSERT(_folderMap.count() == 0);
|
||||
@@ -187,7 +191,7 @@ int FolderMan::setupFolders()
|
||||
{
|
||||
unloadAndDeleteAllFolders();
|
||||
|
||||
auto settings = Account::settingsWithGroup(QLatin1String("Accounts"));
|
||||
auto settings = Utility::settingsWithGroup(QLatin1String("Accounts"));
|
||||
const auto accountsWithSettings = settings->childGroups();
|
||||
if (accountsWithSettings.isEmpty()) {
|
||||
int r = setupFoldersMigration();
|
||||
@@ -211,7 +215,7 @@ int FolderMan::setupFolders()
|
||||
if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) {
|
||||
Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
|
||||
if (f) {
|
||||
slotScheduleSync(f);
|
||||
scheduleFolder(f);
|
||||
emit folderSyncStateChange(f);
|
||||
}
|
||||
}
|
||||
@@ -244,7 +248,7 @@ int FolderMan::setupFoldersMigration()
|
||||
foreach ( const QString& alias, list ) {
|
||||
Folder *f = setupFolderFromOldConfigFile( alias, accountState );
|
||||
if( f ) {
|
||||
slotScheduleSync(f);
|
||||
scheduleFolder(f);
|
||||
emit folderSyncStateChange(f);
|
||||
}
|
||||
}
|
||||
@@ -422,7 +426,7 @@ void FolderMan::slotFolderSyncPaused( Folder *f, bool paused )
|
||||
|
||||
if (!paused) {
|
||||
_disabledFolders.remove(f);
|
||||
slotScheduleSync(f);
|
||||
scheduleFolder(f);
|
||||
} else {
|
||||
_disabledFolders.insert(f);
|
||||
}
|
||||
@@ -462,11 +466,11 @@ Folder *FolderMan::folder( const QString& alias )
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FolderMan::slotScheduleAllFolders()
|
||||
void FolderMan::scheduleAllFolders()
|
||||
{
|
||||
foreach( Folder *f, _folderMap.values() ) {
|
||||
if (f && f->canSync()) {
|
||||
slotScheduleSync( f );
|
||||
scheduleFolder( f );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,7 +490,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::slotScheduleSync( Folder *f )
|
||||
void FolderMan::scheduleFolder( Folder *f )
|
||||
{
|
||||
if( !f ) {
|
||||
qWarning() << "slotScheduleSync called with null folder";
|
||||
@@ -496,7 +500,7 @@ void FolderMan::slotScheduleSync( Folder *f )
|
||||
|
||||
qDebug() << "Schedule folder " << alias << " to sync!";
|
||||
|
||||
if( ! _scheduleQueue.contains(f) ) {
|
||||
if( ! _scheduledFolders.contains(f) ) {
|
||||
if( !f->canSync() ) {
|
||||
qDebug() << "Folder is not ready to sync, not scheduled!";
|
||||
_socketApi->slotUpdateFolderView(f);
|
||||
@@ -504,7 +508,7 @@ void FolderMan::slotScheduleSync( Folder *f )
|
||||
}
|
||||
f->prepareToSync();
|
||||
emit folderSyncStateChange(f);
|
||||
_scheduleQueue.enqueue(f);
|
||||
_scheduledFolders.enqueue(f);
|
||||
emit scheduleQueueChanged();
|
||||
} else {
|
||||
qDebug() << " II> Sync for folder " << alias << " already scheduled, do not enqueue!";
|
||||
@@ -568,7 +572,7 @@ void FolderMan::slotAccountStateChanged()
|
||||
if (f
|
||||
&& f->canSync()
|
||||
&& f->accountState() == accountState) {
|
||||
slotScheduleSync(f);
|
||||
scheduleFolder(f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -580,7 +584,7 @@ void FolderMan::slotAccountStateChanged()
|
||||
_currentSyncFolder->slotTerminateSync();
|
||||
}
|
||||
|
||||
QMutableListIterator<Folder*> it(_scheduleQueue);
|
||||
QMutableListIterator<Folder*> it(_scheduledFolders);
|
||||
while (it.hasNext()) {
|
||||
Folder* f = it.next();
|
||||
if (f->accountState() == accountState) {
|
||||
@@ -595,7 +599,7 @@ void FolderMan::slotAccountStateChanged()
|
||||
// this is not the same as Pause and Resume of folders.
|
||||
void FolderMan::setSyncEnabled( bool enabled )
|
||||
{
|
||||
if (!_syncEnabled && enabled && !_scheduleQueue.isEmpty()) {
|
||||
if (!_syncEnabled && enabled && !_scheduledFolders.isEmpty()) {
|
||||
// We have things in our queue that were waiting for the connection to come back on.
|
||||
startScheduledSyncSoon();
|
||||
}
|
||||
@@ -604,19 +608,19 @@ void FolderMan::setSyncEnabled( bool enabled )
|
||||
emit( folderSyncStateChange(0) );
|
||||
}
|
||||
|
||||
void FolderMan::startScheduledSyncSoon(qint64 msMinimumDelay)
|
||||
void FolderMan::startScheduledSyncSoon()
|
||||
{
|
||||
if (_startScheduledSyncTimer.isActive()) {
|
||||
return;
|
||||
}
|
||||
if (_scheduleQueue.empty()) {
|
||||
if (_scheduledFolders.empty()) {
|
||||
return;
|
||||
}
|
||||
if (_currentSyncFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 msDelay = msMinimumDelay;
|
||||
qint64 msDelay = 100; // 100ms minimum delay
|
||||
qint64 msSinceLastSync = 0;
|
||||
|
||||
// Require a pause based on the duration of the last sync run.
|
||||
@@ -631,15 +635,6 @@ void FolderMan::startScheduledSyncSoon(qint64 msMinimumDelay)
|
||||
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!
|
||||
@@ -648,11 +643,7 @@ void FolderMan::startScheduledSyncSoon(qint64 msMinimumDelay)
|
||||
// Time since the last sync run counts against the delay
|
||||
msDelay = qMax(1ll, msDelay - msSinceLastSync);
|
||||
|
||||
// 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";
|
||||
qDebug() << "Starting the next scheduled sync in" << (msDelay/1000) << "seconds";
|
||||
_startScheduledSyncTimer.start(msDelay);
|
||||
}
|
||||
|
||||
@@ -673,15 +664,15 @@ void FolderMan::slotStartScheduledFolderSync()
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "XX slotScheduleFolderSync: folderQueue size: " << _scheduleQueue.count();
|
||||
if( _scheduleQueue.isEmpty() ) {
|
||||
qDebug() << "XX slotScheduleFolderSync: folderQueue size: " << _scheduledFolders.count();
|
||||
if( _scheduledFolders.isEmpty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first folder in the queue that can be synced.
|
||||
Folder* f = 0;
|
||||
while( !_scheduleQueue.isEmpty() ) {
|
||||
f = _scheduleQueue.dequeue();
|
||||
while( !_scheduledFolders.isEmpty() ) {
|
||||
f = _scheduledFolders.dequeue();
|
||||
Q_ASSERT(f);
|
||||
|
||||
if( f->canSync() ) {
|
||||
@@ -711,7 +702,7 @@ void FolderMan::slotEtagPollTimerTimeout()
|
||||
if (_currentSyncFolder == f) {
|
||||
continue;
|
||||
}
|
||||
if (_scheduleQueue.contains(f)) {
|
||||
if (_scheduledFolders.contains(f)) {
|
||||
continue;
|
||||
}
|
||||
if (_disabledFolders.contains(f)) {
|
||||
@@ -740,7 +731,7 @@ void FolderMan::slotRemoveFoldersForAccount(AccountState* accountState)
|
||||
}
|
||||
|
||||
foreach (const auto &f, foldersToRemove) {
|
||||
slotRemoveFolder(f);
|
||||
removeFolder(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,10 +757,54 @@ void FolderMan::slotServerVersionChanged(Account *account)
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMan::slotScheduleFolderOwningFile(const QString& path)
|
||||
void FolderMan::slotWatchedFileUnlocked(const QString& path)
|
||||
{
|
||||
if (Folder* f = folderForPath(path)) {
|
||||
slotScheduleSync(f);
|
||||
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?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +862,6 @@ 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()));
|
||||
@@ -880,7 +914,7 @@ QStringList FolderMan::findFileInLocalFolders( const QString& relPath, const Acc
|
||||
return re;
|
||||
}
|
||||
|
||||
void FolderMan::slotRemoveFolder( Folder *f )
|
||||
void FolderMan::removeFolder( Folder *f )
|
||||
{
|
||||
if( !f ) {
|
||||
qWarning() << "!! Can not remove null folder";
|
||||
@@ -895,7 +929,7 @@ void FolderMan::slotRemoveFolder( Folder *f )
|
||||
terminateSyncProcess();
|
||||
}
|
||||
|
||||
if (_scheduleQueue.removeAll(f) > 0) {
|
||||
if (_scheduledFolders.removeAll(f) > 0) {
|
||||
emit scheduleQueueChanged();
|
||||
}
|
||||
|
||||
@@ -1262,7 +1296,7 @@ void FolderMan::setIgnoreHiddenFiles(bool ignore)
|
||||
|
||||
QQueue<Folder*> FolderMan::scheduleQueue() const
|
||||
{
|
||||
return _scheduleQueue;
|
||||
return _scheduledFolders;
|
||||
}
|
||||
|
||||
Folder *FolderMan::currentSyncFolder() const
|
||||
|
||||
+92
-41
@@ -37,6 +37,26 @@ 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
|
||||
{
|
||||
@@ -54,6 +74,9 @@ 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);
|
||||
|
||||
@@ -126,6 +149,24 @@ 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.
|
||||
@@ -139,41 +180,12 @@ 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
|
||||
@@ -190,10 +202,22 @@ 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();
|
||||
@@ -207,12 +231,29 @@ private slots:
|
||||
void slotServerVersionChanged(Account* account);
|
||||
|
||||
/**
|
||||
* Schedules the folder for synchronization that contains
|
||||
* A file whose locks were being monitored has become unlocked.
|
||||
*
|
||||
* This schedules the folder for synchronization that contains
|
||||
* the file with the given path.
|
||||
*/
|
||||
void slotScheduleFolderOwningFile(const QString& 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();
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -222,7 +263,7 @@ private:
|
||||
void unloadFolder( Folder * );
|
||||
|
||||
/** Will start a sync after a bit of delay. */
|
||||
void startScheduledSyncSoon(qint64 msMinimumDelay = 0);
|
||||
void startScheduledSyncSoon();
|
||||
|
||||
// finds all folder configuration files
|
||||
// and create the folders
|
||||
@@ -238,19 +279,29 @@ 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;
|
||||
QScopedPointer<SocketApi> _socketApi;
|
||||
|
||||
/** The aliases of folders that shall be synced. */
|
||||
QQueue<Folder*> _scheduleQueue;
|
||||
/// Occasionally schedules folders
|
||||
QTimer _timeScheduler;
|
||||
|
||||
/** When the timer expires one of the scheduled syncs will be started. */
|
||||
/// 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;
|
||||
|
||||
bool _appRestartRequired;
|
||||
|
||||
static FolderMan *_instance;
|
||||
|
||||
@@ -38,6 +38,12 @@ 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)
|
||||
{
|
||||
@@ -58,7 +64,6 @@ 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;
|
||||
@@ -69,6 +74,14 @@ 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();
|
||||
}
|
||||
@@ -375,12 +388,13 @@ QModelIndex FolderStatusModel::indexForPath(Folder *f, const QString& path) cons
|
||||
if (slashPos == -1) {
|
||||
// first level folder
|
||||
for (int i = 0; i < _folders.size(); ++i) {
|
||||
if (_folders.at(i)._folder == f) {
|
||||
auto& info = _folders.at(i);
|
||||
if (info._folder == f) {
|
||||
if( path.isEmpty() ) { // the folder object
|
||||
return index(i, 0);
|
||||
}
|
||||
for (int j = 0; j < _folders.at(i)._subs.size(); ++j) {
|
||||
const QString subName = _folders.at(i)._subs.at(j)._name;
|
||||
for (int j = 0; j < info._subs.size(); ++j) {
|
||||
const QString subName = info._subs.at(j)._name;
|
||||
if (subName == path) {
|
||||
return index(j, 0, index(i));
|
||||
}
|
||||
@@ -785,7 +799,7 @@ void FolderStatusModel::slotApplySelectiveSync()
|
||||
foreach(const auto &it, changes) {
|
||||
folder->journalDb()->avoidReadFromDbOnNextSync(it);
|
||||
}
|
||||
FolderMan::instance()->slotScheduleSync(folder);
|
||||
FolderMan::instance()->scheduleFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -986,10 +1000,12 @@ 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.
|
||||
_folders[folderIndex]._progress = SubFolderInfo::Progress();
|
||||
pi = SubFolderInfo::Progress();
|
||||
} else if (state == SyncResult::NotYetStarted) {
|
||||
FolderMan* folderMan = FolderMan::instance();
|
||||
int pos = folderMan->scheduleQueue().indexOf(f);
|
||||
@@ -1003,16 +1019,16 @@ void FolderStatusModel::slotFolderSyncStateChange(Folder *f)
|
||||
} else {
|
||||
message = tr("Waiting for %n other folder(s)...", "", pos);
|
||||
}
|
||||
_folders[folderIndex]._progress = SubFolderInfo::Progress();
|
||||
_folders[folderIndex]._progress._overallSyncString = message;
|
||||
pi = SubFolderInfo::Progress();
|
||||
pi._overallSyncString = message;
|
||||
} else if (state == SyncResult::SyncPrepare) {
|
||||
_folders[folderIndex]._progress = SubFolderInfo::Progress();
|
||||
_folders[folderIndex]._progress._overallSyncString = tr("Preparing to sync...");
|
||||
pi = SubFolderInfo::Progress();
|
||||
pi._overallSyncString = tr("Preparing to sync...");
|
||||
} else if (state == SyncResult::Problem || state == SyncResult::Success) {
|
||||
// Reset the progress info after a sync.
|
||||
_folders[folderIndex]._progress = SubFolderInfo::Progress();
|
||||
pi = SubFolderInfo::Progress();
|
||||
} else if (state == SyncResult::Error) {
|
||||
_folders[folderIndex]._progress = SubFolderInfo::Progress();
|
||||
pi = SubFolderInfo::Progress();
|
||||
}
|
||||
|
||||
// update the icon etc. now
|
||||
@@ -1099,7 +1115,7 @@ void FolderStatusModel::slotSyncAllPendingBigFolders()
|
||||
foreach (const auto &it, undecidedList) {
|
||||
folder->journalDb()->avoidReadFromDbOnNextSync(it);
|
||||
}
|
||||
FolderMan::instance()->slotScheduleSync(folder);
|
||||
FolderMan::instance()->scheduleFolder(folder);
|
||||
}
|
||||
|
||||
resetFolders();
|
||||
|
||||
@@ -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->slotScheduleSync(folder);
|
||||
folderMan->scheduleFolder(folder);
|
||||
}
|
||||
|
||||
ExcludedFiles::instance().reloadExcludes();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "winuser.h"
|
||||
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "owncloud.exe.manifest-mingw"
|
||||
@@ -66,7 +66,7 @@ void OcsJob::start()
|
||||
req.setRawHeader("Ocs-APIREQUEST", "true");
|
||||
req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
QUrl url = Account::concatUrlPath(account()->url(), path());
|
||||
QUrl url = Utility::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
|
||||
<< Account::concatUrlPath(account()->url(), path())
|
||||
<< Utility::concatUrlPath(account()->url(), path())
|
||||
<< _params
|
||||
<< ":" << replyData;
|
||||
}
|
||||
@@ -117,7 +117,7 @@ bool OcsJob::finished()
|
||||
if (!_passStatusCodes.contains(statusCode)) {
|
||||
qDebug() << "Reply to"
|
||||
<< _verb
|
||||
<< Account::concatUrlPath(account()->url(), path())
|
||||
<< Utility::concatUrlPath(account()->url(), path())
|
||||
<< _params
|
||||
<< "has unexpected status code:" << statusCode << replyData;
|
||||
emit ocsError(statusCode, message);
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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>
|
||||
@@ -903,6 +903,9 @@ void ownCloudGui::setPauseOnAllFoldersHelper(bool pause)
|
||||
foreach (Folder* f, FolderMan::instance()->map()) {
|
||||
if (accounts.contains(f->accountState())) {
|
||||
f->setSyncPaused(pause);
|
||||
if (pause) {
|
||||
f->slotTerminateSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ void SelectiveSyncDialog::accept()
|
||||
_folder->journalDb()->avoidReadFromDbOnNextSync(it);
|
||||
}
|
||||
|
||||
folderMan->slotScheduleSync(_folder);
|
||||
folderMan->scheduleFolder(_folder);
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
@@ -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(Account::concatUrlPath(_account->url(), QString("index.php/s/%1").arg(data.value("token").toString())).toString());
|
||||
url = QUrl(Utility::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(Account::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
|
||||
url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("public.php"), queryArgs).toString());
|
||||
}
|
||||
|
||||
QDate expireDate;
|
||||
|
||||
@@ -63,6 +63,10 @@ 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
|
||||
|
||||
@@ -39,7 +39,6 @@ Account::Account(QObject *parent)
|
||||
: QObject(parent)
|
||||
, _capabilities(QVariantMap())
|
||||
, _davPath( Theme::instance()->webDavPath() )
|
||||
, _wasMigrated(false)
|
||||
{
|
||||
qRegisterMetaType<AccountPtr>("AccountPtr");
|
||||
}
|
||||
@@ -94,30 +93,6 @@ 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();
|
||||
@@ -157,12 +132,7 @@ void Account::setCredentials(AbstractCredentials *cred)
|
||||
|
||||
QUrl Account::davUrl() const
|
||||
{
|
||||
return concatUrlPath(url(), davPath());
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> Account::lastAuthCookies() const
|
||||
{
|
||||
return _am->cookieJar()->cookiesForUrl(_url);
|
||||
return Utility::concatUrlPath(url(), davPath());
|
||||
}
|
||||
|
||||
void Account::clearCookieJar()
|
||||
@@ -209,7 +179,7 @@ QNetworkAccessManager *Account::networkAccessManager()
|
||||
|
||||
QNetworkReply *Account::headRequest(const QString &relPath)
|
||||
{
|
||||
return headRequest(concatUrlPath(url(), relPath));
|
||||
return headRequest(Utility::concatUrlPath(url(), relPath));
|
||||
}
|
||||
|
||||
QNetworkReply *Account::headRequest(const QUrl &url)
|
||||
@@ -223,7 +193,7 @@ QNetworkReply *Account::headRequest(const QUrl &url)
|
||||
|
||||
QNetworkReply *Account::getRequest(const QString &relPath)
|
||||
{
|
||||
return getRequest(concatUrlPath(url(), relPath));
|
||||
return getRequest(Utility::concatUrlPath(url(), relPath));
|
||||
}
|
||||
|
||||
QNetworkReply *Account::getRequest(const QUrl &url)
|
||||
@@ -246,7 +216,7 @@ QNetworkReply *Account::deleteRequest( const QUrl &url)
|
||||
|
||||
QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data)
|
||||
{
|
||||
return davRequest(verb, concatUrlPath(davUrl(), relPath), req, data);
|
||||
return davRequest(verb, Utility::concatUrlPath(davUrl(), relPath), req, data);
|
||||
}
|
||||
|
||||
QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
|
||||
@@ -340,43 +310,6 @@ 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) {
|
||||
@@ -475,16 +408,6 @@ void Account::handleInvalidCredentials()
|
||||
emit invalidCredentials();
|
||||
}
|
||||
|
||||
bool Account::wasMigrated()
|
||||
{
|
||||
return _wasMigrated;
|
||||
}
|
||||
|
||||
void Account::setMigrated(bool mig)
|
||||
{
|
||||
_wasMigrated = mig;
|
||||
}
|
||||
|
||||
const Capabilities &Account::capabilities() const
|
||||
{
|
||||
return _capabilities;
|
||||
|
||||
+36
-45
@@ -56,10 +56,28 @@ 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 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.
|
||||
@@ -69,44 +87,15 @@ public:
|
||||
void setDavPath(const QString&s) { _davPath = s; }
|
||||
void setNonShib(bool nonShib);
|
||||
|
||||
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;
|
||||
/** Returns webdav entry URL, based on url() */
|
||||
QUrl davUrl() 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);
|
||||
@@ -115,6 +104,7 @@ 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; }
|
||||
@@ -138,25 +128,21 @@ public:
|
||||
// pluggable handler
|
||||
void setSslErrorHandler(AbstractSslErrorHandler *handler);
|
||||
|
||||
// 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
|
||||
// To be called by credentials only, for storing username and the like
|
||||
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());
|
||||
|
||||
void setCapabilities(const QVariantMap &caps);
|
||||
/** Access the server capabilities */
|
||||
const Capabilities &capabilities() const;
|
||||
void setServerVersion(const QString &version);
|
||||
void setCapabilities(const QVariantMap &caps);
|
||||
|
||||
/** Access the server version */
|
||||
QString serverVersion() const;
|
||||
int serverVersionInt() const;
|
||||
void setServerVersion(const QString &version);
|
||||
|
||||
/** Whether the server is too old.
|
||||
*
|
||||
@@ -171,6 +157,7 @@ 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();
|
||||
@@ -179,12 +166,16 @@ public:
|
||||
void resetNetworkAccessManager();
|
||||
QNetworkAccessManager* networkAccessManager();
|
||||
|
||||
/// Called by network jobs on credential errors.
|
||||
/// Called by network jobs on credential errors, emits invalidCredentials()
|
||||
void handleInvalidCredentials();
|
||||
|
||||
signals:
|
||||
/// Emitted whenever there's network activity
|
||||
void propagatorNetworkActivity();
|
||||
|
||||
/// Triggered by handleInvalidCredentials()
|
||||
void invalidCredentials();
|
||||
|
||||
void credentialsFetched(AbstractCredentials* credentials);
|
||||
void credentialsAsked(AbstractCredentials* credentials);
|
||||
|
||||
@@ -203,6 +194,7 @@ protected Q_SLOTS:
|
||||
|
||||
private:
|
||||
Account(QObject *parent = 0);
|
||||
void setSharedThis(AccountPtr sharedThis);
|
||||
|
||||
QWeakPointer<Account> _sharedThis;
|
||||
QString _id;
|
||||
@@ -224,7 +216,6 @@ private:
|
||||
QByteArray _pemCertificate;
|
||||
QString _pemPrivateKey;
|
||||
QString _davPath; // defaults to value from theme, might be overwritten in brandings
|
||||
bool _wasMigrated;
|
||||
friend class AccountManager;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ 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;
|
||||
|
||||
@@ -17,13 +17,6 @@
|
||||
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");
|
||||
|
||||
@@ -27,7 +27,6 @@ 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;
|
||||
|
||||
@@ -71,21 +71,6 @@ 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");
|
||||
@@ -147,7 +132,7 @@ void HttpCredentials::fetchFromKeychain()
|
||||
_certificatePath = _account->credentialSetting(QLatin1String(certifPathC)).toString();
|
||||
_certificatePasswd = _account->credentialSetting(QLatin1String(certifPasswdC)).toString();
|
||||
|
||||
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
|
||||
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
|
||||
const QString kck = keychainKey(_account->url().toString(), _user );
|
||||
|
||||
QString key = QString::fromLatin1( "%1/data" ).arg( kck );
|
||||
@@ -229,7 +214,7 @@ void HttpCredentials::invalidateToken()
|
||||
}
|
||||
|
||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
|
||||
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
|
||||
settings->setParent(job); // make the job parent to make setting deleted properly
|
||||
job->setSettings(settings.release());
|
||||
job->setInsecureFallback(true);
|
||||
@@ -280,7 +265,7 @@ void HttpCredentials::persist()
|
||||
_account->setCredentialSetting(QLatin1String(certifPathC), _certificatePath);
|
||||
_account->setCredentialSetting(QLatin1String(certifPasswdC), _certificatePasswd);
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
auto settings = _account->settingsWithGroup(Theme::instance()->appName());
|
||||
auto settings = Utility::settingsWithGroup(Theme::instance()->appName());
|
||||
settings->setParent(job); // make the job parent to make setting deleted properly
|
||||
job->setSettings(settings.release());
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ 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;
|
||||
|
||||
@@ -80,17 +80,6 @@ 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");
|
||||
|
||||
@@ -40,7 +40,6 @@ 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;
|
||||
|
||||
@@ -680,7 +680,7 @@ void JsonApiJob::start()
|
||||
{
|
||||
QNetworkRequest req;
|
||||
req.setRawHeader("OCS-APIREQUEST", "true");
|
||||
QUrl url = Account::concatUrlPath(account()->url(), path());
|
||||
QUrl url = Utility::concatUrlPath(account()->url(), path());
|
||||
QList<QPair<QString, QString> > params = _additionalParams;
|
||||
params << qMakePair(QString::fromLatin1("format"), QString::fromLatin1("json"));
|
||||
url.setQueryItems(params);
|
||||
|
||||
@@ -384,8 +384,6 @@ void OwncloudPropagator::start(const SyncFileItemVector& items)
|
||||
// will delete directories, so defer execution
|
||||
directoriesToRemove.prepend(current);
|
||||
removedDirectory = item->_file + "/";
|
||||
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC){
|
||||
directories.top().second->appendSyncJob(current);
|
||||
} else {
|
||||
directories.top().second->append(current);
|
||||
}
|
||||
@@ -595,11 +593,7 @@ bool PropagateDirectory::scheduleNextJob()
|
||||
_state = Running;
|
||||
|
||||
if (!_firstJob && _subJobs.isEmpty()) {
|
||||
if (!_scheduledSyncJobs && _syncJobsScheduler) {
|
||||
scheduleSyncJobs();
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
finalize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -612,9 +606,15 @@ bool PropagateDirectory::scheduleNextJob()
|
||||
return false;
|
||||
}
|
||||
|
||||
// cache the value of first unfinished subjob
|
||||
bool stopAtDirectory = false;
|
||||
// FIXME: use the cached value of finished job
|
||||
for (int i = 0; i < _subJobs.count(); ++i) {
|
||||
int i = _firstUnfinishedSubJob;
|
||||
int subJobsCount = _subJobs.count();
|
||||
while (i < subJobsCount && _subJobs.at(i)->_state == Finished) {
|
||||
_firstUnfinishedSubJob = ++i;
|
||||
}
|
||||
|
||||
for (int i = _firstUnfinishedSubJob; i < subJobsCount; ++i) {
|
||||
if (_subJobs.at(i)->_state == Finished) {
|
||||
continue;
|
||||
}
|
||||
@@ -663,23 +663,12 @@ void PropagateDirectory::slotSubJobFinished(SyncFileItem::Status status)
|
||||
// check if we finished
|
||||
if (_jobsFinished >= totalJobs) {
|
||||
Q_ASSERT(!_runningNow); // how can we be finished if there are still jobs running now
|
||||
if (!_scheduledSyncJobs && _syncJobsScheduler) {
|
||||
scheduleSyncJobs();
|
||||
} else {
|
||||
finalize();
|
||||
}
|
||||
finalize();
|
||||
} else {
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateDirectory::scheduleSyncJobs() {
|
||||
_scheduledSyncJobs = true;
|
||||
//at this point, scheduler will be destroyed together with _subJobs
|
||||
_subJobs.append(_syncJobsScheduler.data());
|
||||
emit ready();
|
||||
}
|
||||
|
||||
void PropagateDirectory::finalize()
|
||||
{
|
||||
bool ok = true;
|
||||
@@ -728,80 +717,6 @@ qint64 PropagateDirectory::committedDiskSpace() const
|
||||
return needed;
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
|
||||
PropagatorJob::JobParallelism PropagateSyncItems::parallelism()
|
||||
{
|
||||
return FullParallelism;
|
||||
}
|
||||
|
||||
|
||||
bool PropagateSyncItems::scheduleNextJob()
|
||||
{
|
||||
if (_state == Finished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_state == NotYetStarted) {
|
||||
_state = Running;
|
||||
|
||||
if (_syncJobs.isEmpty()) {
|
||||
finalize();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
while (!_syncJobs.isEmpty()) {
|
||||
PropagatorJob *next = _syncJobs.takeFirst();
|
||||
if (next->_state == Finished) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (possiblyRunNextJob(next)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Q_ASSERT(next->_state == Running);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PropagateSyncItems::slotSubJobFinished(SyncFileItem::Status status)
|
||||
{
|
||||
if (status == SyncFileItem::FatalError) {
|
||||
abort();
|
||||
_hasError = status;
|
||||
finalize();
|
||||
return;
|
||||
} else if (status == SyncFileItem::NormalError || status == SyncFileItem::SoftError) {
|
||||
_hasError = status;
|
||||
}
|
||||
|
||||
_jobsFinished++;
|
||||
// We finished processing all the jobs
|
||||
// check if we finished
|
||||
if (_syncJobs.isEmpty() && (_syncJobsCount == _jobsFinished)) {
|
||||
finalize();
|
||||
} else {
|
||||
emit ready();
|
||||
}
|
||||
}
|
||||
|
||||
void PropagateSyncItems::finalize()
|
||||
{
|
||||
_state = Finished;
|
||||
emit finished(_hasError == SyncFileItem::NoStatus ? SyncFileItem::Success : _hasError);
|
||||
}
|
||||
|
||||
qint64 PropagateSyncItems::committedDiskSpace() const
|
||||
{
|
||||
qint64 needed = 0;
|
||||
foreach (PropagatorJob* job, _syncJobs) {
|
||||
needed += job->committedDiskSpace();
|
||||
}
|
||||
return needed;
|
||||
}
|
||||
|
||||
CleanupPollsJob::~CleanupPollsJob()
|
||||
{}
|
||||
|
||||
|
||||
@@ -176,59 +176,6 @@ public slots:
|
||||
virtual void start() = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Propagate sync items within a specific folder, and all its sub entries.
|
||||
* @ingroup libsync
|
||||
*/
|
||||
class PropagateSyncItems : public PropagatorJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// all the sub files or sub directories.
|
||||
QList<PropagatorJob *> _syncJobs;
|
||||
|
||||
int _jobsFinished; // number of jobs that have completed
|
||||
int _syncJobsCount; // number of subJobs running right now
|
||||
SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error
|
||||
|
||||
PropagateSyncItems(OwncloudPropagator *propagator)
|
||||
: PropagatorJob(propagator)
|
||||
, _jobsFinished(0), _syncJobsCount(0), _hasError(SyncFileItem::NoStatus)
|
||||
{ }
|
||||
|
||||
virtual ~PropagateSyncItems() {
|
||||
qDeleteAll(_syncJobs);
|
||||
}
|
||||
|
||||
void append(PropagatorJob *subJob) {
|
||||
_syncJobs.append(subJob);
|
||||
_syncJobsCount++;
|
||||
}
|
||||
|
||||
virtual bool scheduleNextJob() Q_DECL_OVERRIDE;
|
||||
virtual JobParallelism parallelism() Q_DECL_OVERRIDE;
|
||||
virtual void abort() Q_DECL_OVERRIDE {
|
||||
foreach (PropagatorJob *j, _syncJobs)
|
||||
j->abort();
|
||||
}
|
||||
|
||||
void finalize();
|
||||
|
||||
qint64 committedDiskSpace() const Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
bool possiblyRunNextJob(PropagatorJob *next) {
|
||||
if (next->_state == NotYetStarted) {
|
||||
connect(next, SIGNAL(finished(SyncFileItem::Status)), this, SLOT(slotSubJobFinished(SyncFileItem::Status)), Qt::QueuedConnection);
|
||||
connect(next, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
|
||||
this, SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)));
|
||||
connect(next, SIGNAL(progress(const SyncFileItem &,quint64)), this, SIGNAL(progress(const SyncFileItem &,quint64)));
|
||||
connect(next, SIGNAL(ready()), this, SIGNAL(ready()));
|
||||
}
|
||||
return next->scheduleNextJob();
|
||||
}
|
||||
|
||||
void slotSubJobFinished(SyncFileItem::Status status);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Propagate a directory, and all its sub entries.
|
||||
@@ -240,12 +187,7 @@ public:
|
||||
// e.g: create the directory
|
||||
QScopedPointer<PropagateItemJob>_firstJob;
|
||||
|
||||
// all the new and changed files without conflicts scheduler class
|
||||
// remark: do not QScopedPointer, since this class is either deleted via qDeleteAll(_subJobs) or usual delete,
|
||||
// depending on ownership determined by flag _scheduledSyncJobs
|
||||
QPointer<PropagateSyncItems> _syncJobsScheduler;
|
||||
|
||||
// all the other file operation or sub directories.
|
||||
// all the sub files or sub directories.
|
||||
QVector<PropagatorJob *> _subJobs;
|
||||
|
||||
SyncFileItemPtr _item;
|
||||
@@ -253,18 +195,14 @@ 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
|
||||
bool _scheduledSyncJobs; // verify if already scheduled execution of files sync jobs
|
||||
int _firstUnfinishedSubJob;
|
||||
|
||||
explicit PropagateDirectory(OwncloudPropagator *propagator, const SyncFileItemPtr &item = SyncFileItemPtr(new SyncFileItem))
|
||||
: PropagatorJob(propagator)
|
||||
, _firstJob(0), _syncJobsScheduler(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus), _scheduledSyncJobs(false)
|
||||
, _firstJob(0), _item(item), _jobsFinished(0), _runningNow(0), _hasError(SyncFileItem::NoStatus), _firstUnfinishedSubJob(0)
|
||||
{ }
|
||||
|
||||
virtual ~PropagateDirectory() {
|
||||
// check whether the owner of the pointer is _subJobs or PropagateDirectory class
|
||||
if (!_scheduledSyncJobs){
|
||||
delete _syncJobsScheduler;
|
||||
}
|
||||
qDeleteAll(_subJobs);
|
||||
}
|
||||
|
||||
@@ -272,20 +210,11 @@ public:
|
||||
_subJobs.append(subJob);
|
||||
}
|
||||
|
||||
void appendSyncJob(PropagatorJob *subJob) {
|
||||
if (!_syncJobsScheduler) {
|
||||
_syncJobsScheduler = new PropagateSyncItems(_propagator);
|
||||
}
|
||||
_syncJobsScheduler->append(subJob);
|
||||
}
|
||||
|
||||
virtual bool scheduleNextJob() Q_DECL_OVERRIDE;
|
||||
virtual JobParallelism parallelism() Q_DECL_OVERRIDE;
|
||||
virtual void abort() Q_DECL_OVERRIDE {
|
||||
if (_firstJob)
|
||||
_firstJob->abort();
|
||||
if (_syncJobsScheduler)
|
||||
_syncJobsScheduler->abort();
|
||||
foreach (PropagatorJob *j, _subJobs)
|
||||
j->abort();
|
||||
}
|
||||
@@ -296,8 +225,6 @@ public:
|
||||
|
||||
void finalize();
|
||||
|
||||
void scheduleSyncJobs();
|
||||
|
||||
qint64 committedDiskSpace() const Q_DECL_OVERRIDE;
|
||||
|
||||
private slots:
|
||||
@@ -316,6 +243,7 @@ private slots:
|
||||
void slotSubJobFinished(SyncFileItem::Status status);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Dummy job that just mark it as completed and ignored
|
||||
* @ingroup libsync
|
||||
|
||||
@@ -44,9 +44,10 @@ QString ownCloudTheme::configFileName() const
|
||||
QString ownCloudTheme::about() const
|
||||
{
|
||||
QString devString;
|
||||
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>"
|
||||
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>"
|
||||
"<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 "
|
||||
|
||||
@@ -593,7 +593,7 @@ void PropagateDownloadFile::deleteExistingFolder()
|
||||
}
|
||||
|
||||
QString conflictDir = FileSystem::makeConflictFileName(
|
||||
existingDir, Utility::qDateTimeFromTime_t(_item->_modtime));
|
||||
existingDir, Utility::qDateTimeFromTime_t(FileSystem::getModTime(existingDir)));
|
||||
|
||||
emit _propagator->touchedFile(existingDir);
|
||||
emit _propagator->touchedFile(conflictDir);
|
||||
@@ -722,7 +722,8 @@ void PropagateDownloadFile::downloadFinished()
|
||||
&& !FileSystem::fileEquals(fn, _tmpFile.fileName());
|
||||
if (isConflict) {
|
||||
QString renameError;
|
||||
QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime));
|
||||
QString conflictFileName = FileSystem::makeConflictFileName(
|
||||
fn, Utility::qDateTimeFromTime_t(FileSystem::getModTime(fn)));
|
||||
if (!FileSystem::rename(fn, conflictFileName, &renameError)) {
|
||||
// If the rename fails, don't replace it.
|
||||
|
||||
|
||||
@@ -334,6 +334,16 @@ 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;
|
||||
@@ -589,11 +599,15 @@ 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) {
|
||||
if (std::difftime(file->modtime, file->other.modtime) < 0) {
|
||||
auto difftime = std::difftime(file->modtime, file->other.modtime);
|
||||
if (difftime < -3600*2) {
|
||||
// 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 {
|
||||
} else if (difftime > 0) {
|
||||
_hasForwardInTimeFiles = true;
|
||||
}
|
||||
}
|
||||
@@ -928,7 +942,10 @@ 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) {
|
||||
} 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.
|
||||
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;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "utility.h"
|
||||
|
||||
#include "version.h"
|
||||
#include "configfile.h"
|
||||
|
||||
// Note: This file must compile without QtGui
|
||||
#include <QCoreApplication>
|
||||
@@ -586,4 +587,41 @@ 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
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QMap>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
|
||||
class QSettings;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -142,6 +146,14 @@ 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
|
||||
|
||||
|
||||
@@ -598,7 +598,6 @@ 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; }
|
||||
|
||||
@@ -50,7 +50,7 @@ private slots:
|
||||
QFETCH(QueryItems, query);
|
||||
QFETCH(QString, expected);
|
||||
QUrl baseUrl("http://example.com" + base);
|
||||
QUrl resultUrl = Account::concatUrlPath(baseUrl, concat, query);
|
||||
QUrl resultUrl = Utility::concatUrlPath(baseUrl, concat, query);
|
||||
QString result = QString::fromUtf8(resultUrl.toEncoded());
|
||||
QString expectedFull = "http://example.com" + expected;
|
||||
QCOMPARE(result, expectedFull);
|
||||
|
||||
@@ -80,7 +80,7 @@ private slots:
|
||||
quint64 sec = 1000;
|
||||
quint64 hour = 3600 * sec;
|
||||
|
||||
QDateTime current = QDateTime::currentDateTime();
|
||||
QDateTime current = QDateTime::currentDateTimeUtc();
|
||||
|
||||
QCOMPARE(durationToDescriptiveString2(0), QString("0 second(s)") );
|
||||
QCOMPARE(durationToDescriptiveString2(5), QString("0 second(s)") );
|
||||
|
||||
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-369
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-369
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+415
-363
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+420
-372
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+415
-363
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+415
-363
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+415
-363
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+418
-372
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-369
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-371
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-371
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+419
-367
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-371
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+419
-373
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+422
-374
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+420
-374
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-366
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+422
-373
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+420
-372
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+428
-380
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-370
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+415
-363
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+417
-370
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+416
-368
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
Referência em uma Nova Issue
Bloquear um usuário