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