Comparar commits

..

123 Commits

Autor SHA1 Mensagem Data
Olivier Goffart 1c0cf646c7 Merge remote-tracking branch 'origin/master' into browser-based-client-login 2016-11-16 11:59:52 +01:00
Markus Goetz 52552a4204 Merge pull request #5102 from owncloud/chunking-ng
Chunking ng
2016-11-15 15:56:52 +01:00
Olivier Goffart 2f3db04e87 Rename SyncFileStatus::sharedWithMe to shared
Last commit for issue #4788 made the meaning to be simply shared
(with the user or from the user)
2016-11-15 14:32:20 +01:00
Olivier Goffart 2d6e473a40 Discovery: consider also the "shared by me" as shared
The "S" in the permission is only for the "Shared with me" files.
It is only used to show the shared status in the overlay icons.
But we also wish to show the shared status for files that are shared
"by" the users. We can find that out using the 'share-types' webdav
property. If set, then we are sharing the object.
We fake a 'S' in the permission as for our purpose, they mean the same.

Issue #4788
2016-11-15 14:32:20 +01:00
Olivier Goffart 92027e8692 SyncEngineTestUtils: Do don't allocate a buffer for the whole file
As the file can be some hunreds of megabytes, allocating such big arrays may
cause problems.

Also make the timeout a bit bigger so the test can rununder valgrind.
2016-11-15 11:39:40 +01:00
Olivier Goffart 2723cd225e FolderStatusDelegate: Fix compiler warning.
QStyleOptionProgressBarV2 is deprecated in Qt5.
2016-11-15 10:16:41 +01:00
Jenkins for ownCloud acd151102c [tx-robot] updated from transifex 2016-11-15 02:18:36 +01:00
Olivier Goffart 77e790d2ee Merge pull request #5304 from owncloud/Kawohl-linktocentral
documnetation.
2016-11-14 12:43:10 +01:00
Olivier Goffart 899c675f0a Merge pull request #5267 from payload/fix_4608
nautilus integration: "share" extension for syncing folders
2016-11-14 12:35:18 +01:00
Olivier Goffart fb76487e76 Merge pull request #5266 from rmekarni/patch-1
Fix find_library FindCMocka.cmake
2016-11-14 12:20:24 +01:00
Tobias Haeussler 0c0a3ca0a5 Add manifest file on Windows to make the application UAC aware. 2016-11-14 12:16:58 +01:00
Olivier Goffart 5377d1e283 Chunking-NG: code cleanup after review 2016-11-14 10:42:07 +01:00
Jenkins for ownCloud 676ad530e7 [tx-robot] updated from transifex 2016-11-14 02:18:26 +01:00
Jenkins for ownCloud fbcb01ed27 [tx-robot] updated from transifex 2016-11-13 02:18:26 +01:00
Jenkins for ownCloud c2b006e857 [tx-robot] updated from transifex 2016-11-12 02:18:26 +01:00
Jenkins for ownCloud 0e7ccea588 [tx-robot] updated from transifex 2016-11-11 02:18:27 +01:00
Jenkins for ownCloud c16deb3e44 [tx-robot] updated from transifex 2016-11-10 02:18:27 +01:00
Jenkins for ownCloud 019544fcfd [tx-robot] updated from transifex 2016-11-10 01:15:14 +01:00
Jonathan Kawohl 5f0167b19e removed openssl_version variable, 2016-11-09 10:25:32 +01:00
Jonathan Kawohl 29d3c33eb2 added missing quotes 2016-11-09 08:59:14 +01:00
Jonathan Kawohl 469eca1f5f added link to central 2016-11-09 08:59:14 +01:00
Jonathan Kawohl 0864d67a9a fixing wrong files Revert "fix for https://github.com/owncloud/client/issues/1251"
This reverts commit 4f1feab845.
2016-11-09 08:59:14 +01:00
Jonathan Kawohl e9503664f5 fix for https://github.com/owncloud/client/issues/1251 2016-11-09 08:59:14 +01:00
Jonathan Kawohl 58cee61624 added info on qtkeychain
added installation info on qtkeychain
2016-11-09 08:59:14 +01:00
Jonathan Kawohl d23d07f99b Correcting for PR comments 2016-11-09 08:59:14 +01:00
Jonathan Kawohl d70db810dd updated link for documentation and added more info to osx build 2016-11-09 08:59:14 +01:00
Jenkins for ownCloud 842129f99d [tx-robot] updated from transifex 2016-11-09 02:18:28 +01:00
ckamm 36d61ef3a9 Doc: Add sync algorithm overview and comments (#5277)
* Doc: Add sync algorithm overview and comments
2016-11-08 16:10:55 +01:00
Christian Kamm f1f27221a7 Conflicts: Use the local mtime for the conflict file name #5273
Otherwise local conflict files may be overridden in a restore
situation. See ticket for details.
2016-11-08 14:47:51 +01:00
Christian Kamm eb012d26ee FolderStatusModel: Minor cleanups. 2016-11-08 14:01:07 +01:00
Christian Kamm 9ee8187083 Sort folder list alphabetically #5299 2016-11-08 14:01:07 +01:00
Christian Kamm b0c45cfc89 Test: Don't fail when DST change is near #5284 2016-11-08 11:13:19 +01:00
Christian Kamm 90cea69692 Network: Add "Accept" header for some firewalls #5298 2016-11-08 11:01:49 +01:00
Jenkins for ownCloud 155bdfbffe [tx-robot] updated from transifex 2016-11-08 02:18:26 +01:00
Christian Kamm 0e2782d369 Terminate sync when pausing from context menu #5290 2016-11-07 12:50:06 +01:00
Jenkins for ownCloud 6b3d0e69aa [tx-robot] updated from transifex 2016-11-07 02:18:26 +01:00
Jenkins for ownCloud f30aee6d4e [tx-robot] updated from transifex 2016-11-06 02:18:34 +01:00
Jenkins for ownCloud 82f86eb019 [tx-robot] updated from transifex 2016-11-05 02:18:29 +01:00
Olivier Goffart 9573c5e64d Merge remote-tracking branch '2.2' into master
For the reanslation. Now transifex should be updated to update master.
This should normally be the last merge from 2.2

 Conflicts:
	mirall.desktop.in
	translations/client_sl.ts
	translations/client_th.ts
2016-11-04 16:51:58 +01:00
Olivier Goffart 15f2b911d9 ChunkingNG: remove stale files when resuming 2016-11-04 16:43:01 +01:00
Jenkins for ownCloud f5aff70398 [tx-robot] updated from transifex 2016-11-04 16:41:40 +01:00
Olivier Goffart e3a4c3989a SyncEngine: disable heuristics for backup restoration for server >= 9.1
The ownCloud 9.1 server has a data-fingerprint property that the admin must
change in case of backup restoration. When this change, the client understands
that a backup was restored, and will generate conflict files and re-upload
new files.

The heuristics based system checks that there is at least two files wose mtime
is put back in the past and no files that goes forward. In that case we ask the
user before creating the conflicts.

This commit disable the heuristics for newer server that have the data-fingerpint.
And change the heuristics to two hours because we want to avoid false positive due
to some clock error, and that 2 hours of lost due to backup restoration is probably
not so bad.

We only ask the user in the heuristics based aproach so in practice this mean that
the "backup-detected" dialog will no longer appear with newer server.

Relates issues #5260, #5109
2016-11-04 16:30:58 +01:00
Jenkins for ownCloud 3e954bc17f [tx-robot] updated from transifex 2016-11-04 02:18:27 +01:00
Jenkins for ownCloud 2527569bb8 [tx-robot] updated from transifex 2016-11-03 02:18:27 +01:00
Jenkins for ownCloud 1e197531cb [tx-robot] updated from transifex 2016-11-02 02:18:28 +01:00
Jenkins for ownCloud 8b876576eb [tx-robot] updated from transifex 2016-11-01 02:18:29 +01:00
Piotr Mrówczyński a2287c9657 Merge pull request #5274 from owncloud/reduce_loops_dirjob
Cache the value of last unfinished job in the PropagateDerectory scheduleNextJob
2016-10-31 22:07:50 +01:00
Piotr M 34c59ba9ed reduce number of loops 2016-10-31 19:45:24 +01:00
Olivier Goffart c8014a0afd ChunkingNG: Add Test 2016-10-31 15:16:53 +01:00
Olivier Goffart 456d82715e Fix compile after merge 2016-10-31 11:29:33 +01:00
Olivier Goffart 8ca3eb7883 Merge remote-tracking branch 'origin/master' into chunking-ng 2016-10-31 11:09:12 +01:00
Jenkins for ownCloud c110745330 [tx-robot] updated from transifex 2016-10-31 02:18:27 +01:00
Jenkins for ownCloud 19521863a0 [tx-robot] updated from transifex 2016-10-30 02:18:24 +01:00
Jenkins for ownCloud c256896165 [tx-robot] updated from transifex 2016-10-30 02:18:26 +02:00
Jenkins for ownCloud ef922f60fa [tx-robot] updated from transifex 2016-10-29 02:18:28 +02:00
Jenkins for ownCloud 733ea90e6b [tx-robot] updated from transifex 2016-10-28 02:18:28 +02:00
Jenkins for ownCloud a4e1fa9dcf [tx-robot] updated from transifex 2016-10-27 02:18:30 +02:00
Jenkins for ownCloud 1a1684ca2a [tx-robot] updated from transifex 2016-10-26 02:18:34 +02:00
Gilbert Röhrbein 9c8572e335 nautilus integration: refactored _connectToSocketServer, removed superfluous check 2016-10-25 21:45:47 +02:00
Gilbert Röhrbein e675a34fbb nautilus integration: use os.path.join, os.sep and tempfile.gettempdir instead of string concats 2016-10-25 20:15:17 +02:00
Christian Kamm 5f47c01346 Account: Rearrange for readability 2016-10-25 13:20:23 +02:00
Christian Kamm 10644d3568 Move concatUrl and settingsWithGroup to Utility
There was little reason to keep them cluttering Account.
2016-10-25 12:05:28 +02:00
Christian Kamm 9ee3144358 Account: Remove wasMigrated/setMigrated
It was unused since early in the multi-account work:
a932eac832
2016-10-25 11:43:06 +02:00
Christian Kamm cf48ea2e00 Remove unused functions
Account::changed and AbstractCredentials::changed have not been needed
in a long while.
2016-10-25 11:33:38 +02:00
Christian Kamm 6026148692 FolderMan: Clean up API
Many public slots didn't need to be public or slots.
2016-10-25 09:44:34 +02:00
Christian Kamm a2222228c9 FolderMan: Document and clean up folder scheduling 2016-10-25 09:44:34 +02:00
Jenkins for ownCloud 9e2d3f5bc7 [tx-robot] updated from transifex 2016-10-25 02:18:32 +02:00
Marcus Hoffmann fe984b61d7 Add script to create a caja plugin. (#5262)
Caja is a fork of nautilus maintained by the Mate project.
This adds a script to create a caja plugin from the nautilus plugin.
It replaces all occurences of nautilus with caja (case sensitive).
This is done in the same way the nemo plugin is generated from the nautilus one.
2016-10-24 15:36:23 +02:00
Jenkins for ownCloud e6be670e49 [tx-robot] updated from transifex 2016-10-24 02:18:28 +02:00
Jenkins for ownCloud 46ce2f4722 [tx-robot] updated from transifex 2016-10-23 02:18:28 +02:00
Gilbert Röhrbein 166a0f60ca nautilus integration: "share" extension for syncing folders
Issue #4608

* folder state is either OK or SYNC usually
* this extension does not know about if a folder is shareable
* a OK folder is shareable
* a SYNC folder could be uploaded or not uploaded
* this commit looks into file entries below this folder
* if one exists with state OK or SYNC
   this folder must have been uploaded
* better would be if the server (gui) tells us
   if the folder is uploaded
2016-10-22 23:26:44 +02:00
Romain Mekarni a9019ccbad Fix find_library FindCMocka.cmake 2016-10-22 14:52:27 +02:00
Jenkins for ownCloud 0a4806af44 [tx-robot] updated from transifex 2016-10-22 02:18:28 +02:00
Olivier Goffart e33b89c222 Chunking-NG: Enable if the server supports it 2016-10-21 16:42:27 +02:00
Olivier Goffart 6b899be895 Merge remote-tracking branch 'origin/2.2' into master 2016-10-21 10:27:33 +02:00
Jenkins for ownCloud 641785298f [tx-robot] updated from transifex 2016-10-21 02:18:31 +02:00
Olivier Goffart 273590fdfc ChunkingNG: Use the 'If' header
As discussed in https://github.com/owncloud/core/pull/26368
2016-10-20 11:16:06 +02:00
Olivier Goffart 0960058842 Merge remote-tracking branch 'origin/master' into chunking-ng 2016-10-20 09:25:03 +02:00
Jenkins for ownCloud 1f78ea0fd1 [tx-robot] updated from transifex 2016-10-20 02:18:28 +02:00
Jenkins for ownCloud 2fd9767892 [tx-robot] updated from transifex 2016-10-19 02:18:33 +02:00
Christian Kamm 69a1e46d0c Progress: Reset lastCompletedItem #5256
Otherwise progress listeners think it's still the last-completed
item when the next sync starts. This lead to spurious entries in
the "Recent Changes" list.
2016-10-18 12:29:52 +02:00
Christian Kamm e020a5327e Force a remote discovery when upgrading to this version #5242
A remote discovery will fix up any lingering problems caused by #5190.
2016-10-18 11:21:01 +02:00
Christian Kamm 1b04489887 Remove stale commented code
These functions don't even exist anymore.
2016-10-18 11:21:01 +02:00
Olivier Goffart e625e06066 Browser auth: add support in the Wizard
Does not work unless the server gives the account back
2016-10-17 17:13:20 +02:00
Olivier Goffart c5f0fa1468 AsyncAuth: fix spelling 2016-10-17 15:16:51 +02:00
Olivier Goffart 7046f93ab3 Merge remote-tracking branch 'origin/master' into browser-based-client-login 2016-10-17 14:45:15 +02:00
Olivier Goffart 36a19703db SyncEngine: Permission error in subfolder of a folder that cannnot be added is a SoftError
Issue #5059
2016-10-17 12:22:04 +02:00
Jenkins for ownCloud f3a345a23b [tx-robot] updated from transifex 2016-10-17 02:18:29 +02:00
Jenkins for ownCloud 826459eae7 [tx-robot] updated from transifex 2016-10-17 01:15:13 +02:00
Jenkins for ownCloud c2c36b85cb [tx-robot] updated from transifex 2016-10-16 02:18:28 +02:00
Jenkins for ownCloud 7b2f8ae6f7 [tx-robot] updated from transifex 2016-10-15 02:18:29 +02:00
Markus Goetz d600fdc89b OS X: Use monochrome tray icon by default (#5229) 2016-10-14 14:26:31 +02:00
Markus Goetz 8c5ea8dc90 Propagator: Also upload more in parallel (#5230)
Fix for https://github.com/owncloud/client/issues/4986#issuecomment-227071801

This is before we have bundling at some point.
2016-10-14 14:23:55 +02:00
Jenkins for ownCloud 0eddcd6384 [tx-robot] updated from transifex 2016-10-14 02:18:29 +02:00
Klaas Freitag 9f2ae5dd17 Add a note that the source repositories need to be enabled. (#5239)
* Add a note that the source repositories need to be enabled.

* Fix typo, is => are.
2016-10-13 16:22:54 +02:00
Olivier Goffart 766d9ae1ac QProgressIndicator: update copy from upstream
Update from commit 05ce8a23cdc12e825532dc6de06c267fb8d48b4f from
https://github.com/dragotin/QProgressIndicator
Which itself is forked from commit e5ba0fd09bfd43b067ee3646d70b294c7efcb558 from
upstream, with additional license header.

It was relicensed to MIT according to
https://github.com/mojocorp/QProgressIndicator/commit/14bb9d10e29407175fdf4b9f03ed9cc7bb124f93

Relates to issues #5180 and #5184
2016-10-13 12:05:59 +02:00
Jenkins for ownCloud 21128ed762 [tx-robot] updated from transifex 2016-10-13 02:18:28 +02:00
Markus Goetz 5000d40619 Fixup previous commit
Thanks @ogoffart for spotting.
2016-10-12 19:10:56 +02:00
Jenkins for ownCloud 0d321050a6 [tx-robot] updated from transifex 2016-10-12 02:18:34 +02:00
Jenkins for ownCloud 089ecf8222 [tx-robot] updated from transifex 2016-10-11 02:18:32 +02:00
Jenkins for ownCloud d54f8adac5 [tx-robot] updated from transifex 2016-10-10 02:18:29 +02:00
Jenkins for ownCloud d35ecafa0b [tx-robot] updated from transifex 2016-10-09 02:18:27 +02:00
Jenkins for ownCloud 2ca807280a [tx-robot] updated from transifex 2016-10-08 02:18:33 +02:00
Jenkins for ownCloud e1a01c75d9 [tx-robot] updated from transifex 2016-10-06 02:18:30 +02:00
Jenkins for ownCloud 6a4adbc87e [tx-robot] updated from transifex 2016-10-05 02:18:30 +02:00
Jenkins for ownCloud 89974ab1d4 [tx-robot] updated from transifex 2016-10-04 02:18:30 +02:00
Jenkins for ownCloud 8d0c313486 [tx-robot] updated from transifex 2016-10-03 02:18:39 +02:00
Jenkins for ownCloud aab0cdf12a [tx-robot] updated from transifex 2016-10-02 02:18:29 +02:00
Jenkins for ownCloud a89520043e [tx-robot] updated from transifex 2016-10-01 02:18:30 +02:00
Jenkins for ownCloud 02c403e360 [tx-robot] updated from transifex 2016-09-30 02:18:34 +02:00
Olivier Goffart da26e59770 Chunking-NG: add some headers when uploading chunks
These are not understood by owncloud yet, but were requested for CernBox

OC-Total-Length in the MKCOL: The full lenght of the file
OC-Chunk-Offset in the PUT: The offset within the file in which this chunk belongs
OC-Checksum in the MOVE: The transission checksum
2016-09-16 16:36:46 +02:00
Olivier Goffart 3c24d5a148 Chunking-NG: The MOVE will return the code 204 if the file was already there 2016-09-16 16:20:40 +02:00
Olivier Goffart c222793525 Chunking-NG: use OC-If-Destination-Match instread of If-Match
For the MOVE command, because If-Match in Webdav relates to the source, not
the destination
2016-09-16 16:15:09 +02:00
Olivier Goffart 28018e8590 Chunking-NG: Fix destination URL 2016-09-16 16:14:53 +02:00
Olivier Goffart 4c79ce2ae6 ConnectionValidator: fetch the account name.
This is needed for the new webdav path used by the new chunking.
The user might not be the same as the one used to connect
2016-09-16 15:49:43 +02:00
Olivier Goffart 7c75a39bc1 Chunking-NG: Some fixup after feedback from the pull request #5102 2016-09-10 12:30:14 +02:00
Olivier Goffart c41f6ed76b Chunking-NG: use new dav URL for the move 2016-08-31 12:12:34 +02:00
Olivier Goffart 818b5854ce Chunking-NG: Qt4 compile 2016-08-31 10:28:44 +02:00
Olivier Goffart 79abb8b4e3 ChunkingNg: enable depending on an environment variable 2016-08-31 10:28:15 +02:00
Olivier Goffart fad387b6b8 Chunking-Ng: Resume 2016-08-31 10:28:15 +02:00
Olivier Goffart a1558100b8 WIP: new chunking algorithm
Current limitations of this WiP
 - No resuming implemented yet
 - No parallel chunks
 - Hackish way to get the webdav paths
2016-08-31 10:28:15 +02:00
Olivier Goffart 4f3f642da6 Upload: refactor the upload in two classes so the new chuning can be implemented 2016-08-31 10:28:15 +02:00
Olivier Goffart 764d7d61f7 WIP: browser-based-client-login 2016-06-29 16:00:22 +02:00
126 arquivos alterados com 15758 adições e 12687 exclusões
+1 -1
Ver Arquivo
@@ -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
+2 -2
Ver Arquivo
@@ -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!"
+1 -1
Ver Arquivo
@@ -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"
+1 -1
Ver Arquivo
@@ -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)
+15 -1
Ver Arquivo
@@ -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
+13 -1
Ver Arquivo
@@ -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;
+31
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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`.
@@ -233,6 +244,10 @@ To build the most up-to-date version of the client:
.. note:: On Mac OS X, you need to specify ``-DCMAKE_INSTALL_PREFIX=target``, .. note:: On Mac OS X, you need to specify ``-DCMAKE_INSTALL_PREFIX=target``,
where ``target`` is a private location, i.e. in parallel to your build 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``.
+84
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
+9
Ver Arquivo
@@ -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)
+7
Ver Arquivo
@@ -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
+66 -37
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -11,4 +11,4 @@ Qt 4.4.x.
## License ## License
LGPL MIT
+5
Ver Arquivo
@@ -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}
+6 -12
Ver Arquivo
@@ -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);
+3 -3
Ver Arquivo
@@ -321,7 +321,7 @@ void AccountSettings::slotFolderWizardAccepted()
// The user already accepted the selective sync dialog. everything is in the white list // 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()
+1 -1
Ver Arquivo
@@ -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;
} }
+1 -1
Ver Arquivo
@@ -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);
} }
} }
+122 -3
Ver Arquivo
@@ -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
+40 -1
Ver Arquivo
@@ -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
+4 -19
Ver Arquivo
@@ -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();
} }
-1
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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;
+4
Ver Arquivo
@@ -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;
+29 -13
Ver Arquivo
@@ -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();
+48 -30
Ver Arquivo
@@ -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)
{ {
+3 -1
Ver Arquivo
@@ -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;
+1 -1
Ver Arquivo
@@ -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();
+3
Ver Arquivo
@@ -0,0 +1,3 @@
#include "winuser.h"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "owncloud.exe.manifest-mingw"
+3 -3
Ver Arquivo
@@ -66,7 +66,7 @@ void OcsJob::start()
req.setRawHeader("Ocs-APIREQUEST", "true"); req.setRawHeader("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);
+10
Ver Arquivo
@@ -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>
+3
Ver Arquivo
@@ -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();
}
} }
} }
} }
+27 -5
Ver Arquivo
@@ -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)
+3
Ver Arquivo
@@ -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;
}; };
} }
+1 -1
Ver Arquivo
@@ -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();
} }
+2 -2
Ver Arquivo
@@ -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;
+81
Ver Arquivo
@@ -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
+55
Ver Arquivo
@@ -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
+2
Ver Arquivo
@@ -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;
} }
+10 -1
Ver Arquivo
@@ -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();
+4
Ver Arquivo
@@ -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
+3 -1
Ver Arquivo
@@ -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
}; };
+2
Ver Arquivo
@@ -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
+4
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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;
}; };
+6
Ver Arquivo
@@ -107,4 +107,10 @@ QByteArray Capabilities::uploadChecksumType() const
return QByteArray(); return QByteArray();
} }
bool Capabilities::chunkingNg() const
{
return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
}
} }
+1
Ver Arquivo
@@ -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;
+6 -1
Ver Arquivo
@@ -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)
+18 -2
Ver Arquivo
@@ -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)
{ {
+12 -2
Ver Arquivo
@@ -27,7 +27,7 @@ namespace OCC {
* This is a job-like class to check that the server is up and that we are connected. * 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;
-1
Ver Arquivo
@@ -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;
-7
Ver Arquivo
@@ -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");
-1
Ver Arquivo
@@ -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;
+3 -18
Ver Arquivo
@@ -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());
+2 -1
Ver Arquivo
@@ -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
-11
Ver Arquivo
@@ -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");
-1
Ver Arquivo
@@ -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 -2
Ver Arquivo
@@ -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;
} }
+19 -3
Ver Arquivo
@@ -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);
+6
Ver Arquivo
@@ -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:
+17 -4
Ver Arquivo
@@ -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;
} }
+4 -2
Ver Arquivo
@@ -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
}; };
+4 -3
Ver Arquivo
@@ -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 "
+1
Ver Arquivo
@@ -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()
+3 -2
Ver Arquivo
@@ -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.
+4 -1
Ver Arquivo
@@ -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 ) {
+2
Ver Arquivo
@@ -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;
+9 -1
Ver Arquivo
@@ -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 ) {
+4
Ver Arquivo
@@ -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;
+71 -403
Ver Arquivo
@@ -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 &copy)
{
// Normally, copy == _item, but when it comes from the UpdateMTimeAndETagJob, we need to do
// some updates
_item->_etag = copy._etag;
_item->_fileId = copy._fileId;
_item->_requestDuration = _duration.elapsed(); _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 &copy)
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
Ver Arquivo
@@ -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);
};
} }
+488
Ver Arquivo
@@ -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);
}
}
+375
Ver Arquivo
@@ -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);
}
}
+21 -4
Ver Arquivo
@@ -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");
} }
+7 -7
Ver Arquivo
@@ -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");
} }
+4 -4
Ver Arquivo
@@ -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) {
+1 -1
Ver Arquivo
@@ -272,7 +272,7 @@ SyncFileStatus SyncFileStatusTracker::resolveSyncAndErrorStatus(const QString &r
} }
if (isShared) if (isShared)
status.setSharedWithMe(true); status.setShared(true);
return status; return status;
} }
+9
Ver Arquivo
@@ -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 "
+38
Ver Arquivo
@@ -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
+12
Ver Arquivo
@@ -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
+3
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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 &currentRemoteState() { return _remoteRootFileInfo; } FileInfo &currentRemoteState() { 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:
+78
Ver Arquivo
@@ -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"
+1 -1
Ver Arquivo
@@ -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);
+25 -23
Ver Arquivo
@@ -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)
+7 -1
Ver Arquivo
@@ -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());
} }
+1 -1
Ver Arquivo
@@ -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)") );
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff

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