1
0
mirror of https://github.com/chylex/Nextcloud-Desktop.git synced 2026-04-05 14:34:16 +02:00

Compare commits

..

479 Commits

Author SHA1 Message Date
Klaas Freitag
046d180d2f Rather count the blacklist items in the database.
Use database functions to count the entries rather than trying to
compute it from the progress items the widget got.
2013-12-03 14:48:49 +01:00
Klaas Freitag
278e76b774 Add blackListEntryCount method. 2013-12-03 14:48:49 +01:00
Olivier Goffart
09d850bfaa When the temporary file is the same size as the file we would download, just keep it
Don't download the file if the temporary file is complete.

else, we get error from server: Requested Range not satisfiable
2013-12-03 14:44:04 +01:00
Klaas Freitag
f0a6047ecf Add a button to acitivity view to clean the blacklist. 2013-12-03 14:04:01 +01:00
Klaas Freitag
e8ffb17b3b Add a slot to wipe the folder's blacklist. 2013-12-03 14:04:01 +01:00
Klaas Freitag
09d4fa2127 Fixed wording, do not expose the term blacklist to users. 2013-12-03 14:04:01 +01:00
Daniel Molkentin
f521301c51 Add plugins in install_qt4_executable to ensure the lib paths get fixed up
Fixes #1228
2013-12-03 13:33:50 +01:00
Olivier Goffart
de5161137f remove unneeded file 2013-12-03 13:11:41 +01:00
Klaas Freitag
1abfd4ba44 Fixed wording, removed the term "problem". 2013-12-03 12:31:28 +01:00
Klaas Freitag
ecf45856e1 Add missing call to CloseHandle on the win32 platform.
This fixes bug mirall#1236 and also mirall#1237
Also some minor cleanups.
2013-12-03 10:08:17 +01:00
Mr. Jenkins
148b22f53d [tx-robot] updated from transifex 2013-12-02 17:13:40 -05:00
Thomas Müller
ec075a6d2a removing libiniparder.dll and libdl.dll from NSNIS installer 2013-12-02 13:20:10 +01:00
Thomas Müller
6923f26597 Adding missing \ on zypper install command 2013-12-02 11:37:06 +01:00
Mr. Jenkins
206c62a171 [tx-robot] updated from transifex 2013-11-29 13:11:24 -05:00
Klaas Freitag
c03b2bbc87 Make unblacklisting depending on Up or Download
Compare modtime in case of uploading and ETag in case of downloading
as they are the correct indicators.
2013-11-29 16:16:04 +01:00
Klaas Freitag
2f708c0877 Move checkAccountExists out of gui class. 2013-11-29 16:16:04 +01:00
Olivier Goffart
58eb000163 Make the "details" manu entry work
Fix #1214
2013-11-29 11:18:59 +01:00
Olivier Goffart
f5f56e45c0 Fix pausing one of the folder pauses all foder after it while restarting. 2013-11-29 10:57:34 +01:00
Klaas Freitag
951ac79a68 Destinguish between rename and move in wording. 2013-11-28 10:58:04 +01:00
Klaas Freitag
ce47adb482 push version to 1.5.0beta1 2013-11-28 10:58:04 +01:00
Olivier Goffart
ef8fe11f5a Do not write the directory etag in the database in case of soft error
Else we won't retry next time as we think nothing has changed.
2013-11-28 10:01:30 +01:00
Olivier Goffart
30db533cea Stop iterating files when aborting. 2013-11-28 10:01:30 +01:00
Olivier Goffart
99eeaa0db5 no need to set twice the error string
done() is already taking care of that
2013-11-28 10:01:30 +01:00
Klaas Freitag
80a01ecff3 Use the activity icon in settings dialog. 2013-11-28 09:47:49 +01:00
Klaas Freitag
5f3ce25ccf Push version to 1.4.90 2013-11-28 09:44:09 +01:00
Klaas Freitag
0d85810c23 Change tab name in settingsdialog to Activity rather than Status. 2013-11-28 09:44:09 +01:00
Mr. Jenkins
4626ef1ffb [tx-robot] updated from transifex 2013-11-27 11:20:43 -05:00
Klaas Freitag
23007613a9 Fix progress bar in new propagator. 2013-11-27 16:48:16 +01:00
Daniel Molkentin
0fc51704f5 Enable accessibility on OS X
Fixes #736
2013-11-27 15:43:47 +01:00
Daniel Molkentin
46a403eb02 Report connection errors explicitly on explicit sign in attempts
Implicit connection attempts fail silently. This is roughly what
users expect from e.g. mail clients.

Fixes #1205.
2013-11-27 14:15:56 +01:00
Daniel Molkentin
e719e80409 ConnectionValidator: Account::setState no longer takes a bool
This makes the client come up with the correct state when the
initial connect fails.
2013-11-27 14:12:30 +01:00
Daniel Molkentin
310278f580 Account: Swap connected and disconnected state
Also, avoid full integer enumeration
2013-11-27 14:11:10 +01:00
Klaas Freitag
59072a81f1 Put detailed sync status widget into ButtonGroup for common style. 2013-11-26 14:27:32 +01:00
Klaas Freitag
d55bffb319 Removed not longer needed Info button.
The detailed sync status is now in the same dialog in another tab.
2013-11-26 14:15:37 +01:00
Klaas Freitag
232cbc45b5 Simplify progress and error signals again.
Now handle everything through the progress signal and let the errors
be progressed in the done slot.

Remove unused code, fix sorting of the list, more efficiency.

This fixes #916
2013-11-26 14:02:11 +01:00
Klaas Freitag
a88d45bff4 Comment some unused code. 2013-11-26 14:02:11 +01:00
Klaas Freitag
090e474d70 Add a couple of missing progress signals. 2013-11-26 14:02:11 +01:00
Klaas Freitag
4f7546768b Do not blacklist Softerrors. 2013-11-26 14:02:11 +01:00
Klaas Freitag
7a3c086be2 Display a proper error message for missing sync directory on server.
this fixes bug mirall#1149
2013-11-26 14:02:11 +01:00
Olivier Goffart
5c8b6ed902 Fix Remove -gzip from etag
Remove it from both "foo"-gzip  and "foo-gzip"

See issue #1195
2013-11-26 12:10:52 +01:00
dragotin
cb6918f3d5 Merge pull request #1203 from pascalBokBok/patch-1
Remove spammy "sync starting" notification on every program start.

Thanks!
2013-11-26 02:39:08 -08:00
pascalBokBok
489cc8aa29 Remove spammy "sync starting" notification on every program start. 2013-11-26 10:01:36 +01:00
Mr. Jenkins
d8a8d5da63 [tx-robot] updated from transifex 2013-11-26 03:05:08 -05:00
Daniel Molkentin
0d85dcdd9e Toggle Settings window when clicking on the icon
This restores the old behavior for every DE but OS X and Ubuntu Unity
(which respond to all clicks by opening the menu).

Discussed in #896
2013-11-26 03:15:03 +01:00
Daniel Molkentin
7eddff39e7 Wizard: Allow /index.php or /remote.php/webdav in URLs.
We will strip those. Also works on paste.

Fixes #349
2013-11-26 02:32:04 +01:00
Daniel Molkentin
81e47b0896 Folder wizard: sanitize error detection
* Wrap text properly
* Format multiple warnings as bullet points
* Use 'Folder' instead of 'Directory' everywhere
* Fix false positives when checking if one directory contains another
* Fix logic errors in target folder warning detection

Fixes #1201
2013-11-26 02:03:00 +01:00
Daniel Molkentin
ef0c7348ad Account Settings: Fix label 2013-11-26 00:04:50 +01:00
Daniel Molkentin
050bb55f1e Wizard: Do not start from scratch if the initial folder is non-default
Before, the folder was initialized to "ownCloud" in any case,
which lead the wizard to conclude it had to sync everything anew,
because the location moved -- even if the folder location was manually
corrected.

Fixes parts of #1172
2013-11-26 00:04:33 +01:00
Markus Goetz
fa715ce135 Propagator: Open download file as Unbuffered 2013-11-25 19:25:19 +01:00
Markus Goetz
911e0bdd6e Propagator: Check write errors when downloading 2013-11-25 19:25:19 +01:00
Olivier Goffart
fa0f773fcb Separate the case of file changing durng upload in the chunk or non chunk case.
If the file is changed between chunk, it is easy and we can just retry as the
file has not been changed on the server.
But if the file is changed after it has been updated on the server, we must still
update the database with the etag. (and possibly delete the partial file)

Relates to issue #1002
2013-11-25 19:16:54 +01:00
Markus Goetz
ac8296fb94 Propagator: Check E-Tag when resuming
Should fix #756
2013-11-25 19:01:04 +01:00
Daniel Molkentin
f47ce2fea6 Account Settings: Set initial button state correctly
Fixes #1185
2013-11-25 17:56:09 +01:00
Klaas Freitag
33ff6b3934 Even if problems occured show the Ok-Icon in the setup dialog.
Fixes bug #942.
2013-11-25 17:49:59 +01:00
Olivier Goffart
ca3d8ab193 Add one case of missing -gzip removal 2013-11-25 17:48:57 +01:00
Daniel Molkentin
d85009a2e9 Account Settings: fix connect error
Fixes #1198
2013-11-25 17:34:40 +01:00
Markus Goetz
72d2ac09e3 Propagator: Don't ignore error if no HTTP error code 2013-11-25 16:37:48 +01:00
Olivier Goffart
6b7da798b8 Remove -gzip from Etag
Fix #1195
2013-11-25 16:30:13 +01:00
Klaas Freitag
2e4043b498 Show proper error message and icon according to error class. 2013-11-25 16:18:07 +01:00
Klaas Freitag
dc29046d61 Add new progressProblem signal and slots.
Now the sync problems are handled differently than the sync progress
to ease error message handling and stuff.
2013-11-25 16:18:07 +01:00
Klaas Freitag
0c6dca25c4 Register meta type for SyncProblem 2013-11-25 16:18:07 +01:00
Klaas Freitag
1a3f246c46 Add new Error Types to progress: Soft, Normal, Fatal. 2013-11-25 16:18:07 +01:00
Daniel Molkentin
ad6c42b031 Wizard: let us handle/ignore credential failures 2013-11-25 15:50:19 +01:00
Daniel Molkentin
9ddedf81ac Cleanup: "Use QMutexLocker" 2013-11-25 15:34:17 +01:00
Daniel Molkentin
685c13dead Distiguish "Signed out" from "Disconnected" 2013-11-25 15:34:17 +01:00
Daniel Molkentin
c25c7daca7 docs: Add infos about installing the sql plugin and module
Fixes #1184
2013-11-25 15:34:17 +01:00
Daniel Molkentin
2a17a2a102 Remove credential-exposing debug output 2013-11-25 15:34:16 +01:00
Daniel Molkentin
4e22fff427 Introduce online/offline state, accessible via GUI 2013-11-25 15:34:16 +01:00
Daniel Molkentin
6165c38289 Fix indentation 2013-11-25 15:34:16 +01:00
Daniel Molkentin
f554cca3d6 Fix initial state of quota info class. 2013-11-25 15:34:16 +01:00
Daniel Molkentin
67132326d2 Prefix tooltips with app name 2013-11-25 15:34:16 +01:00
Daniel Molkentin
ea2b5fb29c Query credentials when needed. Put the account offline if user aborts.
This is only implemented for HTTP auth. Shibboleth still does its own thing.
2013-11-25 15:34:16 +01:00
Daniel Molkentin
0a2861a731 Disable quota polling when default account does not exist or is offline 2013-11-25 15:34:16 +01:00
Olivier Goffart
6f17131e3c Fix mutex usage in the journal
All public function must lock the mutex. And therefore none of the journal
function may call public function because the mutex is already locked.

So have a public commit that lock the mutex,  and a private  commitInternal
that assume the mutex is locked
2013-11-25 15:11:37 +01:00
Olivier Goffart
ca3885de2a Fix some SQL error and warning
Such as:
 Error opening the db:  "Driver not loaded Driver not loaded"
or
 QSqlDatabasePrivate::removeDatabase: connection '...' is still in use, all queries will cease to wor

We need to clear the QSqlDatabase _db handle before calling removeDatabase.
And we also need to give a different name to different folder database, just to be sure
2013-11-25 15:07:58 +01:00
Daniel Molkentin
5d6700c68d Merge pull request #1165 from csware/startmenu-according-to-ms-guidelines
Startmenu according to ms guidelines
2013-11-25 04:27:43 -08:00
Daniel Molkentin
37d6f6eeab Build on OS X 2013-11-25 10:54:18 +01:00
Klaas Freitag
fd1552f7a0 Handle SoftError and show blacklisted files. 2013-11-24 22:27:11 +01:00
Klaas Freitag
055a8d7e74 Do not display error messages if user aborts the sync run. 2013-11-24 22:26:50 +01:00
Klaas Freitag
1964e60eb0 Do not show an error message if user aborted. Also CSYNC_STATUS fixes. 2013-11-24 22:21:29 +01:00
Klaas Freitag
11acfde55a Refresh the Protocol widget when the dialog is raised. 2013-11-24 22:20:43 +01:00
Mr. Jenkins
3a1c6429ab [tx-robot] updated from transifex 2013-11-23 23:14:03 -05:00
Klaas Freitag
ecb2444923 Handle changing source file in upload correctly.
Delete the file on the server if the source file is new, but
the source did not arrive completely within a timespan.
2013-11-22 19:45:26 +01:00
Klaas Freitag
65bd4be16e Make sure all queries are initialized on our database object.
Since we use a database with the non default name, we need to do that,
otherwise the query is initialized on the default db which is not open
in our case.
2013-11-22 15:37:35 +01:00
Mr. Jenkins
39e48d3d01 [tx-robot] updated from transifex 2013-11-21 20:06:22 -05:00
Mr. Jenkins
c0b3672f32 [tx-robot] updated from transifex 2013-11-21 08:09:18 -05:00
Klaas Freitag
8d2950f66c Enable the overall file count in progress again. 2013-11-21 11:37:47 +01:00
Klaas Freitag
55e82ee4c1 Made transaction management a bit more transparent. Some fixes. 2013-11-21 11:13:58 +01:00
Klaas Freitag
aa17be40cc Some database code cleanups. 2013-11-20 18:19:14 +01:00
Klaas Freitag
97c661c909 Cleanups 2013-11-20 14:59:58 +01:00
Klaas Freitag
2767e7084a Minor cleanups. 2013-11-20 14:27:44 +01:00
Klaas Freitag
5900b1ad25 Add blacklisting for files with error conditions. 2013-11-20 14:27:44 +01:00
Klaas Freitag
20b9ae757d Add a http status code varialbe to the sync item object. 2013-11-20 14:27:44 +01:00
Klaas Freitag
aff2dd9f44 The IGNORE statement is handled further down the code. 2013-11-20 14:27:44 +01:00
Klaas Freitag
e30c484a7a Ignore the SIGPIPE signal as that disturbs debugging with Qt Creator.
It is questionable why this happens and if this patch really helps,
or if it might have side effects. Input appreciated :)
2013-11-20 14:27:44 +01:00
Klaas Freitag
0f6dd8748f Fixed some header wording. 2013-11-20 14:27:44 +01:00
Klaas Freitag
a342f63fdf Changed wording: Replaced Protocol against Status. 2013-11-20 14:27:44 +01:00
Daniel Molkentin
5c4d240c66 Build on mac with Qt 5
I still had this patch on disk.
2013-11-19 14:41:40 +01:00
Markus Goetz
e551e92e13 Downloads: Ignore file if no ETag was sent
Fixes part of #970
2013-11-19 12:44:25 +01:00
Markus Goetz
b98d97a96d SyncJournalDb: Fix warnings
I got those warnings for the latter sync runs:
11-19 10:58:15:997 QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.
11-19 10:58:15:997 QSqlDatabasePrivate::addDatabase: duplicate connection name 'qt_sql_default_connection', old connection removed.
2013-11-19 11:28:08 +01:00
Klaas Freitag
f30ac49264 Cached more queries, used safe QSqlQuery pattern 2013-11-18 13:02:09 +01:00
Klaas Freitag
49ba252fff Database initialize code cleanup.
If the QSqlQuery class is called with a query in the constructor, the query
is executed immediately. In fact, we executed each query twice before.
Later on we might want to implement a wrapper clas around the query.
2013-11-18 10:02:33 +01:00
Klaas Freitag
69269f8f75 Use transactions to speed up database access. 2013-11-18 10:02:32 +01:00
Klaas Freitag
e73730cb94 Close database after retrieval of file record count.
As the csync updater opens the database itself, it is cleaner to close
the db before and open it again after csync has finished.
Added a close method to the journal class.
2013-11-18 10:02:32 +01:00
Klaas Freitag
098e04c13f Set PRAGMA synchronous to NORMAL 2013-11-18 10:02:32 +01:00
Klaas Freitag
b0f6628584 Moved implementation of start() to cpp file. 2013-11-18 10:02:32 +01:00
Klaas Freitag
42f6867329 Add some useful logging. 2013-11-18 10:02:32 +01:00
Daniel Molkentin
0a9a3d8f04 Cleanup in folderman 2013-11-18 09:37:09 +01:00
Daniel Molkentin
ec850e83b9 Folders need to be setup before initializing the GUI
This fixes the incomplete context menu
2013-11-18 09:37:09 +01:00
Markus Goetz
08665d6ac2 Settings: Don't specify encoding
QSettings automatically does escaping. When UTF-8 was set, I
got all kind of problems with the QByteArray for the geometry
and the umlaut in my name for the proxy user.
2013-11-18 09:30:42 +01:00
Markus Goetz
4194a078d5 Revert "set utf8 for the central settings file"
This reverts commit 6758c89130.
2013-11-15 15:45:21 +01:00
Daniel Molkentin
6758c89130 set utf8 for the central settings file 2013-11-15 14:43:48 +01:00
Olivier Goffart
c15a1eedd1 Only update the db for ETAG if the etag has changed for directories 2013-11-15 13:53:18 +01:00
Klaas Freitag
bf6e1f10ce Prepare the queries after the database was migrated. 2013-11-15 11:21:27 +01:00
Klaas Freitag
c22f8a47f1 Initialise values properly, avoid warning. 2013-11-15 10:32:13 +01:00
Klaas Freitag
858facb5e0 Use precompiled database statements. 2013-11-15 10:32:13 +01:00
Olivier Goffart
b610dd2754 Revert "Partial revert of "CMake: Remove cmake module""
That patch reverted the oposite of what it should have reverted :-)

This reverts commit b22ef9f8fa.
2013-11-15 10:20:12 +01:00
Olivier Goffart
b3972a5ba8 Do not fetch the file id in the propagator with a HEAD
Normally, the fileid comes with the PUT or the GET.
If it did not comes with the PUT, it means the server do not support
fileid,  we should not query it with a useless HEAD.

Also, in case the fileid changes (which it should not)  update anyway
to the new fileid in the db
2013-11-15 10:18:19 +01:00
Daniel Molkentin
36e8273da0 fix mac builds 2013-11-14 20:53:56 +01:00
Daniel Molkentin
2f4de3cc48 Build fix 2013-11-14 20:41:23 +01:00
Daniel Molkentin
b22ef9f8fa Partial revert of "CMake: Remove cmake module"
This reverts commit 63188667bb.
2013-11-14 19:34:12 +01:00
Daniel Molkentin
e20f39f040 Another cleanup 2013-11-14 19:31:38 +01:00
Daniel Molkentin
63188667bb CMake: Remove cmake module 2013-11-14 19:26:09 +01:00
Daniel Molkentin
c2eaf5e627 owncloud is no longer a dl'opened module, but linked into csync 2013-11-14 19:22:22 +01:00
Daniel Molkentin
7ba8983f0a Change all Network Jobs to use start() 2013-11-14 19:20:19 +01:00
Markus Goetz
088aa6ebdd Wizard: Default button to Next 2013-11-14 19:13:59 +01:00
Markus Goetz
767ec4ed59 Wizard: Support redirects again 2013-11-14 17:54:38 +01:00
Markus Goetz
b12b8c981d Wizard: Also reset timeout on forwards 2013-11-14 17:23:56 +01:00
Markus Goetz
b499a62593 Wizard: Timeout handling for CheckServerJob 2013-11-14 17:23:56 +01:00
Markus Goetz
fa0a2764a4 Fix changing URL in wizard 2013-11-14 17:23:56 +01:00
Markus Goetz
a537a98f03 Tray menu: Populate at start 2013-11-14 17:23:56 +01:00
Sven Strickroth
ed5b0973dd Do not create folder for single link
Signed-off-by: Sven Strickroth <email@cs-ware.de>
2013-11-14 15:45:14 +01:00
Olivier Goffart
e8e27b61f6 Revert the changes that fetch the file id in the propagator.
We don't want to fetch the file id in the propagator.

Revert "Put item member variable to base class."
This reverts commit f7aa2aa348.

Revert "Add isValidFileId and getFileIdPropget methods."
This reverts commit ccd254abba.
2013-11-14 14:59:03 +01:00
Sven Strickroth
336e22233d Do not create uninstall shortcut in start menu
According to Microsoft Design guidelines (http://msdn.microsoft.com/en-us/library/windows/desktop/aa511447.aspx) no icons for uninstallers should be created.

Signed-off-by: Sven Strickroth <email@cs-ware.de>
2013-11-14 14:40:21 +01:00
Daniel Molkentin
578431c791 Fix building owncloudcmd in case of theming 2013-11-14 14:20:09 +01:00
Daniel Molkentin
ead0f8d029 Update build docs 2013-11-14 12:36:09 +01:00
Klaas Freitag
f7aa2aa348 Put item member variable to base class. 2013-11-14 11:37:32 +01:00
Klaas Freitag
ccd254abba Add isValidFileId and getFileIdPropget methods. 2013-11-14 11:37:08 +01:00
Daniel Molkentin
56de183155 Merge pull request #1160 from csware/exclude-office-tempfiles
Add another Office lock/temporary file pattern
2013-11-13 15:52:37 -08:00
Daniel Molkentin
c8256eea0e Merge pull request #1162 from csware/startmenu-according-to-ms-guidelines
Startmenu according to ms guidelines
2013-11-13 15:52:05 -08:00
Sven Strickroth
b026bb308d Do not create folder for single link
Signed-off-by: Sven Strickroth <email@cs-ware.de>
2013-11-14 00:45:18 +01:00
Sven Strickroth
6bd4d367d4 Do not create uninstall shortcut in start menu
According to Microsoft Design guidelines (http://msdn.microsoft.com/en-us/library/windows/desktop/aa511447.aspx) no icons for uninstallers should be created.

Signed-off-by: Sven Strickroth <email@cs-ware.de>
2013-11-14 00:43:11 +01:00
Sven Strickroth
d401a62fd9 Add another Office lock/temporary file pattern
Experienced with Microsoft Office 2010.

Signed-off-by: Sven Strickroth <email@cs-ware.de>
2013-11-14 00:33:11 +01:00
Daniel Molkentin
8f61cc4041 Hint the OS to not show [?] in Wizards and Dialogs
Fixes #1156
2013-11-13 20:12:56 +01:00
Daniel Molkentin
21c9fc2d35 Cleanup 2013-11-13 19:11:46 +01:00
Daniel Molkentin
171572e400 Remember paused state throughout application restarts
Fixes #823
2013-11-13 18:58:35 +01:00
Daniel Molkentin
475a4f2676 Windows: ship QtSql lib 2013-11-13 14:32:09 +01:00
Olivier Goffart
52e01b39d6 Adapt to csync 'md5'->'etag' change 2013-11-13 14:28:41 +01:00
Daniel Molkentin
af1dcfd179 Compile fix 2013-11-13 14:24:02 +01:00
Daniel Molkentin
233a6908dd Partial revert "Compile with Qt5"
This reverts commit 878ae56a71.
2013-11-13 14:19:01 +01:00
Daniel Molkentin
ae83c12f05 Fix compiler warning 2013-11-13 14:19:01 +01:00
Daniel Molkentin
878ae56a71 Compile with Qt5 2013-11-13 14:00:12 +01:00
Daniel Molkentin
2ff4ebc72f Make user() available throught AbsctractCredentials 2013-11-13 13:59:35 +01:00
Daniel Molkentin
ca79d3b437 Merge remote-tracking branch 'origin/master' into account_refactoring 2013-11-13 13:55:58 +01:00
Klaas Freitag
14081a197b Call ne_sock_init and ne_sock_exit to initialize neons SSL stack.
See https://github.com/owncloud/mirall/issues/1115 for more details on
why that is done here now.
2013-11-11 17:47:24 +01:00
Daniel Molkentin
9fe2549938 Add user info if the auth backends provides it 2013-11-11 16:57:15 +01:00
Klaas Freitag
e0a50d4bb9 PostSyncCleanup added: Remove superfluous entries from database after
sync.
2013-11-11 16:45:40 +01:00
Daniel Molkentin
ad1c1f4130 Remove legacy code 2013-11-11 15:36:49 +01:00
Daniel Molkentin
121b18bf70 QtKeychain is not a hard dependency 2013-11-11 15:36:29 +01:00
Daniel Molkentin
22ed29a30b Remove obsolete credentialstore class 2013-11-11 15:24:07 +01:00
Daniel Molkentin
64b59f8679 Don't use integration directory 2013-11-11 15:22:51 +01:00
Daniel Molkentin
406ed5a0c0 Make new folder wizard work again 2013-11-11 15:20:07 +01:00
Daniel Molkentin
d5081f4328 Fix logic 2013-11-11 14:02:54 +01:00
Daniel Molkentin
95d7afb0d0 cleanups 2013-11-11 10:57:33 +01:00
Klaas Freitag
2b2987d962 Make sure to rebuild the recent changes menu. 2013-11-11 10:06:14 +01:00
Klaas Freitag
2eb77445be Add rename actions to progress dispatching.
With that fix rename operations are shown as such in the progress
window and in bubble help etc.
2013-11-08 16:21:59 +01:00
Daniel Molkentin
12b1b38351 Fix connect 2013-11-08 14:02:13 +01:00
Daniel Molkentin
b0abb6362a Don't set the content header length
This breaks POSTS and is not needed in QNAM, since QNAM
does this itself internally.
2013-11-08 14:01:56 +01:00
Klaas Freitag
a458ffd472 Add missing return in error case. 2013-11-08 12:56:54 +01:00
Daniel Molkentin
60b6f520e7 Make Shibboleth browser aware of Accounts
Needed for SSL error handling. It's also more consistent.
2013-11-07 18:47:38 +01:00
Daniel Molkentin
8d0d5b4077 Make redirects work 2013-11-07 18:46:57 +01:00
Daniel Molkentin
5c9a6afb82 Fix crash when opening settings without a valid account 2013-11-07 18:46:28 +01:00
Klaas Freitag
162acc1cc2 Fix conflict file handling, create a proper conflict file name.
Before this fix, the conflict file name was set in the file name
variable which lead to the problem that the subsequent rename of
the temp file to the final final filename was moving the temp file on
the conflict file. No final file was there.
2013-11-07 16:53:22 +01:00
Daniel Molkentin
64ef43d918 Fix the key used for storing the password 2013-11-07 15:59:58 +01:00
Daniel Molkentin
33c3d2a7d0 Wizard: Initialize account credentials.
Required to be able to readily retrieve the user name later on
2013-11-07 15:24:49 +01:00
Daniel Molkentin
1238ab4f69 fix broken connect() statement 2013-11-07 12:50:55 +01:00
Daniel Molkentin
a42ff5a07c Show floating point prec. in account settings, too 2013-11-07 12:50:39 +01:00
Daniel Molkentin
aa4b6bd4ea Readd "online" notion as a state in account.
This gets updated by the ConnectionValidator. Not sure if that's
the best choice, but it mimmicks the old behavior the closest.
2013-11-07 12:22:17 +01:00
Daniel Molkentin
478ba9c5ef Fix approved SSL certificates being forgotten 2013-11-07 12:04:45 +01:00
Klaas Freitag
c58e9d17a8 Again minor doc changes. 2013-11-07 10:29:27 +01:00
Klaas Freitag
41ccbc0334 Minor docu change. 2013-11-07 10:28:14 +01:00
Daniel Molkentin
bd46ad56fc Add new quotainfo class 2013-11-07 10:14:38 +01:00
Daniel Molkentin
bde5e86c50 Reformatting fixes 2013-11-07 10:14:25 +01:00
Daniel Molkentin
ec0f01fd7c Ensure an SSL Error Handler is always available 2013-11-07 10:14:12 +01:00
Klaas Freitag
fe4c1cc35a Remove straycat code from mirall. 2013-11-06 14:20:44 +01:00
Klaas Freitag
b6152c19d6 Adjust generic build documentation to the current state. 2013-11-06 11:13:55 +01:00
Daniel Molkentin
800abbf8b7 Make http credentials work 2013-11-05 18:16:00 +01:00
Daniel Molkentin
3af622d535 Make quota display work 2013-11-05 18:15:47 +01:00
Klaas Freitag
ee4cbf52dc Remove straycats collected in the reconcile phase from journal. 2013-11-05 17:53:01 +01:00
Klaas Freitag
5cd2be058d Fix local rename. 2013-11-05 17:50:09 +01:00
Klaas Freitag
3a21edca2b Keep originalFile member as QString rather than C string.
This avoids encoding problems with interesting file names.
2013-11-05 17:48:51 +01:00
Klaas Freitag
c6a926842a Fix recursivley flag misinterpretation. 2013-11-05 17:47:51 +01:00
Daniel Molkentin
440b5164ad Build fix 2013-11-04 16:41:59 +01:00
Daniel Molkentin
cc5f17a7d2 Merge remote-tracking branch 'origin/master' into account_refactoring
Conflicts:
	src/mirall/accountsettings.cpp
	src/mirall/folder.cpp
2013-11-04 16:38:55 +01:00
Daniel Molkentin
85d5b82811 - Make saving/restoring accounts work.
- Prepare for fetching quota
2013-11-04 16:36:23 +01:00
Olivier Goffart
f0a1ac4346 Fix syncing folder with nothing in it
Do the start after connecting, as the finish may be imediate if there
is nothing to do.
2013-10-31 12:11:56 +01:00
Olivier Goffart
bdc39f9cc2 Allow folder that are prefix of another 2013-10-31 11:41:56 +01:00
Olivier Goffart
d3ae2f42a7 Reset the proxy module property at every sync
This is required for the fix for #713
Since the module properties are shared in global variables shared by
every folders, removing another folder may clean the proxy settings.
So we need to restore them at every sync
2013-10-31 11:39:43 +01:00
Olivier Goffart
df39ab0b2f Don't leak the system tray 2013-10-31 10:52:19 +01:00
Olivier Goffart
28833ee5ac Fix crash when removing folder while syncing.
We need to set _thread to 0 after destoying it
2013-10-31 10:51:45 +01:00
Klaas Freitag
c66a1d1895 Removed unused code from LocalRename 2013-10-30 18:14:33 +01:00
Klaas Freitag
cdee0dc1cf Added a PropagateLocalRename job to do local renames. 2013-10-30 17:37:34 +01:00
Klaas Freitag
a43c5fcfe8 Fix header name according to changes on server master. 2013-10-30 17:37:34 +01:00
Olivier Goffart
5c67f39476 Ignore error 404 on DELETE
This may happen if we have stale entries in the database
2013-10-30 17:33:06 +01:00
Olivier Goffart
fb47657b1f Make F6 in the account config sync the selected folder, for debugging purposes
Issue #409
2013-10-30 16:58:08 +01:00
Olivier Goffart
76d46af4b7 Revert "Implement a sync now button"
Actually, after discussion with the team, we decided this button is a bad idea

This reverts commit b56fcd8ebd.
2013-10-30 16:48:28 +01:00
Daniel Molkentin
86af2848dd Merge remote-tracking branch 'origin/master' into account_refactoring
Conflicts:
	src/mirall/accountsettings.cpp
2013-10-30 16:37:49 +01:00
Daniel Molkentin
4ca310b63b Remove ownCloudInfo for good 2013-10-30 16:33:15 +01:00
Daniel Molkentin
59bc1d8966 Credential Store is no longer static
It now belongs to HttpCredentials
2013-10-30 16:32:34 +01:00
Daniel Molkentin
14c2ff44f3 More porting to Account class
- Finish port of owncloud setup wizard to Account
- Deprive MirallConfigFile of the customHandle
2013-10-30 16:31:47 +01:00
Olivier Goffart
b56fcd8ebd Implement a sync now button
The button is in the account config, next to the "pause/resume" button

Fixes #409
2013-10-30 16:27:00 +01:00
Olivier Goffart
e38d6d974b Replace _Button* with _button
Identifiers starting with underscore and uppercase are reserved in C++.
2013-10-30 15:52:30 +01:00
Olivier Goffart
9500c5ffab Do not change the instruction anymore when we finish an item
The status is in the status.  Keep the original instruction so the
UI knows what to display for eath item
2013-10-30 10:46:05 +01:00
Olivier Goffart
b079cedbf5 Use the status instead of the instruction for finished items 2013-10-30 10:44:41 +01:00
Olivier Goffart
6e088e28f5 Adjust the mtime in case of fake conflicts
"Fake conflicts" happen when the etag changes on the server, and the
mtime changes on the client,  but the actual file is still exactly
the same.  We correctly detect them as false conflict and we do not
generate the conflict file for them.
But we should also update the local mtime to the server mtime so future
sync don't get confused.
2013-10-29 12:23:51 +01:00
Olivier Goffart
01e2743bae Always download the conflicted files.
Conflicts have a direction==NONE. And they need to be downloaded, not uploaded
2013-10-29 12:13:30 +01:00
Olivier Goffart
75ffa787a6 emit the EndDownload progress at the right place.
It was put in rename instead of download
2013-10-29 12:13:30 +01:00
Olivier Goffart
4ad9f34807 Save the UPDATED files in the database
Those are files that were detected as "resolved conflict".
They should have been a conflict because they both were new, or both
had their local mtime or remote etag modified, but the size and mtime
is the same on the server.  This typically happen when the database is removed.
Nothing will be done for those file, but we still need to update the database.
2013-10-29 12:13:30 +01:00
Daniel Molkentin
a91799a11c Make the setup wizard compile again
- introduces more jobs
- needs more cleaning up
2013-10-28 20:01:59 +01:00
Daniel Molkentin
44fd03c058 Merge pull request #1125 from iAndre89/patch-1
Update mingw32-cross-nsis-plugin-processes and mingw32-cross-nsis-plugin...
2013-10-28 09:20:09 -07:00
Andrea Sosso
083e75998f Update mingw32-cross-nsis-plugin-processes and mingw32-cross-nsis-plugin-uac 2013-10-28 17:17:10 +01:00
Olivier Goffart
adc47948a5 Add a comment explaining OwncloudPropagator::start 2013-10-28 17:00:27 +01:00
Olivier Goffart
6e886e28e9 Merge branch 'propagator-job'
Conflicts:
	src/mirall/owncloudpropagator.cpp
2013-10-28 16:26:25 +01:00
Klaas Freitag
e63fc184a5 Options parsing added. 2013-10-28 16:07:13 +01:00
Olivier Goffart
84a40dcb59 Refactor the new propagator in jobs
This makes the code (IMHO) more easy to understand, and will allow
even more easy parallelism
2013-10-28 15:58:35 +01:00
Olivier Goffart
8e90782107 Make owncloudcmd display the debug output 2013-10-28 15:45:37 +01:00
Klaas Freitag
68ba99b7f0 Fixed fileID parsing and improved logging. 2013-10-28 15:28:34 +01:00
Klaas Freitag
2fefc428a8 Write file Id to journal table.
Since this requires a change of existing sync journals, database migration
code was added.
2013-10-25 13:31:00 +02:00
Klaas Freitag
17220f2604 Query file Id from server if unknown. 2013-10-25 13:31:00 +02:00
Klaas Freitag
7a68961b25 Add handling for the file_id data to the sync items. 2013-10-25 13:31:00 +02:00
Daniel Molkentin
aa2baa45fb Cleanups, ownership for ssl error handler 2013-10-24 12:55:26 +02:00
Daniel Molkentin
eda5feb82c Bring back proper ssl handling 2013-10-24 00:29:08 +02:00
Daniel Molkentin
d2b445c80c Basic port, misses wizard 2013-10-23 14:48:53 +02:00
hefee
b4621e22e6 Merge pull request #1114 from owncloud/freebsd-inotify
Need inotify on FreeBSD for libsync
2013-10-23 02:36:12 -07:00
hefee
38679f79b5 Need inotify on FreeBSD for libsync 2013-10-23 01:24:27 +02:00
hefee
a927caf2b0 Fixing Utility::platform for all platforms 2013-10-23 00:59:29 +02:00
Daniel Molkentin
29c846a764 WIP: Dissolve owncloudinfo class 2013-10-21 21:42:52 +02:00
Jenkins for ownCloud
804c9fbd6f [tx-robot] updated from transifex 2013-10-21 12:01:30 -04:00
Jenkins for ownCloud
2edebdef08 [tx-robot] updated from transifex 2013-10-20 06:49:52 -04:00
Daniel Molkentin
a91ba0fd48 WIP: Move network tasks from ownCloudInfo into Job classes. 2013-10-18 12:24:29 +02:00
Daniel Molkentin
a25d55a265 Add QtJson to 3rdparty, use it in ownCloudInfo 2013-10-18 02:00:19 +02:00
Jenkins for ownCloud
c99f75b247 [tx-robot] updated from transifex 2013-10-17 12:48:20 -04:00
Olivier Goffart
0efbfb10aa Fix MOVE of directory.
Remove the right entry from the database (i.e: the original file name)
Fetch the etags of the sub entries
2013-10-17 13:06:39 +02:00
Olivier Goffart
cb8006b89f Fix comments of the propagation step.
Also, don't mark item as removed if the instruction is not REMOVE
2013-10-17 12:09:44 +02:00
Klaas Freitag
660469cbf5 Do not propagate a move of the Shared folder. 2013-10-17 10:48:31 +02:00
Daniel Molkentin
a06e551469 Merge pull request #1103 from Absolight/freebsd-port
Patches for FreeBSD
2013-10-16 08:52:46 -07:00
Olivier Goffart
746b86a1dd Save the progress db once a chunk has been upload
Allow to resume when we cancel the sync
2013-10-16 16:47:24 +02:00
Mathieu Arnold
1c594b6a8d FreeBSD doesn't have statvfs64, like MacOS X. 2013-10-16 15:57:59 +02:00
Mathieu Arnold
2b652422b9 Don't call make directly, it can have another name. 2013-10-16 15:57:05 +02:00
Olivier Goffart
313832de8d Put the progress database within the journal 2013-10-16 12:01:14 +02:00
Jenkins for ownCloud
250f281189 [tx-robot] updated from transifex 2013-10-15 23:26:29 -04:00
Olivier Goffart
2c63f7a24d Merge branch '1.4'
Conflicts:
	VERSION.cmake
	src/mirall/application.cpp
	src/mirall/application.h
	src/mirall/csyncthread.cpp
	src/mirall/folder.cpp
	src/mirall/folder.h
	src/mirall/folderman.cpp
2013-10-15 17:00:53 +02:00
Olivier Goffart
10fba886dc Do not elide the progress text
It's size is computed so the text fit, there is no need to alide it.

Fixes #1094
2013-10-15 14:58:34 +02:00
Daniel Molkentin
046d955f5c 1.4.2beta1 2013-10-14 20:21:07 +02:00
Klaas Freitag
d0d362664b Handle proxy on folder level, not folderman level. 2013-10-14 16:11:52 +02:00
Klaas Freitag
f841450dae Do append non empty lines not starting with a hash to ignores. 2013-10-14 16:11:52 +02:00
Klaas Freitag
a3927c5c2c Avoid quick flickering up of the ok-icon for the sync prepare state.
For the SyncPrepare phase now the icon that was displayed before is
kept. If the folder was disabled before, the sync icon is displayed.
2013-10-14 12:59:08 +02:00
Klaas Freitag
89cfa387cd Remove bogus html formatting tag. 2013-10-14 11:46:02 +02:00
Olivier Goffart
1fccb23442 Don't show desktop notification when the user don't want to
Fix #1093
2013-10-14 11:33:47 +02:00
Klaas Freitag
bfd50ffcd0 Do not show progress if nothing is transmitted, show number of deletes. 2013-10-14 11:16:17 +02:00
Jenkins for ownCloud
d2301c811e [tx-robot] updated from transifex 2013-10-13 19:29:34 -04:00
Klaas Freitag
4f2a171913 Avoid quick flickering up of the ok-icon for the sync prepare state.
For the SyncPrepare phase now the icon that was displayed before is
kept. If the folder was disabled before, the sync icon is displayed.
2013-10-11 17:54:26 +02:00
Daniel Molkentin
005d70a73c Proxy support: Reset useSystemConfiguration() in all cases but DefaultProxy
Fixes #1016
2013-10-11 14:25:35 +02:00
Klaas Freitag
592291cbcb Fix initialized after warning. 2013-10-11 12:10:47 +02:00
Klaas Freitag
7236bd7dd4 Make problem warnings not appearing in the tray.
This fixes bug #944: Do not show the warning icon in the tray.
2013-10-11 12:09:27 +02:00
Klaas Freitag
c02d5f41a5 Remove bogus html formatting tag. 2013-10-11 12:09:27 +02:00
Daniel Molkentin
5a7cd815ab Reconnect if network is unavailable after startup
Fixes: #1080
2013-10-11 11:43:23 +02:00
Jenkins for ownCloud
006be43c73 [tx-robot] updated from transifex 2013-10-10 22:18:01 -04:00
Daniel Molkentin
c2c01bccfc Fix literal occurances of "ownCloud" in GUI 2013-10-10 11:35:22 +02:00
Daniel Molkentin
2240039442 Fix literal occurances of "ownCloud" in GUI 2013-10-10 11:34:45 +02:00
Klaas Freitag
fb4728c7ee Handle sync enable flag correctly in folder scheduling.
This fixes bug #1083.

Conflicts:
	src/mirall/folder.cpp
2013-10-10 11:32:56 +02:00
Klaas Freitag
f34621578e Use qint64 rather than int64_t. 2013-10-09 14:33:24 +02:00
Klaas Freitag
5e50b1f1fd Add some progress meassuring logging. 2013-10-09 14:33:09 +02:00
Klaas Freitag
3c95d342ee Handle sync enable flag correctly in folder scheduling.
This fixes bug #1083.
2013-10-09 14:27:28 +02:00
Klaas Freitag
53ac5427a8 Integrate progress dialog into settings dialog. 2013-10-09 14:27:28 +02:00
Klaas Freitag
1ed8afba09 Minor changes. 2013-10-09 14:27:28 +02:00
Klaas Freitag
ef81a8a2ad Use qint64 rather than int64_t. 2013-10-08 15:41:15 +02:00
Klaas Freitag
fa9d1614e7 Add some progress meassuring logging. 2013-10-08 14:07:46 +02:00
Jenkins for ownCloud
fe46b588af [tx-robot] updated from transifex 2013-10-07 11:14:31 -04:00
Jenkins for ownCloud
290de7c430 [tx-robot] updated from transifex 2013-10-05 10:12:16 -04:00
Olivier Goffart
05fbfb520f Protect the sync database by a mutex since it is used by the thread 2013-10-04 21:05:46 +02:00
Daniel Molkentin
805e1330ad Compile with MinGW 2013-10-04 20:29:42 +02:00
Olivier Goffart
7c6fcf688c Fix the fact that some success was reported as error 2013-10-04 15:55:59 +02:00
Olivier Goffart
127055dd70 Refactor a bit the error reporting from the propagator
Add different classes of error failures.
Fatal error means the sync should be stopped.
SoftErrors are not to be displayed to the user.

We still need to make a classification of the errors.
2013-10-04 15:55:59 +02:00
Markus Goetz
f4929e849e CsyncThread: Activate recursive PROPFIND 2013-10-04 15:42:40 +02:00
Mackie Messer
ba9ac03b0b Put plugins in PlugIns, not Plugins on Mac
Requires 770539f0a5 on csync
2013-10-04 13:01:46 +02:00
Mackie Messer
0257f7e169 Add sqlite plugin for OS X 2013-10-04 13:01:46 +02:00
Daniel Molkentin
763cb43e46 Add qsqlite plugin on Windows 2013-10-04 12:45:17 +02:00
Olivier Goffart
8ed2588cdf Fix build with Qt5 2013-10-03 23:14:10 +02:00
Olivier Goffart
d8d2d36638 Fix SQL Query 2013-10-03 23:00:47 +02:00
Olivier Goffart
afc13e70b9 fix Qt5 test compilation 2013-10-03 22:51:40 +02:00
Olivier Goffart
baa9ba089c Save the database after each operation. 2013-10-03 22:41:12 +02:00
Olivier Goffart
3495b822a5 TMP 2013-10-03 20:01:07 +02:00
Daniel Molkentin
be88d425fc Fix connecting to the mysql db 2013-10-03 19:52:09 +02:00
Klaas Freitag
94a06cec5b WIP on the journal database. 2013-10-03 18:52:02 +02:00
Klaas Freitag
4008f6b309 Fix file status enum names. 2013-10-03 17:55:32 +02:00
Klaas Freitag
daac6886a0 Added method setFileRecord to update sync journal 2013-10-03 17:48:14 +02:00
Daniel Molkentin
e304dfd5b9 Add socketapi
Slightly amended by: Daniel Molkentin <danimo@owncloud.com>
2013-10-03 17:05:01 +02:00
Dominik Schmidt
d0af48c417 Add KDE tmp files to .gitignore 2013-10-03 17:05:00 +02:00
Klaas Freitag
2e3aabf99b Read header without prefix path now that it's in 3rdparty. 2013-10-03 16:47:48 +02:00
Klaas Freitag
59bf8740a0 Add c_jhash.h header from csync to 3rdparty. 2013-10-03 16:41:23 +02:00
Olivier Goffart
21cd57228e Fix build with Qt5
Missing SQL module
2013-10-03 16:29:47 +02:00
Klaas Freitag
4501c64e61 Do not include whole header of csync_exclude. 2013-10-03 16:22:31 +02:00
Klaas Freitag
273105e78b Handle new case SyncAbortRequested. 2013-10-03 16:21:54 +02:00
Klaas Freitag
2707116350 Minor fixes for file_status 2013-10-03 16:12:50 +02:00
Klaas Freitag
c7d30bae98 Handle new sync result enum. 2013-10-03 15:32:44 +02:00
Klaas Freitag
be328581a7 Implemented sync status function for overlay icons. 2013-10-03 15:29:10 +02:00
Klaas Freitag
c32bc27b3e Add sync journal database class that opens the csync db to read from it. 2013-10-03 15:29:10 +02:00
Klaas Freitag
0fef88a9b9 Some comments added. 2013-10-03 15:29:10 +02:00
Klaas Freitag
3e0fc56495 Dont show the [i] icon in the tray, see bug #942 2013-10-03 15:29:10 +02:00
Olivier Goffart
7ea3fc1533 Fix compilation of the test 2013-10-03 14:45:00 +02:00
Olivier Goffart
396ec4f888 Fix race condition in the logger.
Logs can come from multiple thread, we need to keep the _logstream
protected by a mutex
2013-10-03 14:37:13 +02:00
Olivier Goffart
79ea7c3eed Abort the sync asynchroniously
Don't block the GUI when clicking on pause

Mirall issue #968
2013-10-03 14:36:32 +02:00
Olivier Goffart
b18810f381 Implement abort with the new propagator
abort was implemented in csync after the new propagator was written,
so it was not working with the new propagator
2013-10-03 14:35:56 +02:00
Jenkins for ownCloud
51d9cf099c [tx-robot] updated from transifex 2013-10-03 08:09:15 -04:00
Daniel Molkentin
339ed20abc Utility::showInFileManager(): Substitute valid desktop file parameters 2013-10-03 12:05:00 +02:00
Klaas Freitag
44ed577992 List results for all folders, not only one. 2013-10-03 11:36:18 +02:00
Klaas Freitag
f6685accc3 Use subdirs for more xdg dirs to go through 2013-10-02 19:23:06 +02:00
Klaas Freitag
2c2e79c13d Consider the folder of items to delete from info window. 2013-10-02 19:23:06 +02:00
Daniel Molkentin
81961068a2 Utility: Move OS specific implementations into separate files 2013-10-02 19:15:56 +02:00
Daniel Molkentin
4e91a6450c Change the recent changes menu and file item dialog behavior
Recent Changes:
- "No items synced recently"
- Add separator to the details section
- Make items clickable (will open file location in file manager)
- If a file was deleted, disable it

File Item Dialog:
- Make items clickable (will open file location in file manager)

This is implemented via a new method Utility::showInFileManager(QString),
which goes through great lengths to use use the right file manager on all
OSes/WMs/DEs, and make sure it selects the file in question if possible.
This needs good testing, though.

Fixes #1044
2013-10-02 18:17:48 +02:00
Daniel Molkentin
3dc2547bb5 WIP 2013-10-02 18:16:32 +02:00
Olivier Goffart
47f299f0ee Send the last known etag to the server in a If-Match header
That way we avoid race conditions in case the file changed between the
update and the reconcile
2013-10-02 15:57:49 +02:00
Olivier Goffart
d135aab86c clear the etag in every cases, not only when we upload files 2013-10-02 15:57:49 +02:00
Olivier Goffart
e2a2b882bb Remove the unused errorDetails
It was not displayed to the user. Only use errorString instead.

Also report neon error to the user
2013-10-02 15:57:49 +02:00
Klaas Freitag
d8309a64cb Even more cleanup. 2013-10-02 15:55:15 +02:00
Klaas Freitag
55722099fa More refactoring: Logger and Logbrowser out of application class. 2013-10-02 15:29:24 +02:00
Jenkins for ownCloud
48abe62151 [tx-robot] updated from transifex 2013-10-02 09:08:33 -04:00
Klaas Freitag
2149814428 Fix/enhance user information about problems on startup.
Pass the error conditions to the gui class.
2013-10-01 18:25:43 +02:00
Klaas Freitag
ea1c951006 Refactoring: Moved all GUI related computing out of application class.
Created ownCloudGui class to contain all gui related stuff.
2013-10-01 13:58:01 +02:00
Klaas Freitag
4a96f9a5c9 Add Mirall namespace. 2013-10-01 13:58:01 +02:00
Klaas Freitag
fcc4151810 Fix sequence of directories in help text. 2013-10-01 13:58:01 +02:00
Klaas Freitag
82cd79c004 Add unify defines to header. 2013-10-01 13:58:00 +02:00
Klaas Freitag
74983af3b4 Make evaluateSync public, review pls. 2013-10-01 13:58:00 +02:00
Klaas Freitag
f5bcb11fe9 Add connected server to error message. 2013-10-01 13:58:00 +02:00
Klaas Freitag
f6f1c638a5 Allow Constructor to be public, for synclibrary 2013-10-01 13:58:00 +02:00
Daniel Molkentin
e250672e4a CMake: libhttplib -> httplib 2013-09-30 23:16:04 +02:00
Jenkins for ownCloud
13093f5de3 [tx-robot] updated from transifex 2013-09-30 09:04:25 -04:00
Daniel Molkentin
9b62104f30 Add Folder column to Details sync protocol
Also fixes padding

Fixes #1037
2013-09-30 14:12:59 +02:00
Daniel Molkentin
df36ebf308 Merge branch '1.4'
Conflicts:
	VERSION.cmake
2013-09-30 13:34:10 +02:00
Jenkins for ownCloud
0d0c6a15fb [tx-robot] updated from transifex 2013-09-28 23:02:52 -04:00
Daniel Molkentin
86e42a9cf0 Fix possible endless loop in inotify
Fixes #1041
2013-09-27 20:10:12 +02:00
Jenkins for ownCloud
b4911cc5ce [tx-robot] updated from transifex 2013-09-26 23:55:07 -04:00
Klaas Freitag
3e31d86596 Add missing trailing semicolon as nagged by rpmlint. 2013-09-26 11:13:19 +02:00
Klaas Freitag
ab007e2bb2 Set version to 1.4.1 2013-09-26 10:29:14 +02:00
Klaas Freitag
de9dcbd231 Transmit the new mtime of the file with the PUT request.
If the server supports it already, we can save another request by
sending the mtime as a header value in the PUT request and check the
servers reply for a acknoledge header.
2013-09-25 14:26:13 +02:00
Jenkins for ownCloud
00fff7f793 [tx-robot] updated from transifex 2013-09-24 11:58:13 -04:00
Klaas Freitag
5cf13dfa32 Make logging work again. 2013-09-24 15:56:03 +02:00
Klaas Freitag
27b9a5aed9 Merge branch '1.4', brings in the changes we did for 1.4.1 2013-09-24 14:18:25 +02:00
Klaas Freitag
c45d55b94b Create a rc2. 2013-09-24 12:45:45 +02:00
Klaas Freitag
7048bd8cc9 Handle app icon names properly. 2013-09-24 12:19:29 +02:00
Klaas Freitag
cae6ca987f Revert "Fix application shortname for proper icon"
We must not change the app short name for ownCloud. Too dangerous as it
is used all over.
This reverts commit 65171cfb3a.
2013-09-24 11:48:19 +02:00
Klaas Freitag
65171cfb3a Fix application shortname for proper icon 2013-09-23 18:23:21 +02:00
Klaas Freitag
7ec636aeb6 Added changelog entries. 2013-09-23 16:36:49 +02:00
Klaas Freitag
c96980a57d Set version to 1.4.1rc1 2013-09-23 16:36:36 +02:00
Klaas Freitag
0959ceeff2 Disable delete of folder object temparily. 2013-09-23 15:10:42 +02:00
Klaas Freitag
1988c9d8c4 Do complete sync if network was disconnected.
This fixes owncloud/mirall#1007
2013-09-23 15:10:42 +02:00
Klaas Freitag
1eaa1e47fe Be more careful with items in model when deleting rows. 2013-09-23 15:10:42 +02:00
Klaas Freitag
31923b314e Removed left over stuff from refactoring. 2013-09-23 15:10:42 +02:00
Klaas Freitag
ee840b62e5 Fix syncEnable in folder and folderman. 2013-09-23 15:10:42 +02:00
Jenkins for ownCloud
4de99d2540 [tx-robot] updated from transifex 2013-09-22 11:52:39 -04:00
Daniel Molkentin
363efaa408 Theming build fix: Icons need shortname 2013-09-20 23:09:41 +02:00
Jenkins for ownCloud
5b60522e5e [tx-robot] updated from transifex 2013-09-20 09:44:46 -04:00
Daniel Molkentin
b0beeea95f More fixes related to object destruction on exit
- _logBrowser is not delete on close, call deleteLater
- move writing geometry out of dtor.
- _folderWizard was unused, remove it and all related noops

Hopefully solves #945
2013-09-20 14:18:28 +02:00
Daniel Molkentin
d0a4650d69 Settings: calculate sidebar width dynamically
Fixes #1020
2013-09-20 10:40:22 +02:00
Daniel Molkentin
ec5a84cf01 Pop up config wizard if no server url is configured.
Fixes #1018.
2013-09-19 17:24:22 +02:00
Daniel Molkentin
a057eb8590 Fix focus issue during setup wizard 2013-09-19 14:52:41 +02:00
Daniel Molkentin
6a8753b119 Start only one wizard, delete it when done 2013-09-19 12:11:46 +02:00
Jenkins for ownCloud
b334c82fdd [tx-robot] updated from transifex 2013-09-18 10:45:45 -04:00
Daniel Molkentin
23fab90a31 _tray is a QPointer, not a QScopedPointer 2013-09-18 16:41:37 +02:00
Daniel Molkentin
8fdf9cac6b Try to be more graceful during shutdown
- Do not delete _tray, use deleteLater() via QScropedPointer
- force closing any dialoges that might want to write their
  geometry in aboutToQuit(), before the actual destruction
  of the sync thread, etc starts.

This tries to fix #945
2013-09-18 15:42:35 +02:00
Daniel Molkentin
525d12f5a2 Make "Sync started" message optional
Fixes #934
2013-09-18 14:14:42 +02:00
Daniel Molkentin
15d247a708 Ui::SslErrorDialog belongs within the Mirall namespace 2013-09-18 14:06:23 +02:00
Jenkins for ownCloud
9ed4aa4111 [tx-robot] updated from transifex 2013-09-16 10:34:17 -04:00
Daniel Molkentin
a1d0b85277 Add copyright header to syncfileitem.h
Fixes #993
2013-09-15 22:49:24 +02:00
Daniel Molkentin
5e5a77b040 Fix parameter order 2013-09-15 22:48:12 +02:00
Daniel Molkentin
e420ae6942 Folder Wizard: Fix various issues
- Use data role for pathes
- call root dir "ownCloud"
- don't connect() multiple times
- ensure chars like # or ? in path are interpreted as part of the url path segment

Should fix #992
2013-09-15 22:47:35 +02:00
Daniel Molkentin
0be7c0273e Replace forgotton 22px app icon version which was still s/w 2013-09-15 00:48:06 +02:00
Jenkins for ownCloud
41b1b53c76 [tx-robot] updated from transifex 2013-09-13 20:47:28 -04:00
Thomas Müller
60ef5535c6 fixing testOctetsToString
(cherry picked from commit 78c8f13645)
2013-09-13 20:14:53 +02:00
Thomas Müller
e38d0807c9 moving back to 1024 as divider for kB, MB, GB & TB
(cherry picked from commit c87c456ae0)
2013-09-13 20:14:47 +02:00
Daniel Molkentin
864b7cd41e Merge pull request #998 from owncloud/fixing-mb-1.4
Fixing mb 1.4
2013-09-13 11:11:43 -07:00
Thomas Müller
78c8f13645 fixing testOctetsToString 2013-09-13 17:30:53 +02:00
Thomas Müller
c87c456ae0 moving back to 1024 as divider for kB, MB, GB & TB 2013-09-13 15:00:49 +02:00
hefee
94d9b8fb39 Don't ship doc/scripts/README.rst via sphinx 2013-09-13 02:26:21 +02:00
Daniel Molkentin
7045a6c239 Merge pull request #996 from owncloud/dont-ship-doc_scripts
Don't ship doc/scripts/README.rst via sphinx
2013-09-12 17:25:39 -07:00
hefee
2b1602872c Don't ship doc/scripts/README.rst via sphinx 2013-09-13 01:26:02 +02:00
Daniel Molkentin
b488ad12c2 Do not build org and com theming when building from tar ball
Fixes #989
2013-09-12 12:56:24 +02:00
Daniel Molkentin
2dad027001 Do not build org and com theming when building from tar ball
Fixes #989
2013-09-12 12:55:12 +02:00
Daniel Molkentin
f97bb240d7 Setting thread priority is only possible after calling start() 2013-09-11 15:52:07 +02:00
Daniel Molkentin
574e029254 Setting thread priority is only possible after calling start() 2013-09-11 15:51:06 +02:00
Jenkins for ownCloud
91759c4a27 [tx-robot] updated from transifex 2013-09-11 06:40:51 -04:00
Klaas Freitag
ce5934461e Do not en- or disable the watcher in setSyncEnabled.
The watcher is handled through the start and stop sync hooks in the
folder class. Even if the the folder is disabled and the watcher
fires, the folder does not schedule because it checks first if the
folder is enabled.
2013-09-11 12:33:07 +02:00
Klaas Freitag
80332fb6ab Do not en- or disable the watcher in setSyncEnabled.
The watcher is handled through the start and stop sync hooks in the
folder class. Even if the the folder is disabled and the watcher
fires, the folder does not schedule because it checks first if the
folder is enabled.
2013-09-11 12:30:29 +02:00
Klaas Freitag
287ec2f3df Enable and disable syncing also in the folder objects. 2013-09-11 10:50:03 +02:00
Klaas Freitag
eb5824f713 Handle startup network errors correctly. 2013-09-11 10:49:45 +02:00
Klaas Freitag
eded901f69 Minor code cleanups- 2013-09-11 10:49:10 +02:00
Klaas Freitag
6f637e40d8 Enable and disable syncing also in the folder objects. 2013-09-11 10:32:09 +02:00
Klaas Freitag
ec06663dee Handle startup network errors correctly. 2013-09-11 10:32:09 +02:00
Klaas Freitag
3c5c432e1d Minor code cleanups- 2013-09-11 10:32:09 +02:00
Olivier Goffart
927f7549d4 Revert "Compile (find httpbf)"
This reverts commit 2d2e843804.

Turns out i had an old build of csync
2013-09-11 09:00:27 +02:00
Olivier Goffart
177114f8d9 Initialize the size of the SyncItem 2013-09-11 08:49:09 +02:00
Olivier Goffart
6bfdfd1af0 Fix progress with new propagator 2013-09-11 08:42:29 +02:00
Olivier Goffart
2d2e843804 Compile (find httpbf) 2013-09-11 08:11:03 +02:00
Daniel Molkentin
fd30df82cb Folder Wizard: More sanity checks
Check if a server folder or its parent
is already part of a sync and refuse
to create a sync in this case.

Avoids "recursive syncing"

Fixes #962
2013-09-10 23:38:00 +02:00
Daniel Molkentin
55f2fcb4c6 Folder Wizard: More sanity checks
Check if a server folder or its parent
is already part of a sync and refuse
to create a sync in this case.

Avoids "recursive syncing"

Fixes #962
2013-09-10 23:37:52 +02:00
Daniel Molkentin
d8888432c3 Send basic auth in utf8.
Implies circumventing QAuthenticator and handling
authentication manually again.

Fixes #941
2013-09-10 23:04:10 +02:00
Daniel Molkentin
59425741b6 Send basic auth in utf8.
Implies circumventing QAuthenticator and handling
authentication manually again.

Fixes #941
2013-09-10 23:03:52 +02:00
Daniel Molkentin
3577ba2981 Fix httpbf/neon linkage 2013-09-10 19:50:44 +02:00
Jenkins for ownCloud
d4477b0d65 [tx-robot] updated from transifex 2013-09-10 09:42:18 -04:00
Daniel Molkentin
48b4c57d92 Use shared version of libhttpbf, package it on win 2013-09-10 14:45:08 +02:00
morriswinkler
51712fea53 Update building.rst
eider fix it or add the note here
2013-09-10 12:15:16 +02:00
Daniel Molkentin
f597f99198 Wait up to 30 secs before complaining about missing systray
Conflicts:
	src/mirall/utility.cpp

Fixes #949
2013-09-10 11:40:02 +02:00
Daniel Molkentin
00d09763af Fix compile error 2013-09-10 11:37:52 +02:00
Daniel Molkentin
5639572ef3 Wait up to 30 secs before complaining about missing systray 2013-09-10 11:37:52 +02:00
Daniel Molkentin
1e7716abb4 Merge pull request #964 from morriswinkler/patch-1
Update building.rst
2013-09-10 02:28:03 -07:00
morriswinkler
751181d14f Update building.rst
eider fix it or add the note here
2013-09-09 19:09:08 +02:00
Olivier Goffart
013b87f9c5 ignore the progress database 2013-09-09 15:18:28 +02:00
Olivier Goffart
6da2f6bbaa Qt5: QSettings is no longer case insensitive 2013-09-09 14:00:13 +02:00
Olivier Goffart
198cb43ad6 Qt5: Q_WS_* macro were deprecated, replace by QT_OS_*
in particular, QT_WS_X11 is no longer defined
2013-09-09 13:31:39 +02:00
Jenkins for ownCloud
db648a0268 [tx-robot] updated from transifex 2013-09-08 20:37:37 -04:00
Jenkins for ownCloud
7cea2225de [tx-robot] updated from transifex 2013-09-07 04:38:12 -04:00
Daniel Molkentin
51a7cbeb55 kill warning 2013-09-05 20:15:55 +02:00
Daniel Molkentin
171a9e1575 fix Qt 5 build on Mac/Win 2013-09-05 19:53:39 +02:00
Klaas Freitag
76deabe4df Use List to handle errors and display multiline. 2013-09-05 19:28:23 +02:00
Klaas Freitag
74ec37f3c7 Use List to handle errors and display multiline. 2013-09-05 19:25:59 +02:00
Daniel Molkentin
6b9950a9a0 Qt5: DBUS is only needed for FDO 2013-09-05 18:41:49 +02:00
Daniel Molkentin
c6ed7dc586 Tests: Find test lib in Qt5 2013-09-05 18:06:00 +02:00
Daniel Molkentin
35ac6610c7 Compile with Qt5 on Linux 2013-09-05 17:13:31 +02:00
Jenkins for ownCloud
121efd8ff1 [tx-robot] updated from transifex 2013-09-05 07:30:27 -04:00
Daniel Molkentin
23d8f01012 Fix locating translations on linux 2013-09-05 11:08:00 +02:00
Daniel Molkentin
05178f0fbf Fix locating translations on linux 2013-09-05 11:06:40 +02:00
Daniel Molkentin
573aaec9fd Fix propagator test 2013-09-04 17:29:19 +02:00
Klaas Freitag
8fe102662d Merge branch 'propagator-ng': Fixes to work with new LGPL ocsync.
Conflicts:
	src/mirall/csyncthread.cpp
2013-09-04 16:33:06 +02:00
Klaas Freitag
b676ffe208 Some more info about the exclude matching. 2013-09-04 15:17:43 +02:00
Daniel Molkentin
625b21152e Merge branch 'qt5' 2013-09-04 15:01:21 +02:00
Daniel Molkentin
385759f3c9 bump version 2013-09-04 15:00:53 +02:00
Olivier Goffart
bf6d0a521c Compile with Qt5 2013-08-28 20:58:22 +02:00
Olivier Goffart
336bbb2403 Compile with Qt4 2013-08-28 20:14:40 +02:00
Daniel Molkentin
73da086964 Initial changes to also build on Qt5, doesn't compile yet 2013-08-28 17:03:31 +02:00
Daniel Molkentin
58f4f3623f Startup: Unity has no proper Systray implementation
...so it can't respond to isSysTrayAvailable()
2013-08-28 15:51:00 +02:00
Olivier Goffart
3c2bb1e2bc Compile after the merge 2013-08-21 15:29:04 +02:00
Olivier Goffart
24af9f38f4 Merge branch 'csync_lgpl' into propagator-ng
Conflicts:
	src/mirall/csyncthread.cpp
	src/mirall/csyncthread.h
	src/mirall/folder.cpp
	src/mirall/progressdispatcher.h
	src/mirall/syncfileitem.h
2013-08-21 13:19:02 +02:00
Klaas Freitag
98efab83a1 Compile against ocsync based on csync 0.50 2013-08-20 13:26:03 +02:00
Olivier Goffart
e4128cd5d8 progress with new propagator 2013-08-14 19:59:16 +02:00
Olivier Goffart
f8e6326880 Default to not cancel
So that the owncloudcmd client is not blocked
2013-08-14 15:44:30 +02:00
Olivier Goffart
67c5f513aa Fix compilation after merge 2013-08-14 15:36:34 +02:00
Olivier Goffart
0589bfc51b Merge branch 'master' into propagator-ng
This just fix the conflict.
Do not compile or work yet

Conflicts:
	src/CMakeLists.txt
	src/mirall/csyncthread.cpp
	src/mirall/csyncthread.h
	src/mirall/owncloudfolder.cpp
	src/mirall/syncfileitem.h
	test/CMakeLists.txt
2013-08-14 15:19:21 +02:00
Olivier Goffart
04f32bd397 Revert "TMP threaded"
This reverts commit 5bb4c3be43.
2013-08-14 14:59:32 +02:00
Olivier Goffart
5bb4c3be43 TMP threaded 2013-06-03 10:43:21 +02:00
Olivier Goffart
07ce6cfa79 don't use member variable of the propagator 2013-05-16 14:50:36 +02:00
Olivier Goffart
9ce47c9675 make the propagator asynchronous 2013-05-16 13:54:22 +02:00
Olivier Goffart
40255d643c propagator-ng: adapt to the merge of rename_folders branch 2013-05-15 15:22:20 +02:00
Olivier Goffart
b31200a6f2 propagator-ng: fix setting the mtime 2013-05-10 13:20:21 +02:00
Olivier Goffart
98cbb599b8 Fix handling of conflicting directories
We can safely ignore them in the propagator
2013-05-10 12:35:10 +02:00
Olivier Goffart
ecd314cef5 Give the tmp file a proper hidden and excluded name 2013-05-08 14:31:52 +02:00
Olivier Goffart
0b1ecd0ac6 propagator-ng: Abort on fatal error 2013-05-08 13:30:30 +02:00
Olivier Goffart
852e30ef07 owncloudcmd: don't abort on unknown argument 2013-05-08 12:11:23 +02:00
Olivier Goffart
2193da0ab5 Fix a few FIXME 2013-05-07 17:47:29 +02:00
Olivier Goffart
7cd12e7dc5 remove some FIXME 2013-05-07 17:16:11 +02:00
Olivier Goffart
685bf395be propagator-ng: resume uploads
Currently i think it corrupt the files
2013-05-06 18:41:56 +02:00
Olivier Goffart
0636ae6f28 propagator-ng: be able to resume download 2013-05-06 16:59:11 +02:00
Olivier Goffart
7f8eba3700 propagator-ng: set the modtime on downloaded files 2013-05-06 12:09:21 +02:00
Olivier Goffart
02957aba45 propagator-ng: Emit the fileReceived signal 2013-05-05 12:46:40 +02:00
Klaas Freitag
946258ca59 Created a test file for ownCloudPropagator. Not much in yet. 2013-05-05 12:36:42 +02:00
Klaas Freitag
2e53e2e4e0 Really return true in case of error. 2013-05-05 12:34:38 +02:00
Klaas Freitag
721a8f79ab Improve error handling, add error string to each individual file item. 2013-05-05 11:47:19 +02:00
Olivier Goffart
ecaf66db5d style: use _ in front of member variable 2013-05-05 11:32:12 +02:00
Olivier Goffart
0c775aba6d propagator-ng: get the etag also in the GET request 2013-05-05 11:32:12 +02:00
Olivier Goffart
71b238031f Fix compilation after previous rebase 2013-05-05 11:32:12 +02:00
Olivier Goffart
662bd4e5fe propagator-ng: handle conflict
Always download the file from the server.
And if they are equals, ignore the conflict
2013-05-05 11:17:42 +02:00
Olivier Goffart
5118054fa3 Enable the backup of conflicts files in owncloudcmd 2013-05-05 11:17:07 +02:00
Klaas Freitag
c956d11183 More error handling of propagator, some cleanups. 2013-05-04 17:14:47 +02:00
Klaas Freitag
7df23a1b19 Proper error reporting of propagator - WIP 2013-05-04 17:14:47 +02:00
Klaas Freitag
72580d7213 move command line client into bin dir 2013-05-04 17:14:47 +02:00
Olivier Goffart
264cc73806 Implement renaming on windows
(Not actually tested)
2013-05-04 16:18:13 +02:00
Olivier Goffart
c041076c95 Handle renames 2013-05-04 15:32:11 +02:00
Olivier Goffart
f6ff189f35 Add owncloudcmd
A simple commandline client like ocsync.
The goal is to be used bu the t1.pl test
2013-05-04 13:52:35 +02:00
Olivier Goffart
e1e8842548 New owncloud propagator that skip the vio abstraction layer
The vio abstraction layer within csync is inneficient for the owncloud
use case because not all calls maps well to the POSIX interface. We can
be much more efficient by doing exactly what we need.

Also, this will allow us to scedule better the calls and possibly to use
threads.
2013-05-04 11:02:30 +02:00
174 changed files with 30040 additions and 24415 deletions

4
.gitignore vendored
View File

@@ -6,3 +6,7 @@ CMakeLists.txt.user*
*~
*.autosave
doc/_build/*
*~
*.kate-swp
*.kdev4
win/

View File

@@ -21,8 +21,20 @@ include(${CMAKE_SOURCE_DIR}/VERSION.cmake)
configure_file( ${CMAKE_SOURCE_DIR}/src/mirall/version.h.in "${CMAKE_CURRENT_BINARY_DIR}/src/mirall/version.h" )
include_directories(BEFORE ${CMAKE_CURRENT_BINARY_DIR} "${CMAKE_CURRENT_BINARY_DIR}/src/mirall/")
#####
## handle DBUS for Fdo notifications
if( UNIX AND NOT APPLE )
add_definitions( -DUSE_FDO_NOTIFICATIONS)
set(WITH_DBUS ON)
endif()
####
include(GNUInstallDirs)
include(DefineInstallationPaths)
include(QtVersionAbstraction)
setup_qt()
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
@@ -58,19 +70,14 @@ else()
endif()
#####
#####
## handle DBUS for Fdo notifications
if( UNIX AND NOT APPLE )
add_definitions( -DUSE_FDO_NOTIFICATIONS)
endif()
####
#### find libs
find_package(Qt4 4.7.0 COMPONENTS QtCore QtGui QtXml QtNetwork QtTest QtWebkit REQUIRED )
if( UNIX AND NOT APPLE ) # Fdo notifications
find_package(Qt4 4.7.0 COMPONENTS QtDBus REQUIRED )
endif()
#find_package(Qt4 4.7.0 COMPONENTS QtCore QtGui QtXml QtNetwork QtTest QtWebkit REQUIRED )
#if( UNIX AND NOT APPLE ) # Fdo notifications
# find_package(Qt4 4.7.0 COMPONENTS QtDBus REQUIRED )
#endif()
find_package(Neon REQUIRED)
find_package(Csync REQUIRED)
find_package(QtKeychain REQUIRED)
if(UNIX)
find_package(INotify REQUIRED)
else()
@@ -78,7 +85,6 @@ find_package(INotify)
endif()
find_package(Sphinx)
find_package(PdfLatex)
find_package(QtKeychain)
set(WITH_QTKEYCHAIN ${QTKEYCHAIN_FOUND})
set(USE_INOTIFY ${INOTIFY_FOUND})

View File

@@ -21,11 +21,9 @@ set( CSYNC_BINARY_DIR @CSYNC_BINARY_DIR@ )
set( MINGW_ROOT @CMAKE_FIND_ROOT_PATH@ )
if(CSYNC_BINARY_DIR)
set( CSYNC_LIBRARY_DIR "${CSYNC_BINARY_DIR}/src" )
set( CSYNC_PLUGIN_DIR "${CSYNC_BINARY_DIR}/modules" )
set( CSYNC_CONFIG_DIR "${CSYNC_BINARY_DIR}/config" )
else()
set( CSYNC_LIBRARY_DIR "${MINGW_ROOT}/bin" )
set( CSYNC_PLUGIN_DIR "${MINGW_ROOT}/plugins-0" ) #FIXME: whatever it is
set( CSYNC_CONFIG_DIR "${MINGW_ROOT}/etc/ocsync" )
endif()
set( BUILD_OWNCLOUD_OSX_BUNDLE @BUILD_OWNCLOUD_OSX_BUNDLE@)

View File

@@ -1,5 +1,27 @@
ChangeLog
=========
version 1.4.1 (release 2013-09-24 ), csync 0.90.1 required
* Translation and documentation fixes.
* Fixed error display in settings/status dialog, displays multi
line error messages now correctly.
* Wait up to 30 secs before complaining about missing systray
Fixes bug #949
* Fixed utf8 issues with basic auth authentication, fixes bug #941
* Fixed remote folder selector, avoid recursive syncing, fixes bug #962
* Handle and display network problems at startup correctly.
* Enable and disable the folder watcher during syncs correctly.
* Fix setting of thread priority.
* Fixed file size display.
* Fixed various folder wizard issues, bug #992
* Made "Sync started" message optional, fixes bug #934
* Fixed shutdown, avoid crashed config on win32, fixes bug #945
* Pop up config wizard if no server url is configured, fixes bug #1018
* Settings: calculate sidebar width dynamically, fixes bug #1020
* Fixed a crash if sync folders were removed, fixes bug #713
* Do proper resync after network disconnect, fixes bug #1007
* Various minor code fixes
version 1.4.0 (release 2013-09-04 ), csync 0.90.0 required
* New Scheduler: Only sync when there are actual changes in the server

View File

@@ -1,6 +1,6 @@
set( VERSION_MAJOR 1 )
set( VERSION_MINOR 4 )
set( VERSION_MINOR 5 )
set( VERSION_PATCH 0 )
set( VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${VERSION_SUFFIX}")
set( VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}${VERSION_SUFFIX}beta2")
set( SOVERSION 0 )

View File

@@ -5,7 +5,6 @@ This sample uses the registry plugin, so you need to download that if you don't
!define APPNAME "UAC_RealWorldFullyLoadedDualMode"
!define ELEVATIONTITLE "${APPNAME}: Elevate" ;displayed during elevation on our custom page
!define SMSUBDIR $StartMenuFolder ;"${APPNAME}"
!define UNINSTALLER_NAME "Uninstall ${APPNAME}.exe"
!define UNINSTALLER_REGSECTION "${APPNAME}"
!define RegPath.MSUninstall "Software\Microsoft\Windows\CurrentVersion\Uninstall"
@@ -200,10 +199,7 @@ UAC::Exec "" "Notepad.exe" "$Windir\Win.INI" "$InstDir"
FunctionEnd
Function CreateSMShortcuts
StrCpy ${SMSUBDIR} $9 ;stupid sync
CreateDirectory "$SMPrograms\${SMSUBDIR}"
CreateShortcut "$SMPrograms\${SMSUBDIR}\${APPNAME}.lnk" "$Windir\Notepad.exe"
CreateShortcut "$SMPrograms\${SMSUBDIR}\Uninstall ${APPNAME}.lnk" "$InstDir\${UNINSTALLER_NAME}"
CreateShortcut "$SMPrograms\${APPNAME}.lnk" "$Windir\Notepad.exe"
FunctionEnd
Function CreateDeskShortcuts
CreateShortcut "$Desktop\${APPNAME}.lnk" "$Windir\Notepad.exe"
@@ -222,7 +218,6 @@ ${registry::Unload}
SectionEnd
Section "Startmenu Shortcuts"
StrCpy $9 ${SMSUBDIR} ;this is stupid as hell, we need correct ${SMSUBDIR} in the outer process, this is the only way (plugins cannot enum "custom" var's AFAIK)
${UAC.CallFunctionAsUser} CreateSMShortcuts
SectionEnd
Section "Desktop Shortcut"
@@ -231,9 +226,7 @@ SectionEnd
Section Uninstall
Delete "$InstDir\${UNINSTALLER_NAME}"
Delete "$SMPrograms\${SMSUBDIR}\${APPNAME}.lnk"
Delete "$SMPrograms\${SMSUBDIR}\Uninstall ${APPNAME}.lnk"
RMDir "$SMPrograms\${SMSUBDIR}"
Delete "$SMPrograms\${APPNAME}.lnk"
Delete "$Desktop\${APPNAME}.lnk"
RMDir "$InstDir"

View File

@@ -0,0 +1,73 @@
# - Try to find Neon
# Once done this will define
#
# NEON_FOUND - system has Neon
# NEON_INCLUDE_DIRS - the Neon include directory
# NEON_LIBRARIES - Link these to use Neon
# NEON_DEFINITIONS - Compiler switches required for using Neon
#
# Copyright (c) 2011 Andreas Schneider <asn@cryptomilk.org>
#
# Redistribution and use is allowed according to the terms of the New
# BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(_NEON neon)
endif (PKG_CONFIG_FOUND)
include(GNUInstallDirs)
find_path(NEON_INCLUDE_DIRS
NAMES
neon/ne_basic.h
HINTS
${_NEON_INCLUDEDIR}
${CMAKE_INSTALL_INCLUDEDIR}
)
find_library(NEON_LIBRARIES
NAMES
neon
HINTS
${_NEON_LIBDIR}
${CMAKE_INSTALL_LIBDIR}
${CMAKE_INSTALL_PREFIX}/lib
${CMAKE_INSTALL_PREFIX}/lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Neon DEFAULT_MSG NEON_LIBRARIES NEON_INCLUDE_DIRS)
# show the NEON_INCLUDE_DIRS and NEON_LIBRARIES variables only in the advanced view
mark_as_advanced(NEON_INCLUDE_DIRS NEON_LIBRARIES)
# Check if neon was compiled with LFS support, if so, the NE_LFS variable has to
# be defined in the owncloud module.
# If neon was not compiled with LFS its also ok since the underlying system
# than probably supports large files anyway.
IF( CMAKE_FIND_ROOT_PATH )
FIND_PROGRAM( NEON_CONFIG_EXECUTABLE NAMES neon-config HINTS ${CMAKE_FIND_ROOT_PATH}/bin )
ELSE( CMAKE_FIND_ROOT_PATH )
FIND_PROGRAM( NEON_CONFIG_EXECUTABLE NAMES neon-config )
ENDIF( CMAKE_FIND_ROOT_PATH )
IF ( NEON_CONFIG_EXECUTABLE )
MESSAGE(STATUS "neon-config executable: ${NEON_CONFIG_EXECUTABLE}")
# neon-config --support lfs
EXECUTE_PROCESS( COMMAND ${NEON_CONFIG_EXECUTABLE} "--support" "lfs"
RESULT_VARIABLE LFS
OUTPUT_STRIP_TRAILING_WHITESPACE )
IF (LFS EQUAL 0)
MESSAGE(STATUS "libneon has been compiled with LFS support")
SET(NEON_WITH_LFS 1 PARENT_SCOPE)
ELSE (LFS EQUAL 0)
MESSAGE(STATUS "libneon has not been compiled with LFS support, rely on OS")
ENDIF (LFS EQUAL 0)
ELSE ( NEON_CONFIG_EXECUTABLE )
MESSAGE(STATUS, "neon-config could not be found.")
ENDIF ( NEON_CONFIG_EXECUTABLE )

View File

@@ -0,0 +1,157 @@
# This file defines the Feature Logging macros.
#
# MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]])
# Logs the information so that it can be displayed at the end
# of the configure run
# VAR : TRUE or FALSE, indicating whether the feature is supported
# FEATURE: name of the feature, e.g. "libjpeg"
# DESCRIPTION: description what this feature provides
# URL: home page
# REQUIRED: TRUE or FALSE, indicating whether the featue is required
# MIN_VERSION: minimum version number. empty string if unneeded
# COMMENTS: More info you may want to provide. empty string if unnecessary
#
# MACRO_DISPLAY_FEATURE_LOG()
# Call this to display the collected results.
# Exits CMake with a FATAL error message if a required feature is missing
#
# Example:
#
# INCLUDE(MacroLogFeature)
#
# FIND_PACKAGE(JPEG)
# MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "")
# ...
# MACRO_DISPLAY_FEATURE_LOG()
# Copyright (c) 2006, Alexander Neundorf, <neundorf@kde.org>
# Copyright (c) 2006, Allen Winter, <winter@kde.org>
# Copyright (c) 2009, Sebastian Trueg, <trueg@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
IF (NOT _macroLogFeatureAlreadyIncluded)
SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
IF (EXISTS ${_file})
FILE(REMOVE ${_file})
ENDIF (EXISTS ${_file})
SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
IF (EXISTS ${_file})
FILE(REMOVE ${_file})
ENDIF (EXISTS ${_file})
SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
IF (EXISTS ${_file})
FILE(REMOVE ${_file})
ENDIF (EXISTS ${_file})
SET(_macroLogFeatureAlreadyIncluded TRUE)
INCLUDE(FeatureSummary)
ENDIF (NOT _macroLogFeatureAlreadyIncluded)
MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments)
STRING(TOUPPER "${ARGV4}" _required)
SET(_minvers "${ARGV5}")
SET(_comments "${ARGV6}")
IF (${_var})
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
ELSE (${_var})
IF ("${_required}" STREQUAL "TRUE")
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
ELSE ("${_required}" STREQUAL "TRUE")
SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
ENDIF ("${_required}" STREQUAL "TRUE")
ENDIF (${_var})
SET(_logtext " * ${_package}")
IF (NOT ${_var})
IF (${_minvers} MATCHES ".*")
SET(_logtext "${_logtext} (${_minvers} or higher)")
ENDIF (${_minvers} MATCHES ".*")
SET(_logtext "${_logtext} <${_url}>\n ")
ELSE (NOT ${_var})
SET(_logtext "${_logtext} - ")
ENDIF (NOT ${_var})
SET(_logtext "${_logtext}${_description}")
IF (NOT ${_var})
IF (${_comments} MATCHES ".*")
SET(_logtext "${_logtext}\n ${_comments}")
ENDIF (${_comments} MATCHES ".*")
# SET(_logtext "${_logtext}\n") #double-space missing features?
ENDIF (NOT ${_var})
FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n")
IF(COMMAND SET_PACKAGE_INFO) # in FeatureSummary.cmake since CMake 2.8.3
SET_PACKAGE_INFO("${_package}" "\"${_description}\"" "${_url}" "\"${_comments}\"")
ENDIF(COMMAND SET_PACKAGE_INFO)
ENDMACRO(MACRO_LOG_FEATURE)
MACRO(MACRO_DISPLAY_FEATURE_LOG)
IF(COMMAND FEATURE_SUMMARY) # in FeatureSummary.cmake since CMake 2.8.3
FEATURE_SUMMARY(FILENAME ${CMAKE_CURRENT_BINARY_DIR}/FindPackageLog.txt
WHAT ALL)
ENDIF(COMMAND FEATURE_SUMMARY)
SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt)
SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt)
SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt)
IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
SET(_printSummary TRUE)
ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile})
IF(_printSummary)
SET(_missingDeps 0)
IF (EXISTS ${_enabledFile})
FILE(READ ${_enabledFile} _enabled)
FILE(REMOVE ${_enabledFile})
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}")
ENDIF (EXISTS ${_enabledFile})
IF (EXISTS ${_disabledFile})
SET(_missingDeps 1)
FILE(READ ${_disabledFile} _disabled)
FILE(REMOVE ${_disabledFile})
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}")
ENDIF (EXISTS ${_disabledFile})
IF (EXISTS ${_missingFile})
SET(_missingDeps 1)
FILE(READ ${_missingFile} _requirements)
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}")
FILE(REMOVE ${_missingFile})
SET(_haveMissingReq 1)
ENDIF (EXISTS ${_missingFile})
IF (NOT ${_missingDeps})
SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.")
ENDIF (NOT ${_missingDeps})
MESSAGE(${_summary})
MESSAGE("-----------------------------------------------------------------------------\n")
IF(_haveMissingReq)
MESSAGE(FATAL_ERROR "Exiting: Missing Requirements")
ENDIF(_haveMissingReq)
ENDIF(_printSummary)
ENDMACRO(MACRO_DISPLAY_FEATURE_LOG)

View File

@@ -0,0 +1,47 @@
# - MACRO_OPTIONAL_FIND_PACKAGE() combines FIND_PACKAGE() with an OPTION()
# MACRO_OPTIONAL_FIND_PACKAGE( <name> [QUIT] )
# This macro is a combination of OPTION() and FIND_PACKAGE(), it
# works like FIND_PACKAGE(), but additionally it automatically creates
# an option name WITH_<name>, which can be disabled via the cmake GUI.
# or via -DWITH_<name>=OFF
# The standard <name>_FOUND variables can be used in the same way
# as when using the normal FIND_PACKAGE()
# Copyright (c) 2006-2010 Alexander Neundorf, <neundorf@kde.org>
#
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
# This is just a helper macro to set a bunch of variables empty.
# We don't know whether the package uses UPPERCASENAME or CamelCaseName, so we try both:
macro(_MOFP_SET_EMPTY_IF_DEFINED _name _var)
if(DEFINED ${_name}_${_var})
set(${_name}_${_var} "")
endif(DEFINED ${_name}_${_var})
string(TOUPPER ${_name} _nameUpper)
if(DEFINED ${_nameUpper}_${_var})
set(${_nameUpper}_${_var} "")
endif(DEFINED ${_nameUpper}_${_var})
endmacro(_MOFP_SET_EMPTY_IF_DEFINED _package _var)
macro (MACRO_OPTIONAL_FIND_PACKAGE _name )
option(WITH_${_name} "Search for ${_name} package" ON)
if (WITH_${_name})
find_package(${_name} ${ARGN})
else (WITH_${_name})
string(TOUPPER ${_name} _nameUpper)
set(${_name}_FOUND FALSE)
set(${_nameUpper}_FOUND FALSE)
_mofp_set_empty_if_defined(${_name} INCLUDE_DIRS)
_mofp_set_empty_if_defined(${_name} INCLUDE_DIR)
_mofp_set_empty_if_defined(${_name} INCLUDES)
_mofp_set_empty_if_defined(${_name} LIBRARY)
_mofp_set_empty_if_defined(${_name} LIBRARIES)
_mofp_set_empty_if_defined(${_name} LIBS)
_mofp_set_empty_if_defined(${_name} FLAGS)
_mofp_set_empty_if_defined(${_name} DEFINITIONS)
endif (WITH_${_name})
endmacro (MACRO_OPTIONAL_FIND_PACKAGE)

View File

@@ -39,7 +39,6 @@
!define IMAGEFORMATS_DLL_PATH "${MING_LIB}/qt4/plugins/imageformats"
!define CSYNC_LIBRARY_DIR "@CSYNC_LIBRARY_DIR@"
!define CSYNC_PLUGIN_DIR "@CSYNC_PLUGIN_DIR@"
!define CSYNC_CONFIG_DIR "@CSYNC_CONFIG_DIR@"
!define NSI_PATH "${source_path}/admin/win/nsi"
@@ -297,11 +296,9 @@ Section "${APPLICATION_NAME}" SEC_APPLICATION
File "${IMAGEFORMATS_DLL_PATH}\qjpeg4.dll"
File "${IMAGEFORMATS_DLL_PATH}\qico4.dll"
SetOutPath "$INSTDIR\modules"
; FIXME: fix installation dir of module, currently needs manual copying to
; /usr/i686-w64-mingw32/sys-root/mingw/bin/csync_modules/
SetOutPath "$INSTDIR\sqldrivers"
File "${SQLITE_DLL_PATH}\qsqlite4.dll"
File "${CSYNC_PLUGIN_DIR}/ocsync_owncloud.dll"
SetOutPath "$INSTDIR"
!endif
@@ -315,16 +312,15 @@ Section "${APPLICATION_NAME}" SEC_APPLICATION
File "${QT_DLL_PATH}\QtCore4.dll"
File "${QT_DLL_PATH}\QtGui4.dll"
File "${QT_DLL_PATH}\QtNetwork4.dll"
File "${QT_DLL_PATH}\QtSql4.dll"
File "${QT_DLL_PATH}\QtXml4.dll"
File "${QT_DLL_PATH}\QtWebKit4.dll"
;QtKeyChain stuff
File "${MING_BIN}\libqtkeychain.dll"
File "${CSYNC_LIBRARY_DIR}/libocsync.dll"
File "${CSYNC_LIBRARY_DIR}\libocsync.dll"
File "${MING_BIN}\libsqlite3-0.dll"
File "${MING_BIN}\libiniparser.dll"
File "${MING_BIN}\libdl.dll"
File "${MING_BIN}\libpng15-15.dll"
; ownCloud plugin
@@ -353,18 +349,13 @@ SectionEnd
SectionGroup "Shortcuts"
!ifdef OPTION_SECTION_SC_START_MENU
${MementoSection} "Start Menu Program Group" SEC_START_MENU
${MementoSection} "Start Menu Program Shortcut" SEC_START_MENU
SectionIn 1 2 3
SetDetailsPrint textonly
DetailPrint "Adding shortcuts for the ${APPLICATION_NAME} program group to the Start Menu."
DetailPrint "Adding shortcut for ${APPLICATION_NAME} to the Start Menu."
SetDetailsPrint listonly
SetShellVarContext all
RMDir /r "$SMPROGRAMS\${APPLICATION_NAME}"
CreateDirectory "$SMPROGRAMS\${APPLICATION_NAME}"
;CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\LICENSE.lnk" "$INSTDIR\LICENSE.txt"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\${APPLICATION_NAME}.lnk" "$INSTDIR\${APPLICATION_EXECUTABLE}"
;CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\Release notes.lnk" "$INSTDIR\NOTES.txt"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}\Uninstall.lnk" "$INSTDIR\uninstall.exe"
CreateShortCut "$SMPROGRAMS\${APPLICATION_NAME}.lnk" "$INSTDIR\${APPLICATION_EXECUTABLE}"
SetShellVarContext current
${MementoSectionEnd}
!endif
@@ -397,7 +388,7 @@ ${MementoSectionDone}
;--------------------------------
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_APPLICATION} "${APPLICATION_NAME} essentials."
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_START_MENU} "${APPLICATION_NAME} program group."
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_START_MENU} "${APPLICATION_NAME} shortcut."
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_DESKTOP} "Desktop shortcut for ${APPLICATION_NAME}."
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_QUICK_LAUNCH} "Quick Launch shortcut for ${APPLICATION_NAME}."
!insertmacro MUI_FUNCTION_DESCRIPTION_END
@@ -505,7 +496,7 @@ Section Uninstall
;Start menu shortcuts.
!ifdef OPTION_SECTION_SC_START_MENU
SetShellVarContext all
RMDir /r "$SMPROGRAMS\${APPLICATION_NAME}"
Delete "$SMPROGRAMS\${APPLICATION_NAME}.lnk"
SetShellVarContext current
!endif

View File

@@ -0,0 +1,131 @@
include (MacroOptionalFindPackage)
include (MacroLogFeature)
option(BUILD_WITH_QT4 "Build with Qt4 no matter if Qt5 was found" ON)
if( NOT BUILD_WITH_QT4 )
find_package(Qt5Core QUIET)
if( Qt5Core_DIR )
find_package(Qt5Widgets QUIET)
find_package(Qt5Quick QUIET)
find_package(Qt5PrintSupport QUIET)
find_package(Qt5WebKit QUIET)
find_package(Qt5Location QUIET)
find_package(Qt5Network QUIET)
find_package(Qt5Sensors QUIET)
find_package(Qt5Xml QUIET)
# find_package(Qt5WebKitWidgets QUIET)
message(STATUS "Using Qt 5!")
# We need this to find the paths to qdbusxml2cpp and co
if (WITH_DBUS)
find_package(Qt5DBus REQUIRED)
include_directories(${Qt5DBus_INCLUDES})
add_definitions(${Qt5DBus_DEFINITIONS})
endif (WITH_DBUS)
include_directories(${Qt5Widgets_INCLUDES})
add_definitions(${Qt5Widgets_DEFINITIONS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
# set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
macro(qt_wrap_ui)
qt5_wrap_ui(${ARGN})
endmacro()
macro(qt_add_resources)
qt5_add_resources(${ARGN})
endmacro()
# find_package(Qt5LinguistTools REQUIRED)
macro(qt_add_translation)
# qt5_add_translation(${ARGN})
endmacro()
macro(qt_add_dbus_interface)
qt5_add_dbus_interface(${ARGN})
endmacro()
macro(qt_add_dbus_adaptor)
qt5_add_dbus_adaptor(${ARGN})
endmacro()
macro(qt_wrap_cpp)
qt5_wrap_cpp(${ARGN})
endmacro()
macro(install_qt_executable)
install_qt5_executable(${ARGN})
endmacro()
macro(setup_qt)
endmacro()
set(QT_RCC_EXECUTABLE "${Qt5Core_RCC_EXECUTABLE}")
#Enable deprecated symbols
add_definitions("-DQT_DISABLE_DEPRECATED_BEFORE=0")
endif()
endif()
if( NOT Qt5Core_DIR )
message(STATUS "Could not find Qt5, searching for Qt4 instead...")
set(NEEDED_QT4_COMPONENTS "QtCore" "QtXml" "QtNetwork" "QtGui" "QtWebkit")
if( BUILD_TESTS )
list(APPEND NEEDED_QT4_COMPONENTS "QtTest")
endif()
macro_optional_find_package(Qt4 4.7.0 COMPONENTS ${NEEDED_QT4_COMPONENTS} )
macro_log_feature(QT4_FOUND "Qt" "A cross-platform application and UI framework" "http://qt.nokia.com" TRUE "" "If you see this, although libqt4-devel is installed, check whether the \n qtwebkit-devel package and whatever contains QtUiTools is installed too")
macro(qt5_use_modules)
endmacro()
macro(qt_wrap_ui)
qt4_wrap_ui(${ARGN})
endmacro()
macro(qt_add_resources)
qt4_add_resources(${ARGN})
endmacro()
macro(qt_add_translation)
qt4_add_translation(${ARGN})
endmacro()
macro(qt_add_dbus_interface)
qt4_add_dbus_interface(${ARGN})
endmacro()
macro(qt_add_dbus_adaptor)
qt4_add_dbus_adaptor(${ARGN})
endmacro()
macro(qt_wrap_cpp)
qt4_wrap_cpp(${ARGN})
endmacro()
macro(install_qt_executable)
install_qt4_executable(${ARGN})
endmacro()
macro(setup_qt)
set(QT_USE_QTGUI TRUE)
set(QT_USE_QTSQL TRUE)
set(QT_USE_QTNETWORK TRUE)
set(QT_USE_QTXML TRUE)
set(QT_USE_QTWEBKIT TRUE)
set(QT_USE_QTDBUS TRUE)
include( ${QT_USE_FILE} )
endmacro()
endif()
if( Qt5Core_DIR )
set( HAVE_QT5 TRUE )
else( Qt5Core_DIR )
set( HAVE_QT5 FALSE )
endif( Qt5Core_DIR )

View File

@@ -11,6 +11,7 @@
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
#cmakedefine SYSCONFDIR "@SYSCONFDIR@"
#cmakedefine DATADIR "@DATADIR@"

View File

@@ -22,7 +22,7 @@ if(SPHINX_FOUND)
add_custom_target(doc DEPENDS doc-html doc-man COMMENT "Building documentation...")
endif(WITH_DOC)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ocdoc")
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ocdoc/_shared_assets")
add_dependencies(doc doc-html-org)
add_dependencies(doc doc-html-com)
endif()
@@ -36,7 +36,7 @@ if(SPHINX_FOUND)
-D latex_logo=${LATEX_LOGO}
${CMAKE_CURRENT_SOURCE_DIR}
${SPHINX_PDF_DIR} )
add_custom_target(doc-pdf make -C ${SPHINX_PDF_DIR} all-pdf
add_custom_target(doc-pdf $(MAKE) -C ${SPHINX_PDF_DIR} all-pdf
DEPENDS doc-latex )
add_dependencies(doc doc-pdf)
if (WITH_DOC)

View File

@@ -1,153 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OwncloudDocumentation.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OwncloudDocumentation.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/OwncloudDocumentation"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OwncloudDocumentation"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -7,6 +7,11 @@ This section explains how to build the ownCloud Client from source
for all major platforms. You should read this section if you want
to development on the desktop client.
Note that the building instruction are subject to change as development
proceeds. It is important to check the version which is to built.
This instructions were updated to work with ownCloud Client 1.5.
Linux
-----
@@ -36,9 +41,10 @@ its own repository which contains non-standard recipes. Add it with::
Next, install the missing dependencies::
brew install $(brew deps ocsync)
brew install $(brew deps ocsync)
brew install $(brew deps mirall)
To build mirall and csync, follow the `generic build instructions`_.
.. note::
@@ -51,13 +57,12 @@ Windows (cross-compile)
Due to the amount of dependencies that csync entails, building the client
for Windows is **currently only supported on openSUSE**, by using the MinGW
cross compiler. You can set up openSUSE 12.1 or 12.2 in a virtual machine
cross compiler. You can set up openSUSE 12.1, 12.2 or 13.1 in a virtual machine
if you do not have it installed already.
In order to cross-compile, the following repositories need to be added
via YaST or ``zypper ar`` (adjust when using openSUSE 12.2)::
via YaST or ``zypper ar`` (adjust when using openSUSE 12.2 or 13.1)::
zypper ar http://download.opensuse.org/repositories/isv:/ownCloud:/devel:/mingw:/win32/openSUSE_12.1/isv:ownCloud:devel:mingw:win32.repo
zypper ar http://download.opensuse.org/repositories/windows:/mingw:/win32/openSUSE_12.1/windows:mingw:win32.repo
zypper ar http://download.opensuse.org/repositories/windows:/mingw/openSUSE_12.1/windows:mingw.repo
@@ -65,14 +70,16 @@ Next, install the cross-compiler packages and the cross-compiled dependencies::
zypper install cmake make mingw32-cross-binutils mingw32-cross-cpp mingw32-cross-gcc \
mingw32-cross-gcc-c++ mingw32-cross-pkg-config mingw32-filesystem \
mingw32-headers mingw32-runtime site-config mingw32-iniparser-devel \
mingw32-libsqlite-devel mingw32-dlfcn-devel mingw32-libssh2-devel \
kdewin-png2ico mingw32-libqt4 mingw32-libqt4-devel mingw32-libgcrypt \
mingw32-libgnutls mingw32-libneon mingw32-libneon-devel mingw32-libbeecrypt \
mingw32-libopenssl mingw32-openssl mingw32-libpng-devel mingw32-libsqlite \
mingw32-qtkeychain mingw32-qtkeychain-devel mingw32-iniparser mingw32-dlfcn \
mingw32-libintl-devel mingw32-libneon-devel mingw32-libopenssl-devel \
mingw32-libproxy-devel mingw32-libxml2-devel mingw32-zlib-devel
mingw32-headers mingw32-runtime site-config mingw32-libqt4-sql \
mingw32-libqt4-sql-sqlite mingw32-libsqlite-devel \
mingw32-dlfcn-devel mingw32-libssh2-devel kdewin-png2ico \
mingw32-libqt4 mingw32-libqt4-devel mingw32-libgcrypt \
mingw32-libgnutls mingw32-libneon-openssl mingw32-libneon-devel \
mingw32-libbeecrypt mingw32-libopenssl mingw32-openssl \
mingw32-libpng-devel mingw32-libsqlite mingw32-qtkeychain \
mingw32-qtkeychain-devel mingw32-dlfcn mingw32-libintl-devel \
mingw32-libneon-devel mingw32-libopenssl-devel mingw32-libproxy-devel \
mingw32-libxml2-devel mingw32-zlib-devel
For the installer, the NSIS installer package is also required::
@@ -84,10 +91,10 @@ For the installer, the NSIS installer package is also required::
mingw32-cross-nsis-plugin-processes mingw32-cross-nsis-plugin-uac
You will also need to manually download and install the following files with
``rpm -ivh <package>`` (They will also work with OpenSUSE 12.2)::
``rpm -ivh <package>`` (They will also work with openSUSE 12.2 and newer)::
rpm -ihv http://pmbs.links2linux.org/download/mingw:/32/openSUSE_12.1/x86_64/mingw32-cross-nsis-plugin-processes-0-1.1.x86_64.rpm
rpm -ihv http://pmbs.links2linux.org/download/mingw:/32/openSUSE_12.1/x86_64/mingw32-cross-nsis-plugin-uac-0-3.1.x86_64.rpm
rpm -ihv http://download.tomahawk-player.org/packman/mingw:32/openSUSE_12.1/x86_64/mingw32-cross-nsis-plugin-processes-0-1.1.x86_64.rpm
rpm -ihv http://download.tomahawk-player.org/packman/mingw:32/openSUSE_12.1/x86_64/mingw32-cross-nsis-plugin-uac-0-3.1.x86_64.rpm
Now, follow the `generic build instructions`_, but pay attention to
the following differences:
@@ -117,7 +124,7 @@ CMake and Mirall can be downloaded at ownCloud's `Client Download Page`_.
If you want to build the leading edge version of the client, you should
use the latest versions of Mirall and CSync via Git_, like so::
git clone git://git.csync.org/users/freitag/csync.git ocsync
git clone git://git.csync.org/users/owncloud/csync.git ocsync
git clone git://github.com/owncloud/mirall.git
Next, create build directories::
@@ -126,11 +133,11 @@ Next, create build directories::
mkdir mirall-build
This guide assumes that all directories are residing next to each other.
Next, make sure to check out the 'dav' branch in the newly checked out
Next, make sure to check out the branch called 'ocsync' in the newly checked out
`ocsync` directory::
cd ocsync
git checkout dav
git checkout ocsync
The first package to build is CSync::
@@ -139,9 +146,10 @@ The first package to build is CSync::
make
You probably have to satisfy some dependencies. Make sure to install all the
needed development packages. You will need ``iniparser``, ``sqlite3`` as well as
``neon`` for the ownCloud module. Take special care about ``neon``. If that is
missing, the cmake run will succeed but silently not build the ownCloud module.
needed development packages. You will need ``sqlite3`` as well as ``neon`` for
the ownCloud module. Take special care about ``neon``. If that is missing, the
cmake run will succeed but silently not build the ownCloud module.
``libssh`` and ``libsmbclient`` are optional and not required for the client
to work. If you want to install the client, run ``make install`` as a final step.

View File

@@ -64,7 +64,7 @@ release = '@VERSION@'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
exclude_patterns = ['_build','scripts/*']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

View File

@@ -25,7 +25,7 @@ Here is an explanation of the individual items in the menu:
* Operation indicator: Shows the status of the current sync process, or
``Up to date`` if server and client are in sync.
* **Recent Changes**: shows the last six files modified by sync operations,
and provides access to the Sync Protocol, which lists all changes
and provides access to the Sync status, which lists all changes
since the last restart of ownCloud Client.
* ``Settings...``: provides access to the settings menu.
* ``Help``: Opens a browser to display this help.
@@ -120,12 +120,12 @@ certain DSL lines with very limited upstream capacity.
ownCloud Client will pick up changes immediately, but ongoing operations
will finish using the old settings.
The Sync Protocol
~~~~~~~~~~~~~~~~~
The Sync Status Display
~~~~~~~~~~~~~~~~~~~~~~~
.. index:: sync protocol
.. index:: sync status
The ``Sync Protocol`` window, which can be invoked from either from the main
The ``Sync Status`` window, which can be invoked from either from the main
menu (``Recent Changes`` -> ``Details...``) or the ``Account Settings``
(``Info`` button), will provide you with an in-depth summary of the recent
sync activity. It will also show files that have not been synched (ignored
@@ -167,6 +167,17 @@ see :ref:`ignored-files-label`.
.. image:: images/ignored_files_editor.png
:scale: 50%
Pattern Matching
^^^^^^^^^^^^^^^^
To match file names against the exclude patterns, the unix standard C
library function fnmatch is used. It checks the filename against the pattern
using standard shell wildcard pattern matching. Check `The opengroup website
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01>`
for the gory details.
The path that is checked is the relative path unter the sync root directory.
Examples:
^^^^^^^^^
+-----------+------------------------------+

View File

@@ -5,4 +5,4 @@ Exec=@APPLICATION_EXECUTABLE@
Name=@APPLICATION_NAME@ desktop sync client
GenericName=Folder Sync
Icon=@APPLICATION_EXECUTABLE@
Keywords=@APPLICATION_NAME@;syncing;file;sharing
Keywords=@APPLICATION_NAME@;syncing;file;sharing;

View File

@@ -11,6 +11,7 @@
<file>resources/view-refresh.png</file>
<file>resources/warning-16.png</file>
<file>resources/settings.png</file>
<file>resources/activity.png</file>
<file>resources/network.png</file>
<file>resources/owncloud_logo_blue.png</file>
</qresource>

BIN
resources/activity.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

245
src/3rdparty/csync/c_jhash.h vendored Normal file
View File

@@ -0,0 +1,245 @@
/*
* c_jhash.c Jenkins Hash
*
* Copyright (c) 1997 Bob Jenkins <bob_jenkins@burtleburtle.net>
*
* lookup8.c, by Bob Jenkins, January 4 1997, Public Domain.
* hash(), hash2(), hash3, and _c_mix() are externally useful functions.
* Routines to test the hash are included if SELF_TEST is defined.
* You can use this free for any purpose. It has no warranty.
*
* See http://burtleburtle.net/bob/hash/evahash.html
*/
/**
* @file c_jhash.h
*
* @brief Interface of the cynapses jhash implementation
*
* @defgroup cynJHashInternals cynapses libc jhash function
* @ingroup cynLibraryAPI
*
* @{
*/
#ifndef _C_JHASH_H
#define _C_JHASH_H
#include <stdint.h>
#define c_hashsize(n) ((uint8_t) 1 << (n))
#define c_hashmask(n) (xhashsize(n) - 1)
/**
* _c_mix -- Mix 3 32-bit values reversibly.
*
* For every delta with one or two bit set, and the deltas of all three
* high bits or all three low bits, whether the original value of a,b,c
* is almost all zero or is uniformly distributed,
* If _c_mix() is run forward or backward, at least 32 bits in a,b,c
* have at least 1/4 probability of changing.
* If _c_mix() is run forward, every bit of c will change between 1/3 and
* 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.)
* _c_mix() was built out of 36 single-cycle latency instructions in a
* structure that could supported 2x parallelism, like so:
* a -= b;
* a -= c; x = (c>>13);
* b -= c; a ^= x;
* b -= a; x = (a<<8);
* c -= a; b ^= x;
* c -= b; x = (b>>13);
* ...
*
* Unfortunately, superscalar Pentiums and Sparcs can't take advantage
* of that parallelism. They've also turned some of those single-cycle
* latency instructions into multi-cycle latency instructions. Still,
* this is the fastest good hash I could find. There were about 2^^68
* to choose from. I only looked at a billion or so.
*/
#define _c_mix(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>13); \
b -= c; b -= a; b ^= (a<<8); \
c -= a; c -= b; c ^= (b>>13); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<16); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>3); \
b -= c; b -= a; b ^= (a<<10); \
c -= a; c -= b; c ^= (b>>15); \
}
/**
* _c_mix64 -- Mix 3 64-bit values reversibly.
*
* _c_mix64() takes 48 machine instructions, but only 24 cycles on a superscalar
* machine (like Intel's new MMX architecture). It requires 4 64-bit
* registers for 4::2 parallelism.
* All 1-bit deltas, all 2-bit deltas, all deltas composed of top bits of
* (a,b,c), and all deltas of bottom bits were tested. All deltas were
* tested both on random keys and on keys that were nearly all zero.
* These deltas all cause every bit of c to change between 1/3 and 2/3
* of the time (well, only 113/400 to 287/400 of the time for some
* 2-bit delta). These deltas all cause at least 80 bits to change
* among (a,b,c) when the _c_mix is run either forward or backward (yes it
* is reversible).
* This implies that a hash using _c_mix64 has no funnels. There may be
* characteristics with 3-bit deltas or bigger, I didn't test for
* those.
*/
#define _c_mix64(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>43); \
b -= c; b -= a; b ^= (a<<9); \
c -= a; c -= b; c ^= (b>>8); \
a -= b; a -= c; a ^= (c>>38); \
b -= c; b -= a; b ^= (a<<23); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>35); \
b -= c; b -= a; b ^= (a<<49); \
c -= a; c -= b; c ^= (b>>11); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<18); \
c -= a; c -= b; c ^= (b>>22); \
}
/**
* @brief hash a variable-length key into a 32-bit value
*
* The best hash table sizes are powers of 2. There is no need to do
* mod a prime (mod is sooo slow!). If you need less than 32 bits,
* use a bitmask. For example, if you need only 10 bits, do
* h = (h & hashmask(10));
* In which case, the hash table should have hashsize(10) elements.
*
* Use for hash table lookup, or anything where one collision in 2^32 is
* acceptable. Do NOT use for cryptographic purposes.
*
* @param k The key (the unaligned variable-length array of bytes).
*
* @param length The length of the key, counting by bytes.
*
* @param initval Initial value, can be any 4-byte value.
*
* @return Returns a 32-bit value. Every bit of the key affects every bit
* of the return value. Every 1-bit and 2-bit delta achieves
* avalanche. About 36+6len instructions.
*/
static inline uint32_t c_jhash(const uint8_t *k, uint32_t length, uint32_t initval) {
uint32_t a,b,c,len;
/* Set up the internal state */
len = length;
a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
c = initval; /* the previous hash value */
while (len >= 12) {
a += (k[0] +((uint32_t)k[1]<<8) +((uint32_t)k[2]<<16) +((uint32_t)k[3]<<24));
b += (k[4] +((uint32_t)k[5]<<8) +((uint32_t)k[6]<<16) +((uint32_t)k[7]<<24));
c += (k[8] +((uint32_t)k[9]<<8) +((uint32_t)k[10]<<16)+((uint32_t)k[11]<<24));
_c_mix(a,b,c);
k += 12; len -= 12;
}
/* handle the last 11 bytes */
c += length;
/* all the case statements fall through */
switch(len) {
case 11: c+=((uint32_t)k[10]<<24);
case 10: c+=((uint32_t)k[9]<<16);
case 9 : c+=((uint32_t)k[8]<<8);
/* the first byte of c is reserved for the length */
case 8 : b+=((uint32_t)k[7]<<24);
case 7 : b+=((uint32_t)k[6]<<16);
case 6 : b+=((uint32_t)k[5]<<8);
case 5 : b+=k[4];
case 4 : a+=((uint32_t)k[3]<<24);
case 3 : a+=((uint32_t)k[2]<<16);
case 2 : a+=((uint32_t)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
_c_mix(a,b,c);
return c;
}
/**
* @brief hash a variable-length key into a 64-bit value
*
* The best hash table sizes are powers of 2. There is no need to do
* mod a prime (mod is sooo slow!). If you need less than 64 bits,
* use a bitmask. For example, if you need only 10 bits, do
* h = (h & hashmask(10));
* In which case, the hash table should have hashsize(10) elements.
*
* Use for hash table lookup, or anything where one collision in 2^^64
* is acceptable. Do NOT use for cryptographic purposes.
*
* @param k The key (the unaligned variable-length array of bytes).
* @param length The length of the key, counting by bytes.
* @param intval Initial value, can be any 8-byte value.
*
* @return A 64-bit value. Every bit of the key affects every bit of
* the return value. No funnels. Every 1-bit and 2-bit delta
* achieves avalanche. About 41+5len instructions.
*/
static inline uint64_t c_jhash64(const uint8_t *k, uint64_t length, uint64_t intval) {
uint64_t a,b,c,len;
/* Set up the internal state */
len = length;
a = b = intval; /* the previous hash value */
c = 0x9e3779b97f4a7c13LL; /* the golden ratio; an arbitrary value */
/* handle most of the key */
while (len >= 24)
{
a += (k[0] +((uint64_t)k[ 1]<< 8)+((uint64_t)k[ 2]<<16)+((uint64_t)k[ 3]<<24)
+((uint64_t)k[4 ]<<32)+((uint64_t)k[ 5]<<40)+((uint64_t)k[ 6]<<48)+((uint64_t)k[ 7]<<56));
b += (k[8] +((uint64_t)k[ 9]<< 8)+((uint64_t)k[10]<<16)+((uint64_t)k[11]<<24)
+((uint64_t)k[12]<<32)+((uint64_t)k[13]<<40)+((uint64_t)k[14]<<48)+((uint64_t)k[15]<<56));
c += (k[16] +((uint64_t)k[17]<< 8)+((uint64_t)k[18]<<16)+((uint64_t)k[19]<<24)
+((uint64_t)k[20]<<32)+((uint64_t)k[21]<<40)+((uint64_t)k[22]<<48)+((uint64_t)k[23]<<56));
_c_mix64(a,b,c);
k += 24; len -= 24;
}
/* handle the last 23 bytes */
c += length;
switch(len) {
case 23: c+=((uint64_t)k[22]<<56);
case 22: c+=((uint64_t)k[21]<<48);
case 21: c+=((uint64_t)k[20]<<40);
case 20: c+=((uint64_t)k[19]<<32);
case 19: c+=((uint64_t)k[18]<<24);
case 18: c+=((uint64_t)k[17]<<16);
case 17: c+=((uint64_t)k[16]<<8);
/* the first byte of c is reserved for the length */
case 16: b+=((uint64_t)k[15]<<56);
case 15: b+=((uint64_t)k[14]<<48);
case 14: b+=((uint64_t)k[13]<<40);
case 13: b+=((uint64_t)k[12]<<32);
case 12: b+=((uint64_t)k[11]<<24);
case 11: b+=((uint64_t)k[10]<<16);
case 10: b+=((uint64_t)k[ 9]<<8);
case 9: b+=((uint64_t)k[ 8]);
case 8: a+=((uint64_t)k[ 7]<<56);
case 7: a+=((uint64_t)k[ 6]<<48);
case 6: a+=((uint64_t)k[ 5]<<40);
case 5: a+=((uint64_t)k[ 4]<<32);
case 4: a+=((uint64_t)k[ 3]<<24);
case 3: a+=((uint64_t)k[ 2]<<16);
case 2: a+=((uint64_t)k[ 1]<<8);
case 1: a+=((uint64_t)k[ 0]);
/* case 0: nothing left to add */
}
_c_mix64(a,b,c);
return c;
}
/**
* }@
*/
#endif /* _C_JHASH_H */

3
src/3rdparty/qjson/AUTHORS vendored Normal file
View File

@@ -0,0 +1,3 @@
Eeli Reilin <eeli@emicode.fi>
Luis Gustavo S. Barreto <gustavosbarreto@gmail.com>
Stephen Kockentiedt <Stephen@Kockentiedt.name>

26
src/3rdparty/qjson/LICENSE vendored Normal file
View File

@@ -0,0 +1,26 @@
Copyright 2011 Eeli Reilin. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation
are those of the authors and should not be interpreted as representing
official policies, either expressed or implied, of Eeli Reilin.

96
src/3rdparty/qjson/README vendored Normal file
View File

@@ -0,0 +1,96 @@
########################################################################
1. INTRODUCTION
The Json class is a simple class for parsing JSON data into a QVariant
hierarchies. Now, we can also reverse the process and serialize
QVariant hierarchies into valid JSON data.
########################################################################
2. HOW TO USE
The parser is really easy to use. Let's say we have the following
QString of JSON data:
------------------------------------------------------------------------
{
"encoding" : "UTF-8",
"plug-ins" : [
"python",
"c++",
"ruby"
],
"indent" : {
"length" : 3,
"use_space" : true
}
}
------------------------------------------------------------------------
We would first call the parse-method:
------------------------------------------------------------------------
//Say that we're using the QtJson namespace
using namespace QtJson;
bool ok;
//json is a QString containing the JSON data
QVariantMap result = Json::parse(json, ok).toMap();
if(!ok) {
qFatal("An error occurred during parsing");
exit(1);
}
------------------------------------------------------------------------
Assuming the parsing process completed without errors, we would then
go through the hierarchy:
------------------------------------------------------------------------
qDebug() << "encoding:" << result["encoding"].toString();
qDebug() << "plugins:";
foreach(QVariant plugin, result["plug-ins"].toList()) {
qDebug() << "\t-" << plugin.toString();
}
QVariantMap nestedMap = result["indent"].toMap();
qDebug() << "length:" << nestedMap["length"].toInt();
qDebug() << "use_space:" << nestedMap["use_space"].toBool();
------------------------------------------------------------------------
The previous code would print out the following:
------------------------------------------------------------------------
encoding: "UTF-8"
plugins:
- "python"
- "c++"
- "ruby"
length: 3
use_space: true
------------------------------------------------------------------------
To write JSON data from Qt object is as simple as parsing:
------------------------------------------------------------------------
QVariantMap map;
map["name"] = "Name";
map["age"] = 22;
QByteArray data = Json::serialize(map);
------------------------------------------------------------------------
The byte array 'data' contains valid JSON data:
------------------------------------------------------------------------
{
name: "Luis Gustavo",
age: 22,
}
------------------------------------------------------------------------
########################################################################
4. CONTRIBUTING
The code is available to download at GitHub. Contribute if you dare!

653
src/3rdparty/qjson/json.cpp vendored Normal file
View File

@@ -0,0 +1,653 @@
/* Copyright 2011 Eeli Reilin. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of Eeli Reilin.
*/
/**
* \file json.cpp
*/
#include "json.h"
namespace QtJson
{
static QString sanitizeString(QString str);
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep);
static QVariant parseValue(const QString &json, int &index, bool &success);
static QVariant parseObject(const QString &json, int &index, bool &success);
static QVariant parseArray(const QString &json, int &index, bool &success);
static QVariant parseString(const QString &json, int &index, bool &success);
static QVariant parseNumber(const QString &json, int &index);
static int lastIndexOfNumber(const QString &json, int index);
static void eatWhitespace(const QString &json, int &index);
static int lookAhead(const QString &json, int index);
static int nextToken(const QString &json, int &index);
/***** public *****/
/**
* parse
*/
QVariant parse(const QString &json)
{
bool success = true;
return parse(json, success);
}
/**
* parse
*/
QVariant parse(const QString &json, bool &success)
{
success = true;
//Return an empty QVariant if the JSON data is either null or empty
if(!json.isNull() || !json.isEmpty())
{
QString data = json;
//We'll start from index 0
int index = 0;
//Parse the first value
QVariant value = parseValue(data, index, success);
//Return the parsed value
return value;
}
else
{
//Return the empty QVariant
return QVariant();
}
}
QByteArray serialize(const QVariant &data)
{
bool success = true;
return serialize(data, success);
}
QByteArray serialize(const QVariant &data, bool &success)
{
QByteArray str;
success = true;
if(!data.isValid()) // invalid or null?
{
str = "null";
}
else if((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) // variant is a list?
{
QList<QByteArray> values;
const QVariantList list = data.toList();
Q_FOREACH(const QVariant& v, list)
{
QByteArray serializedValue = serialize(v);
if(serializedValue.isNull())
{
success = false;
break;
}
values << serializedValue;
}
str = "[ " + join( values, ", " ) + " ]";
}
else if(data.type() == QVariant::Hash) // variant is a hash?
{
const QVariantHash vhash = data.toHash();
QHashIterator<QString, QVariant> it( vhash );
str = "{ ";
QList<QByteArray> pairs;
while(it.hasNext())
{
it.next();
QByteArray serializedValue = serialize(it.value());
if(serializedValue.isNull())
{
success = false;
break;
}
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
}
str += join(pairs, ", ");
str += " }";
}
else if(data.type() == QVariant::Map) // variant is a map?
{
const QVariantMap vmap = data.toMap();
QMapIterator<QString, QVariant> it( vmap );
str = "{ ";
QList<QByteArray> pairs;
while(it.hasNext())
{
it.next();
QByteArray serializedValue = serialize(it.value());
if(serializedValue.isNull())
{
success = false;
break;
}
pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue;
}
str += join(pairs, ", ");
str += " }";
}
else if((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) // a string or a byte array?
{
str = sanitizeString(data.toString()).toUtf8();
}
else if(data.type() == QVariant::Double) // double?
{
str = QByteArray::number(data.toDouble(), 'g', 20);
if(!str.contains(".") && ! str.contains("e"))
{
str += ".0";
}
}
else if (data.type() == QVariant::Bool) // boolean value?
{
str = data.toBool() ? "true" : "false";
}
else if (data.type() == QVariant::ULongLong) // large unsigned number?
{
str = QByteArray::number(data.value<qulonglong>());
}
else if ( data.canConvert<qlonglong>() ) // any signed number?
{
str = QByteArray::number(data.value<qlonglong>());
}
else if (data.canConvert<long>())
{
str = QString::number(data.value<long>()).toUtf8();
}
else if (data.canConvert<QString>()) // can value be converted to string?
{
// this will catch QDate, QDateTime, QUrl, ...
str = sanitizeString(data.toString()).toUtf8();
}
else
{
success = false;
}
if (success)
{
return str;
}
else
{
return QByteArray();
}
}
/***** private *****/
/**
* \enum JsonToken
*/
enum JsonToken
{
JsonTokenNone = 0,
JsonTokenCurlyOpen = 1,
JsonTokenCurlyClose = 2,
JsonTokenSquaredOpen = 3,
JsonTokenSquaredClose = 4,
JsonTokenColon = 5,
JsonTokenComma = 6,
JsonTokenString = 7,
JsonTokenNumber = 8,
JsonTokenTrue = 9,
JsonTokenFalse = 10,
JsonTokenNull = 11
};
static QString sanitizeString(QString str)
{
str.replace(QLatin1String("\\"), QLatin1String("\\\\"));
str.replace(QLatin1String("\""), QLatin1String("\\\""));
str.replace(QLatin1String("\b"), QLatin1String("\\b"));
str.replace(QLatin1String("\f"), QLatin1String("\\f"));
str.replace(QLatin1String("\n"), QLatin1String("\\n"));
str.replace(QLatin1String("\r"), QLatin1String("\\r"));
str.replace(QLatin1String("\t"), QLatin1String("\\t"));
return QString(QLatin1String("\"%1\"")).arg(str);
}
static QByteArray join(const QList<QByteArray> &list, const QByteArray &sep)
{
QByteArray res;
Q_FOREACH(const QByteArray &i, list)
{
if(!res.isEmpty())
{
res += sep;
}
res += i;
}
return res;
}
/**
* parseValue
*/
static QVariant parseValue(const QString &json, int &index, bool &success)
{
//Determine what kind of data we should parse by
//checking out the upcoming token
switch(lookAhead(json, index))
{
case JsonTokenString:
return parseString(json, index, success);
case JsonTokenNumber:
return parseNumber(json, index);
case JsonTokenCurlyOpen:
return parseObject(json, index, success);
case JsonTokenSquaredOpen:
return parseArray(json, index, success);
case JsonTokenTrue:
nextToken(json, index);
return QVariant(true);
case JsonTokenFalse:
nextToken(json, index);
return QVariant(false);
case JsonTokenNull:
nextToken(json, index);
return QVariant();
case JsonTokenNone:
break;
}
//If there were no tokens, flag the failure and return an empty QVariant
success = false;
return QVariant();
}
/**
* parseObject
*/
static QVariant parseObject(const QString &json, int &index, bool &success)
{
QVariantMap map;
int token;
//Get rid of the whitespace and increment index
nextToken(json, index);
//Loop through all of the key/value pairs of the object
bool done = false;
while(!done)
{
//Get the upcoming token
token = lookAhead(json, index);
if(token == JsonTokenNone)
{
success = false;
return QVariantMap();
}
else if(token == JsonTokenComma)
{
nextToken(json, index);
}
else if(token == JsonTokenCurlyClose)
{
nextToken(json, index);
return map;
}
else
{
//Parse the key/value pair's name
QString name = parseString(json, index, success).toString();
if(!success)
{
return QVariantMap();
}
//Get the next token
token = nextToken(json, index);
//If the next token is not a colon, flag the failure
//return an empty QVariant
if(token != JsonTokenColon)
{
success = false;
return QVariant(QVariantMap());
}
//Parse the key/value pair's value
QVariant value = parseValue(json, index, success);
if(!success)
{
return QVariantMap();
}
//Assign the value to the key in the map
map[name] = value;
}
}
//Return the map successfully
return QVariant(map);
}
/**
* parseArray
*/
static QVariant parseArray(const QString &json, int &index, bool &success)
{
QVariantList list;
nextToken(json, index);
bool done = false;
while(!done)
{
int token = lookAhead(json, index);
if(token == JsonTokenNone)
{
success = false;
return QVariantList();
}
else if(token == JsonTokenComma)
{
nextToken(json, index);
}
else if(token == JsonTokenSquaredClose)
{
nextToken(json, index);
break;
}
else
{
QVariant value = parseValue(json, index, success);
if(!success)
{
return QVariantList();
}
list.push_back(value);
}
}
return QVariant(list);
}
/**
* parseString
*/
static QVariant parseString(const QString &json, int &index, bool &success)
{
QString s;
QChar c;
eatWhitespace(json, index);
c = json[index++];
bool complete = false;
while(!complete)
{
if(index == json.size())
{
break;
}
c = json[index++];
if(c == '\"')
{
complete = true;
break;
}
else if(c == '\\')
{
if(index == json.size())
{
break;
}
c = json[index++];
if(c == '\"')
{
s.append('\"');
}
else if(c == '\\')
{
s.append('\\');
}
else if(c == '/')
{
s.append('/');
}
else if(c == 'b')
{
s.append('\b');
}
else if(c == 'f')
{
s.append('\f');
}
else if(c == 'n')
{
s.append('\n');
}
else if(c == 'r')
{
s.append('\r');
}
else if(c == 't')
{
s.append('\t');
}
else if(c == 'u')
{
int remainingLength = json.size() - index;
if(remainingLength >= 4)
{
QString unicodeStr = json.mid(index, 4);
int symbol = unicodeStr.toInt(0, 16);
s.append(QChar(symbol));
index += 4;
}
else
{
break;
}
}
}
else
{
s.append(c);
}
}
if(!complete)
{
success = false;
return QVariant();
}
return QVariant(s);
}
/**
* parseNumber
*/
static QVariant parseNumber(const QString &json, int &index)
{
eatWhitespace(json, index);
int lastIndex = lastIndexOfNumber(json, index);
int charLength = (lastIndex - index) + 1;
QString numberStr;
numberStr = json.mid(index, charLength);
index = lastIndex + 1;
if (numberStr.contains('.')) {
return QVariant(numberStr.toDouble(NULL));
} else if (numberStr.startsWith('-')) {
return QVariant(numberStr.toLongLong(NULL));
} else {
return QVariant(numberStr.toULongLong(NULL));
}
}
/**
* lastIndexOfNumber
*/
static int lastIndexOfNumber(const QString &json, int index)
{
int lastIndex;
for(lastIndex = index; lastIndex < json.size(); lastIndex++)
{
if(QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1)
{
break;
}
}
return lastIndex -1;
}
/**
* eatWhitespace
*/
static void eatWhitespace(const QString &json, int &index)
{
for(; index < json.size(); index++)
{
if(QString(" \t\n\r").indexOf(json[index]) == -1)
{
break;
}
}
}
/**
* lookAhead
*/
static int lookAhead(const QString &json, int index)
{
int saveIndex = index;
return nextToken(json, saveIndex);
}
/**
* nextToken
*/
static int nextToken(const QString &json, int &index)
{
eatWhitespace(json, index);
if(index == json.size())
{
return JsonTokenNone;
}
QChar c = json[index];
index++;
switch(c.toLatin1())
{
case '{': return JsonTokenCurlyOpen;
case '}': return JsonTokenCurlyClose;
case '[': return JsonTokenSquaredOpen;
case ']': return JsonTokenSquaredClose;
case ',': return JsonTokenComma;
case '"': return JsonTokenString;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-': return JsonTokenNumber;
case ':': return JsonTokenColon;
}
index--;
int remainingLength = json.size() - index;
//True
if(remainingLength >= 4)
{
if (json[index] == 't' && json[index + 1] == 'r' &&
json[index + 2] == 'u' && json[index + 3] == 'e')
{
index += 4;
return JsonTokenTrue;
}
}
//False
if (remainingLength >= 5)
{
if (json[index] == 'f' && json[index + 1] == 'a' &&
json[index + 2] == 'l' && json[index + 3] == 's' &&
json[index + 4] == 'e')
{
index += 5;
return JsonTokenFalse;
}
}
//Null
if (remainingLength >= 4)
{
if (json[index] == 'n' && json[index + 1] == 'u' &&
json[index + 2] == 'l' && json[index + 3] == 'l')
{
index += 4;
return JsonTokenNull;
}
}
return JsonTokenNone;
}
} //end namespace

85
src/3rdparty/qjson/json.h vendored Normal file
View File

@@ -0,0 +1,85 @@
/* Copyright 2011 Eeli Reilin. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ''AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL EELI REILIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of Eeli Reilin.
*/
/**
* \file json.h
*/
#ifndef JSON_H
#define JSON_H
#include <QVariant>
#include <QString>
/**
* \namespace QtJson
* \brief A JSON data parser
*
* Json parses a JSON data into a QVariant hierarchy.
*/
namespace QtJson
{
/**
* Parse a JSON string
*
* \param json The JSON data
*/
QVariant parse(const QString &json);
/**
* Parse a JSON string
*
* \param json The JSON data
* \param success The success of the parsing
*/
QVariant parse(const QString &json, bool &success);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
*
* \return QByteArray Textual JSON representation
*/
QByteArray serialize(const QVariant &data);
/**
* This method generates a textual JSON representation
*
* \param data The JSON data generated by the parser.
* \param success The success of the serialization
*
* \return QByteArray Textual JSON representation
*/
QByteArray serialize(const QVariant &data, bool &success);
} //end namespace
#endif //JSON_H

View File

@@ -61,11 +61,13 @@ QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char *
}
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
: QApplication(argc, argv, type)
{
sysInit();
}
#endif
#if defined(Q_WS_X11)

View File

@@ -28,6 +28,9 @@
**
**************************************************************************/
#ifndef _SHAREDTOOLS_SINGLEAPPLICATION
#define _SHAREDTOOLS_SINGLEAPPLICATION
#include <QApplication>
namespace SharedTools {
@@ -41,7 +44,9 @@ class QtSingleApplication : public QApplication
public:
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
QtSingleApplication(const QString &id, int &argc, char **argv);
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
QtSingleApplication(int &argc, char **argv, Type type);
#endif
#if defined(Q_WS_X11)
explicit QtSingleApplication(Display *dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0);
@@ -83,3 +88,4 @@ private:
};
} // namespace SharedTools
#endif // _SHAREDTOOLS_SINGLEAPPLICATION

View File

@@ -1,34 +1,18 @@
include(${QT_USE_FILE})
#add_subdirectory(integration)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
QT4_ADD_RESOURCES ( MIRALL_RC_SRC ../mirall.qrc)
qt_add_resources(MIRALL_RC_SRC ../mirall.qrc)
if ( IS_DIRECTORY ${OEM_THEME_DIR} )
QT4_ADD_RESOURCES ( MIRALL_RC_SRC ${OEM_THEME_DIR}/theme.qrc)
qt_add_resources(MIRALL_RC_SRC ${OEM_THEME_DIR}/theme.qrc)
set(theme_dir ${OEM_THEME_DIR}/theme)
else()
QT4_ADD_RESOURCES ( MIRALL_RC_SRC ../theme.qrc)
qt_add_resources(MIRALL_RC_SRC ../theme.qrc)
set(theme_dir ${CMAKE_CURRENT_SOURCE_DIR}/../theme)
endif()
set(synclib_NAME ${APPLICATION_EXECUTABLE}sync)
set(mirall_UI
mirall/folderwizardsourcepage.ui
mirall/folderwizardtargetpage.ui
mirall/sslerrordialog.ui
mirall/settingsdialog.ui
mirall/generalsettings.ui
mirall/networksettings.ui
mirall/accountsettings.ui
mirall/ignorelisteditor.ui
mirall/itemprogressdialog.ui
wizard/owncloudsetupnocredspage.ui
wizard/owncloudhttpcredspage.ui
wizard/owncloudwizardresultpage.ui
wizard/owncloudadvancedsetuppage.ui
)
set(3rdparty_SRC
3rdparty/qtsingleapplication/qtsingleapplication.cpp
3rdparty/qtsingleapplication/qtlocalpeer.cpp
@@ -45,9 +29,10 @@ set(3rdparty_HEADER
3rdparty/fancylineedit/fancylineedit.h
3rdparty/QProgressIndicator/QProgressIndicator.h
)
qt4_wrap_cpp(3rdparty_MOC ${3rdparty_HEADER})
if(NOT WIN32)
qt_wrap_cpp(3rdparty_MOC ${3rdparty_HEADER})
if(NOT WIN32)
list(APPEND 3rdparty_SRC 3rdparty/qtlockedfile/qtlockedfile_unix.cpp)
else()
list(APPEND 3rdparty_SRC 3rdparty/qtlockedfile/qtlockedfile_win.cpp )
@@ -58,31 +43,34 @@ set(3rdparty_INC
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qtsingleapplication
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/QProgressIndicator
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/fancylineedit
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/csync
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qjson
)
qt4_wrap_ui(mirall_UI_SRCS ${mirall_UI})
set(libsync_SRCS
mirall/folderman.cpp
mirall/folder.cpp
mirall/folderwatcher.cpp
mirall/syncresult.cpp
mirall/networklocation.cpp
mirall/mirallconfigfile.cpp
mirall/csyncthread.cpp
mirall/owncloudpropagator.cpp
mirall/syncjournalfilerecord.cpp
mirall/syncjournaldb.cpp
mirall/fileutils.cpp
mirall/theme.cpp
mirall/owncloudtheme.cpp
mirall/owncloudinfo.cpp
mirall/logger.cpp
mirall/utility.cpp
mirall/connectionvalidator.cpp
mirall/progressdispatcher.cpp
mirall/mirallaccessmanager.cpp
mirall/networkjobs.cpp
mirall/account.cpp
mirall/quotainfo.cpp
creds/dummycredentials.cpp
creds/httpcredentials.cpp
creds/credentialsfactory.cpp
creds/http/credentialstore.cpp
creds/http/httpconfigfile.cpp
creds/shibbolethcredentials.cpp
creds/shibboleth/shibbolethaccessmanager.cpp
@@ -91,6 +79,7 @@ set(libsync_SRCS
creds/shibboleth/shibbolethrefresher.cpp
creds/shibboleth/shibbolethconfigfile.cpp
creds/credentialscommon.cpp
3rdparty/qjson/json.cpp
)
set(libsync_HEADERS
@@ -98,18 +87,21 @@ set(libsync_HEADERS
mirall/folder.h
mirall/folderwatcher.h
mirall/csyncthread.h
mirall/owncloudpropagator.h
mirall/syncjournaldb.h
mirall/theme.h
mirall/owncloudtheme.h
mirall/owncloudinfo.h
mirall/logger.h
mirall/connectionvalidator.h
mirall/progressdispatcher.h
mirall/mirallaccessmanager.h
mirall/networkjobs.h
mirall/account.h
mirall/quotainfo.h
creds/abstractcredentials.h
creds/dummycredentials.h
creds/httpcredentials.h
creds/credentialsfactory.h
creds/http/credentialstore.h
creds/http/httpconfigfile.h
creds/shibbolethcredentials.h
creds/shibboleth/shibbolethaccessmanager.h
@@ -118,6 +110,7 @@ set(libsync_HEADERS
creds/shibboleth/shibbolethrefresher.h
creds/shibboleth/shibbolethconfigfile.h
creds/credentialscommon.h
3rdparty/qjson/json.h
)
IF( INOTIFY_FOUND )
@@ -126,7 +119,7 @@ IF( INOTIFY_FOUND )
set(libsync_HEADERS ${libsync_HEADERS} mirall/inotify.h)
set(libsync_HEADERS ${libsync_HEADERS} mirall/folderwatcher_inotify.h)
ENDIF()
IF( WIN32 )
IF( WIN32 )
set(libsync_SRCS ${libsync_SRCS} mirall/folderwatcher_win.cpp)
set(libsync_HEADERS ${libsync_HEADERS} mirall/folderwatcher_win.h)
ENDIF()
@@ -135,20 +128,42 @@ IF( APPLE )
ENDIF()
qt4_wrap_cpp(syncMoc ${libsync_HEADERS})
qt_wrap_cpp(syncMoc ${libsync_HEADERS})
IF( DEFINED CSYNC_BUILD_PATH )
SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbf.a)
ELSE()
FIND_LIBRARY(HTTPBF_LIBRARY NAMES httpbf HINTS $ENV{CSYNC_DIR})
ENDIF()
list(APPEND libsync_LINK_TARGETS
${QT_LIBRARIES}
${CSYNC_LIBRARY}
dl
${HTTPBF_LIBRARY}
)
IF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
list(APPEND libsync_LINK_TARGETS
inotify
)
ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
if(QTKEYCHAIN_FOUND)
list(APPEND libsync_LINK_TARGETS ${QTKEYCHAIN_LIBRARY})
include_directories(${QTKEYCHAIN_INCLUDE_DIR})
endif()
if(NEON_FOUND)
list(APPEND libsync_LINK_TARGETS ${NEON_LIBRARIES})
include_directories(${NEON_INCLUDE_DIRS})
endif()
add_library(${synclib_NAME} SHARED ${libsync_SRCS} ${syncMoc})
qt5_use_modules(${synclib_NAME} Widgets Network Xml WebKitWidgets Sql)
set_target_properties( ${synclib_NAME} PROPERTIES
VERSION ${VERSION}
SOVERSION ${SOVERSION}
@@ -175,11 +190,30 @@ else()
install(TARGETS ${synclib_NAME} DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/MacOS)
endif()
set(mirall_UI
mirall/folderwizardsourcepage.ui
mirall/folderwizardtargetpage.ui
mirall/sslerrordialog.ui
mirall/settingsdialog.ui
mirall/generalsettings.ui
mirall/networksettings.ui
mirall/accountsettings.ui
mirall/ignorelisteditor.ui
mirall/protocolwidget.ui
wizard/owncloudsetupnocredspage.ui
wizard/owncloudhttpcredspage.ui
wizard/owncloudwizardresultpage.ui
wizard/owncloudadvancedsetuppage.ui
)
qt_wrap_ui(mirall_UI_SRCS ${mirall_UI})
set(mirall_SRCS
mirall/application.cpp
mirall/systray.cpp
mirall/folderwizard.cpp
mirall/folderstatusmodel.cpp
mirall/protocolwidget.cpp
wizard/owncloudwizard.cpp
wizard/owncloudsetuppage.cpp
wizard/owncloudhttpcredspage.cpp
@@ -197,7 +231,8 @@ set(mirall_SRCS
mirall/networksettings.cpp
mirall/accountsettings.cpp
mirall/ignorelisteditor.cpp
mirall/itemprogressdialog.cpp
mirall/owncloudgui.cpp
mirall/socketapi.cpp
)
set(mirall_HEADERS
@@ -221,7 +256,9 @@ set(mirall_HEADERS
mirall/networksettings.h
mirall/accountsettings.h
mirall/ignorelisteditor.h
mirall/itemprogressdialog.h
mirall/protocolwidget.h
mirall/owncloudgui.h
mirall/socketapi.h
)
if( UNIX AND NOT APPLE)
@@ -231,12 +268,12 @@ if( UNIX AND NOT APPLE)
endif()
# csync is required.
include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_BUILD_PATH}/src)
include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_INCLUDE_DIR}/httpbf/src ${CSYNC_BUILD_PATH}/src)
include_directories(${3rdparty_INC})
qt4_wrap_cpp(mirallMoc ${mirall_HEADERS})
qt_wrap_cpp(mirallMoc ${mirall_HEADERS})
qt4_add_translation(mirall_I18N ${TRANSLATIONS})
qt_add_translation(mirall_I18N ${TRANSLATIONS})
set( final_src
${mirall_HEADERS}
@@ -254,19 +291,31 @@ set( final_src
include( AddAppIconMacro )
set(ownCloud_old ${ownCloud})
kde4_add_app_icon( ownCloud "${theme_dir}/colored/${APPLICATION_EXECUTABLE}-icon*.png")
# set an icon_app_name. For historical reasons we can not use the
# application_shortname for ownCloud but must rather set it manually.
if ( EXISTS ${OEM_THEME_DIR}/OEM.cmake )
set(ICON_APP_NAME ${APPLICATION_SHORTNAME})
else()
set(ICON_APP_NAME "owncloud")
endif()
kde4_add_app_icon( ownCloud "${theme_dir}/colored/${ICON_APP_NAME}-icon*.png")
list(APPEND final_src ${ownCloud})
set(ownCloud ${ownCloud_old})
if (WITH_DBUS)
set(ADDITIONAL_APP_MODULES DBus)
endif(WITH_DBUS)
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
if(NOT WIN32)
file( GLOB _icons "${theme_dir}/colored/${APPLICATION_EXECUTABLE}-icon-*.png" )
file( GLOB _icons "${theme_dir}/colored/${ICON_APP_NAME}-icon-*.png" )
foreach( _file ${_icons} )
string( REPLACE "${theme_dir}/colored/${APPLICATION_EXECUTABLE}-icon-" "" _res ${_file} )
string( REPLACE "${theme_dir}/colored/${ICON_APP_NAME}-icon-" "" _res ${_file} )
string( REPLACE ".png" "" _res ${_res} )
install( FILES ${_file} RENAME ${APPLICATION_EXECUTABLE}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps )
install( FILES ${_file} RENAME ${ICON_APP_NAME}.png DESTINATION ${DATADIR}/icons/hicolor/${_res}x${_res}/apps )
endforeach( _file )
endif(NOT WIN32)
@@ -276,7 +325,7 @@ endif(NOT WIN32)
# add_executable( ${APPLICATION_EXECUTABLE} main.cpp ${final_src})
add_executable( ${APPLICATION_EXECUTABLE} WIN32 main.cpp ${final_src})
qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml WebKitWidgets Sql ${ADDITIONAL_APP_MODULES})
else()
set(BIN_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
include(DeployQt4)
@@ -286,13 +335,7 @@ else()
# we must add MACOSX_BUNDLE only if building a bundle
add_executable( ${APPLICATION_EXECUTABLE} WIN32 MACOSX_BUNDLE main.cpp ${final_src})
#FIXME: hardcoded path
if ( EXISTS ${CSYNC_BINARY_DIR}/modules/ocsync_owncloud.so )
install(FILES ${CSYNC_BINARY_DIR}/modules/ocsync_owncloud.so DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/Plugins)
else()
install(FILES /usr/local/lib/ocsync-0/ocsync_owncloud.so DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/Plugins)
endif()
qt5_use_modules(${APPLICATION_EXECUTABLE} Widgets Network Xml WebKitWidgets Sql ${ADDITIONAL_APP_MODULES})
set (QM_DIR ${OWNCLOUD_OSX_BUNDLE}/Contents/Resources/Translations)
install(FILES ${mirall_I18N} DESTINATION ${QM_DIR})
@@ -300,7 +343,6 @@ else()
install(FILES ${qt_I18N} DESTINATION ${QM_DIR})
file(GLOB qtkeychain_I18N ${QT_TRANSLATIONS_DIR}/qtkeychain*.qm)
install(FILES ${qtkeychain_I18N} DESTINATION ${QM_DIR})
list(APPEND dirs "/usr/local/lib")
endif()
@@ -322,7 +364,7 @@ install(TARGETS ${APPLICATION_EXECUTABLE}
# currently it needs to be done because the code right above needs to be executed no matter
# if building a bundle or not and the install_qt4_executable needs to be called afterwards
if(BUILD_OWNCLOUD_OSX_BUNDLE)
install_qt4_executable(${OWNCLOUD_OSX_BUNDLE} "" "${OWNCLOUD_OSX_BUNDLE}/Contents/Plugins/ocsync_owncloud.so" ${dirs})
install_qt4_executable(${OWNCLOUD_OSX_BUNDLE} "qtaccessiblewidgets;qsqlite")
endif()
find_program(KRAZY2_EXECUTABLE krazy2)
@@ -335,3 +377,17 @@ if(KRAZY2_EXECUTABLE)
)
endif()
set(owncloudcmd_NAME ${APPLICATION_EXECUTABLE}cmd)
set(OWNCLOUDCMD_SRC owncloudcmd/owncloudcmd.cpp)
add_executable(${owncloudcmd_NAME} ${OWNCLOUDCMD_SRC})
qt5_use_modules(${owncloudcmd_NAME} Network Sql)
set_target_properties(${owncloudcmd_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
target_link_libraries(${owncloudcmd_NAME} ${synclib_NAME})
target_link_libraries(${owncloudcmd_NAME} ${CSYNC_LIBRARY})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mirall)
install(TARGETS ${owncloudcmd_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

View File

@@ -19,27 +19,34 @@
#include <csync.h>
class QNetworkAccessManager;
class QNetworkReply;
namespace Mirall
{
class Account;
class AbstractCredentials : public QObject
{
Q_OBJECT
Q_OBJECT
public:
// No need for virtual destructor - QObject already has one.
virtual void syncContextPreInit(CSYNC* ctx) = 0;
virtual void syncContextPreStart(CSYNC* ctx) = 0;
virtual bool changed(AbstractCredentials* credentials) const = 0;
virtual QString authType() const = 0;
virtual QNetworkAccessManager* getQNAM() const = 0;
virtual bool ready() const = 0;
virtual void fetch() = 0;
virtual void persistForUrl(const QString& url) = 0;
// No need for virtual destructor - QObject already has one.
virtual void syncContextPreInit(CSYNC* ctx) = 0;
virtual void syncContextPreStart(CSYNC* ctx) = 0;
virtual bool changed(AbstractCredentials* credentials) const = 0;
virtual QString authType() const = 0;
virtual QString user() const = 0;
virtual QNetworkAccessManager* getQNAM() const = 0;
virtual bool ready() const = 0;
virtual void fetch(Account *account) = 0;
virtual bool stillValid(QNetworkReply *reply) = 0;
virtual bool fetchFromUser(Account *account) = 0;
virtual void persist(Account *account) = 0;
/** Invalidates auth token, or password for basic auth */
virtual void invalidateToken(Account *account) = 0;
Q_SIGNALS:
void fetched();
void fetched();
};
} // ns Mirall

View File

@@ -1,5 +1,4 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@kde.org>
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*
@@ -19,10 +18,12 @@
#include <QString>
#include <QSslCertificate>
#include <QDebug>
#include "creds/credentialscommon.h"
#include "mirall/utility.h"
#include "mirall/owncloudinfo.h"
#include "mirall/account.h"
namespace Mirall
{
@@ -46,7 +47,7 @@ int handleNeonSSLProblems(const char* prompt,
int pos = 0;
// This is the set of certificates which QNAM accepted, so we should accept
// them as well
QList<QSslCertificate> certs = ownCloudInfo::instance()->certificateChain();
QList<QSslCertificate> certs = AccountManager::instance()->account()->certificateChain();
while (!certOk && (pos = regexp.indexIn(qPrompt, 1+pos)) != -1) {
QString neon_fingerprint = regexp.cap(1);

View File

@@ -1,5 +1,4 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@kde.org>
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*

View File

@@ -35,6 +35,11 @@ QString DummyCredentials::authType() const
return QString::fromLatin1("dummy");
}
QString DummyCredentials::user() const
{
return QString();
}
QNetworkAccessManager* DummyCredentials::getQNAM() const
{
return new MirallAccessManager;
@@ -45,12 +50,24 @@ bool DummyCredentials::ready() const
return true;
}
void DummyCredentials::fetch()
bool DummyCredentials::stillValid(QNetworkReply *reply)
{
Q_UNUSED(reply)
return true;
}
bool DummyCredentials::fetchFromUser(Account *account)
{
Q_UNUSED(account)
return false;
}
void DummyCredentials::fetch(Account*)
{
Q_EMIT(fetched());
}
void DummyCredentials::persistForUrl(const QString&)
void DummyCredentials::persist(Account*)
{}
} // ns Mirall

View File

@@ -21,17 +21,21 @@ namespace Mirall
class DummyCredentials : public AbstractCredentials
{
Q_OBJECT
Q_OBJECT
public:
void syncContextPreInit(CSYNC* ctx);
void syncContextPreStart(CSYNC* ctx);
bool changed(AbstractCredentials* credentials) const;
QString authType() const;
QNetworkAccessManager* getQNAM() const;
bool ready() const;
void fetch();
void persistForUrl(const QString& url);
void syncContextPreInit(CSYNC* ctx);
void syncContextPreStart(CSYNC* ctx);
bool changed(AbstractCredentials* credentials) const;
QString authType() const;
QString user() const;
QNetworkAccessManager* getQNAM() const;
bool ready() const;
bool stillValid(QNetworkReply *reply);
bool fetchFromUser(Account *account);
void fetch(Account*);
void persist(Account*);
void invalidateToken(Account *) {}
};
} // ns Mirall

View File

@@ -1,338 +0,0 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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 <QtGui>
#include <QInputDialog>
#include "config.h"
#include "creds/http/credentialstore.h"
#include "creds/http/httpconfigfile.h"
#include "mirall/theme.h"
#ifdef WITH_QTKEYCHAIN
#include <qtkeychain/keychain.h>
using namespace QKeychain;
#endif
#define MAX_LOGIN_ATTEMPTS 3
namespace Mirall {
CredentialStore *CredentialStore::_instance=0;
CredentialStore::CredState CredentialStore::_state = NotFetched;
QString CredentialStore::_passwd = QString::null;
QString CredentialStore::_user = QString::null;
QString CredentialStore::_url = QString::null;
QString CredentialStore::_errorMsg = QString::null;
#ifdef WITH_QTKEYCHAIN
CredentialStore::CredentialType CredentialStore::_type = KeyChain;
#else
CredentialStore::CredentialType CredentialStore::_type = Settings;
#endif
CredentialStore::CredentialStore(QObject *parent) :
QObject(parent)
{
}
CredentialStore *CredentialStore::instance()
{
if( !CredentialStore::_instance ) CredentialStore::_instance = new CredentialStore;
return CredentialStore::_instance;
}
QString CredentialStore::password() const
{
return _passwd;
}
QString CredentialStore::user() const
{
return _user;
}
CredentialStore::CredState CredentialStore::state()
{
return _state;
}
void CredentialStore::fetchCredentials()
{
HttpConfigFile cfgFile;
bool ok = false;
QString pwd;
_user = cfgFile.user();
_url = cfgFile.ownCloudUrl();
QString key = keyChainKey(_url);
if( key.isNull() ) {
qDebug() << "Can not fetch credentials, url is zero!";
_state = Error;
emit( fetchCredentialsFinished(false) );
return;
}
switch( _type ) {
case CredentialStore::Settings: {
/* Read from config file. */
_state = Fetching;
cfgFile.fixupOldPassword();
if( cfgFile.passwordExists() ) {
pwd = cfgFile.password();
ok = true;
} else {
ok = false;
_state = EntryNotFound;
}
break;
}
case CredentialStore::KeyChain: {
// If the credentials are here already, return.
if( _state == Ok || _state == AsyncWriting ) {
emit(fetchCredentialsFinished(true));
return;
}
// otherwise fetch asynchronious.
#ifdef WITH_QTKEYCHAIN
_state = AsyncFetching;
if( !_user.isEmpty() ) {
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
job->setKey( key );
connect( job, SIGNAL(finished(QKeychain::Job*)), this,
SLOT(slotKeyChainReadFinished(QKeychain::Job*)));
job->start();
}
#else
qDebug() << "QtKeyChain: Not yet implemented!";
_state = Error;
#endif
break;
}
default: {
break;
}
}
if( _state == Fetching ) { // ...but not AsyncFetching
if( ok ) {
_passwd = pwd;
_state = Ok;
}
if( !ok && _state == Fetching ) {
_state = Error;
}
emit( fetchCredentialsFinished(ok) );
} else {
// in case of AsyncFetching nothing happens here. The finished-Slot
// will emit the finish signal.
}
}
void CredentialStore::reset()
{
_state = NotFetched;
_user = QString::null;
_passwd = QString::null;
}
QString CredentialStore::keyChainKey( const QString& url ) const
{
QString u(url);
if( u.isEmpty() ) {
qDebug() << "Empty url in keyChain, error!";
return QString::null;
}
if( _user.isEmpty() ) {
qDebug() << "Error: User is emty!";
return QString::null;
}
if( !u.endsWith(QChar('/')) ) {
u.append(QChar('/'));
}
QString key = _user+QLatin1Char(':')+u;
return key;
}
void CredentialStore::slotKeyChainReadFinished(QKeychain::Job* job)
{
#ifdef WITH_QTKEYCHAIN
ReadPasswordJob *pwdJob = static_cast<ReadPasswordJob*>(job);
if( pwdJob ) {
switch( pwdJob->error() ) {
case QKeychain::NoError:
_passwd = pwdJob->textData();
#ifdef Q_OS_LINUX
// Currently there is a bug in the keychain on linux that if no
// entry is there, an empty password comes back, but no error.
if( _passwd.isEmpty() ) {
_state = EntryNotFound;
_errorMsg = tr("No password entry found in keychain. Please reconfigure.");
} else
#endif
_state = Ok;
break;
case QKeychain::EntryNotFound:
_state = EntryNotFound;
break;
case QKeychain::CouldNotDeleteEntry:
_state = Error;
break;
case QKeychain::AccessDenied:
_state = AccessDenied;
break;
case QKeychain::NoBackendAvailable:
_state = NoKeychainBackend;
break;
case QKeychain::NotImplemented:
_state = NoKeychainBackend;
break;
case QKeychain::OtherError:
default:
_state = Error;
}
/* In case there is no backend, tranparentely switch to Settings file. */
if( _state == NoKeychainBackend ) {
qDebug() << "No Storage Backend, falling back to Settings mode.";
_type = CredentialStore::Settings;
fetchCredentials();
return;
}
if( _state == EntryNotFound ) {
// try to migrate.
}
if( _state != Ok ) {
qDebug() << "Error with keychain: " << pwdJob->errorString();
if(_errorMsg.isEmpty()) _errorMsg = pwdJob->errorString();
} else {
_errorMsg = QString::null;
}
} else {
_state = Error;
qDebug() << "Error: KeyChain Read Password Job failed!";
}
emit(fetchCredentialsFinished(_state == Ok));
#else
(void) job;
#endif
}
QString CredentialStore::errorMessage()
{
return _errorMsg;
}
void CredentialStore::setCredentials( const QString& url, const QString& user,
const QString& pwd )
{
_passwd = pwd;
_user = user;
#ifdef WITH_QTKEYCHAIN
_type = KeyChain;
#else
_type = Settings;
#endif
_url = url;
_state = Ok;
}
void CredentialStore::saveCredentials( )
{
HttpConfigFile cfgFile;
QString key = keyChainKey(_url);
if( key.isNull() ) {
qDebug() << "Error: Can not save credentials, URL is zero!";
return;
}
#ifdef WITH_QTKEYCHAIN
#endif
cfgFile.setUser(_user);
switch( _type ) {
case CredentialStore::KeyChain: {
#ifdef WITH_QTKEYCHAIN
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
// Set password in KeyChain
job->setKey( key );
job->setTextData(_passwd);
connect( job, SIGNAL(finished(QKeychain::Job*)), this,
SLOT(slotKeyChainWriteFinished(QKeychain::Job*)));
_state = AsyncWriting;
job->start();
#endif
}
break;
case CredentialStore::Settings:
cfgFile.setPassword( _passwd );
reset();
break;
default:
// unsupported.
break;
}
}
void CredentialStore::slotKeyChainWriteFinished( QKeychain::Job *job )
{
#ifdef WITH_QTKEYCHAIN
WritePasswordJob *pwdJob = static_cast<WritePasswordJob*>(job);
if( pwdJob ) {
QKeychain::Error err = pwdJob->error();
if( err != QKeychain::NoError ) {
qDebug() << "Error with keychain: " << pwdJob->errorString();
if( err == NoBackendAvailable || err == NotImplemented ||
pwdJob->errorString().contains(QLatin1String("Could not open wallet"))) {
_state = NoKeychainBackend;
_type = Settings;
saveCredentials();
} else {
_state = Error;
}
} else {
qDebug() << "Successfully stored password for user " << _user;
// Try to remove password formerly stored in the config file.
HttpConfigFile cfgFile;
cfgFile.removePassword();
_state = NotFetched;
}
} else {
qDebug() << "Error: KeyChain Write Password Job failed!";
_state = Error;
}
#else
(void) job;
#endif
}
// Called if a user chooses to not store the password locally.
void CredentialStore::deleteKeyChainCredential( const QString& key )
{
#ifdef WITH_QTKEYCHAIN
// Start the remove job, do not care so much about the result.
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setKey( key );
job->start();
#endif
}
}

View File

@@ -1,132 +0,0 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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.
*/
#ifndef CREDENTIALSTORE_H
#define CREDENTIALSTORE_H
#include <QObject>
#include <QInputDialog>
namespace QKeychain {
class Job;
}
namespace Mirall {
/*
* This object holds the credential information of the ownCloud connection. It
* is implemented as a singleton.
* At startup of the client, at first the fetchCredentials() method must be called
* which tries to get credentials from one of the supported backends. To determine
* which backend should be used, MirallConfigFile::credentialType() is called as
* the backend is configured in the config file.
*
* The fetchCredentials() call changes the internal state of the credential store
* to one of
* Ok: There are credentials. Note that it's unknown if they are correct!!
* Fetching: The fetching is not yet finished.
* EntryNotFound: No password entry found in the storage.
* Error: A general error happened.
* After fetching has finished, signal fetchCredentialsFinished(bool) is emitted.
* The result can be retrieved with state() and password() and user() methods.
*/
class CredentialStore : public QObject
{
Q_OBJECT
public:
enum CredState { NotFetched = 0,
Ok,
Fetching,
AsyncFetching,
EntryNotFound,
AccessDenied,
NoKeychainBackend,
Error,
AsyncWriting };
enum CredentialType {
Settings = 0,
KeyChain
};
QString password( ) const;
QString user( ) const;
/**
* @brief state
* @return the state of the Credentialstore.
*/
CredState state();
/**
* @brief fetchCredentials - start to retrieve user credentials.
*
* This method must be called first to retrieve the credentials.
* At the end, this method emits the fetchKeyChainFinished() signal.
*/
void fetchCredentials();
/**
* @brief instance - singleton pointer.
* @return the singleton pointer to access the object.
*/
static CredentialStore *instance();
/**
* @brief setCredentials - sets the user credentials.
*
* This function is called from the setup wizard to set the credentials
* int this store. Note that it does not store the password.
* The function also sets the state to ok.
* @param url - the connection url
* @param user - the user name
*/
void setCredentials( const QString& url, const QString& user, const QString& pwd);
void saveCredentials( );
QString errorMessage();
void reset();
signals:
/**
* @brief fetchCredentialsFinished
*
* emitted as soon as the fetching of the credentials has finished.
* If the parameter is true, there is a password and user. This does
* however, not say if the credentials are valid log in data.
* If false, the user pressed cancel.
*/
void fetchCredentialsFinished(bool);
protected slots:
void slotKeyChainReadFinished( QKeychain::Job* );
void slotKeyChainWriteFinished( QKeychain::Job* );
private:
explicit CredentialStore(QObject *parent = 0);
void deleteKeyChainCredential( const QString& );
QString keyChainKey( const QString& ) const;
static CredentialStore *_instance;
static CredState _state;
static QString _passwd;
static QString _user;
static QString _url;
static QString _errorMsg;
static CredentialType _type;
};
}
#endif // CREDENTIALSTORE_H

View File

@@ -1,5 +1,4 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@kde.org>
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*
@@ -15,14 +14,23 @@
*/
#include <QMutex>
#include <QDebug>
#include <QNetworkReply>
#include <QSettings>
#include <QInputDialog>
#include "creds/httpcredentials.h"
#include "mirall/owncloudinfo.h"
#include "mirall/mirallconfigfile.h"
#include <qtkeychain/keychain.h>
#include "mirall/account.h"
#include "mirall/mirallaccessmanager.h"
#include "mirall/utility.h"
#include "creds/http/credentialstore.h"
#include "mirall/theme.h"
#include "creds/credentialscommon.h"
#include "creds/httpcredentials.h"
using namespace QKeychain;
Q_DECLARE_METATYPE(Mirall::Account*)
namespace Mirall
{
@@ -39,8 +47,8 @@ int getauth(const char *prompt,
{
int re = 0;
QMutex mutex;
MirallConfigFile cfg;
HttpCredentials* http_credentials = dynamic_cast< HttpCredentials* > (cfg.getCredentials());
// ### safe?
HttpCredentials* http_credentials = qobject_cast<HttpCredentials*>(AccountManager::instance()->account()->credentials());
if (!http_credentials) {
qDebug() << "Not a HTTP creds instance!";
@@ -65,20 +73,38 @@ int getauth(const char *prompt,
return re;
}
const char userC[] = "user";
} // ns
class HttpCredentialsAccessManager : public MirallAccessManager {
public:
HttpCredentialsAccessManager(const HttpCredentials *cred, QObject* parent = 0)
: MirallAccessManager(parent), _cred(cred) {}
protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) {
QByteArray credHash = QByteArray(_cred->user().toUtf8()+":"+_cred->password().toUtf8()).toBase64();
QNetworkRequest req(request);
req.setRawHeader(QByteArray("Authorization"), QByteArray("Basic ") + credHash);
//qDebug() << "Request for " << req.url() << "with authorization" << QByteArray::fromBase64(credHash);
return MirallAccessManager::createRequest(op, req, outgoingData);
}
private:
const HttpCredentials *_cred;
};
HttpCredentials::HttpCredentials()
: _user(),
_password(),
_ready(false),
_attempts()
_ready(false)
{}
HttpCredentials::HttpCredentials(const QString& user, const QString& password)
: _user(user),
_password(password),
_ready(true)
{}
{
}
void HttpCredentials::syncContextPreInit (CSYNC* ctx)
{
@@ -91,7 +117,7 @@ void HttpCredentials::syncContextPreStart (CSYNC* ctx)
// any way to get "session_key" module property from csync. Had we
// have it, then we could remove this code and keep it in
// csyncthread code (or folder code, git remembers).
QList<QNetworkCookie> cookies(ownCloudInfo::instance()->getLastAuthCookies());
QList<QNetworkCookie> cookies(AccountManager::instance()->account()->lastAuthCookies());
QString cookiesAsString;
// Stuff cookies inside csync, then we can avoid the intermediate HTTP 401 reply
@@ -134,7 +160,7 @@ QString HttpCredentials::password() const
QNetworkAccessManager* HttpCredentials::getQNAM() const
{
MirallAccessManager* qnam = new MirallAccessManager;
MirallAccessManager* qnam = new HttpCredentialsAccessManager(this);
connect( qnam, SIGNAL(authenticationRequired(QNetworkReply*, QAuthenticator*)),
this, SLOT(slotAuthentication(QNetworkReply*,QAuthenticator*)));
@@ -147,72 +173,142 @@ bool HttpCredentials::ready() const
return _ready;
}
void HttpCredentials::fetch()
void HttpCredentials::fetch(Account *account)
{
_user = account->credentialSetting(QLatin1String(userC)).toString();
if (_ready) {
Q_EMIT fetched();
} else {
// TODO: merge CredentialStore into HttpCredentials?
CredentialStore* store(CredentialStore::instance());
connect(store, SIGNAL(fetchCredentialsFinished(bool)),
this, SLOT(slotCredentialsFetched(bool)));
store->fetchCredentials();
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
job->setSettings(account->settingsWithGroup(Theme::instance()->appName()));
job->setInsecureFallback(true);
job->setKey(keychainKey(account->url().toString(), _user));
connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotReadJobDone(QKeychain::Job*)));
job->setProperty("account", QVariant::fromValue(account));
job->start();
}
}
bool HttpCredentials::stillValid(QNetworkReply *reply)
{
return ((reply->error() != QNetworkReply::AuthenticationRequiredError)
// returned if user or password is incorrect
&& (reply->error() != QNetworkReply::OperationCanceledError));
}
bool HttpCredentials::fetchFromUser(Account *account)
{
bool ok = false;
QString password = queryPassword(&ok);
if (ok) {
_password = password;
_ready = true;
persist(account);
}
return ok;
}
void HttpCredentials::slotReadJobDone(QKeychain::Job *job)
{
ReadPasswordJob *readJob = static_cast<ReadPasswordJob*>(job);
delete readJob->settings();
_password = readJob->textData();
QKeychain::Error error = job->error();
switch (error) {
case NoError:
_ready = true;
Q_EMIT fetched();
break;
default:
if (!_user.isEmpty()) {
bool ok;
QString pwd = queryPassword(&ok);
if (ok) {
_password = pwd;
_ready = true;
persist(qvariant_cast<Account*>(readJob->property("account")));
Q_EMIT fetched();
break;
}
}
qDebug() << "Error while reading password" << job->errorString();
}
}
void HttpCredentials::persistForUrl(const QString& url)
QString HttpCredentials::queryPassword(bool *ok)
{
CredentialStore* store(CredentialStore::instance());
store->setCredentials(url, _user, _password);
store->saveCredentials();
qDebug() << AccountManager::instance()->account()->state();
if (ok) {
QString str = QInputDialog::getText(0, tr("Enter Password"),
tr("Please enter %1 password for user '%2':")
.arg(Theme::instance()->appNameGUI(), _user),
QLineEdit::Password, QString(), ok);
qDebug() << AccountManager::instance()->account()->state();
return str;
} else {
return QString();
}
}
void HttpCredentials::slotCredentialsFetched(bool ok)
void HttpCredentials::invalidateToken(Account *account)
{
_ready = ok;
if (_ready) {
CredentialStore* store(CredentialStore::instance());
_user = store->user();
_password = store->password();
_password = QString();
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setKey(keychainKey(account->url().toString(), _user));
job->start();
_ready = false;
}
void HttpCredentials::persist(Account *account)
{
account->setCredentialSetting(QLatin1String(userC), _user);
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
job->setSettings(account->settingsWithGroup(Theme::instance()->appName()));
job->setInsecureFallback(true);
connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteJobDone(QKeychain::Job*)));
job->setKey(keychainKey(account->url().toString(), _user));
job->setTextData(_password);
job->start();
}
void HttpCredentials::slotWriteJobDone(QKeychain::Job *job)
{
delete job->settings();
switch (job->error()) {
case NoError:
break;
default:
qDebug() << "Error while writing password" << job->errorString();
}
Q_EMIT fetched();
}
void HttpCredentials::slotAuthentication(QNetworkReply* reply, QAuthenticator* authenticator)
{
if( !(authenticator && reply) ) return;
qDebug() << "Authenticating request for " << reply->url();
if (_attempts.contains(reply)) {
++_attempts[reply];
} else {
connect(reply, SIGNAL(finished()),
this, SLOT(slotReplyFinished()));
_attempts[reply] = 1;
}
// TODO: Replace it with something meaningful...
//if( reply->url().toString().startsWith( webdavUrl( _connection ) ) ) {
if (_attempts[reply] > 1) {
qDebug() << "Too many attempts to authenticate. Stop request.";
reply->close();
} else {
authenticator->setUser( _user );
authenticator->setPassword( _password );
}
//} else {
// qDebug() << "WRN: attempt to authenticate to different url - closing.";
// reply->close();
//}
Q_UNUSED(authenticator)
// we cannot use QAuthenticator, because it sends username and passwords with latin1
// instead of utf8 encoding. Instead, we send it manually. Thus, if we reach this signal,
// those credentials were invalid and we terminate.
qDebug() << "Stop request: Authentication failed for " << reply->url().toString();
reply->close();
}
void HttpCredentials::slotReplyFinished()
QString HttpCredentials::keychainKey(const QString &url, const QString &user)
{
QNetworkReply* reply = qobject_cast< QNetworkReply* >(sender());
QString u(url);
if( u.isEmpty() ) {
qDebug() << "Empty url in keyChain, error!";
return QString::null;
}
if( user.isEmpty() ) {
qDebug() << "Error: User is emty!";
return QString::null;
}
disconnect(reply, SIGNAL(finished()),
this, SLOT(slotReplyFinished()));
_attempts.remove (reply);
if( !u.endsWith(QChar('/')) ) {
u.append(QChar('/'));
}
QString key = user+QLatin1Char(':')+u;
return key;
}
} // ns Mirall

View File

@@ -1,5 +1,4 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
* Copyright (C) by Klaas Freitag <freitag@kde.org>
* Copyright (C) by Krzesimir Nowak <krzesimir@endocode.com>
*
@@ -24,39 +23,46 @@
class QNetworkReply;
class QAuthenticator;
namespace QKeychain {
class Job;
}
namespace Mirall
{
class HttpCredentials : public AbstractCredentials
{
Q_OBJECT
Q_OBJECT
public:
HttpCredentials();
HttpCredentials(const QString& user, const QString& password);
HttpCredentials();
HttpCredentials(const QString& user, const QString& password);
void syncContextPreInit(CSYNC* ctx);
void syncContextPreStart(CSYNC* ctx);
bool changed(AbstractCredentials* credentials) const;
QString authType() const;
QNetworkAccessManager* getQNAM() const;
bool ready() const;
void fetch();
void persistForUrl(const QString& url);
QString user() const;
QString password() const;
void syncContextPreInit(CSYNC* ctx);
void syncContextPreStart(CSYNC* ctx);
bool changed(AbstractCredentials* credentials) const;
QString authType() const;
QNetworkAccessManager* getQNAM() const;
bool ready() const;
void fetch(Account *account);
bool stillValid(QNetworkReply *reply);
bool fetchFromUser(Account *account);
void persist(Account *account);
QString user() const;
QString password() const;
QString queryPassword(bool *ok);
void invalidateToken(Account *account);
private Q_SLOTS:
void slotCredentialsFetched(bool);
void slotAuthentication(QNetworkReply*, QAuthenticator*);
void slotReplyFinished();
void slotAuthentication(QNetworkReply*, QAuthenticator*);
void slotReadJobDone(QKeychain::Job*);
void slotWriteJobDone(QKeychain::Job*);
private:
QString _user;
QString _password;
bool _ready;
QMap<QNetworkReply*, int> _attempts;
static QString keychainKey(const QString &url, const QString &user);
QString _user;
QString _password;
bool _ready;
};
} // ns Mirall

View File

@@ -13,6 +13,7 @@
#include <QDebug>
#include <QNetworkRequest>
#include <QNetworkCookieJar>
#include "creds/shibboleth/shibbolethaccessmanager.h"

View File

@@ -13,14 +13,16 @@
#include <QEventLoop>
#include "mirall/account.h"
#include "creds/shibboleth/shibbolethrefresher.h"
#include "creds/shibbolethcredentials.h"
namespace Mirall
{
ShibbolethRefresher::ShibbolethRefresher(ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent)
ShibbolethRefresher::ShibbolethRefresher(Account *account, ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent)
: QObject(parent),
_account(account),
_creds(creds),
_csync_ctx(csync_ctx)
{}
@@ -33,7 +35,8 @@ void ShibbolethRefresher::refresh()
this, SLOT(onInvalidatedAndFetched(QByteArray)));
connect(_creds, SIGNAL(invalidatedAndFetched(QByteArray)),
&loop, SLOT(quit()));
QMetaObject::invokeMethod(_creds, "invalidateAndFetch", Qt::QueuedConnection);
QMetaObject::invokeMethod(_creds, "invalidateAndFetch",Qt::QueuedConnection,
Q_ARG(Account*, _account));
loop.exec();
disconnect(_creds, SIGNAL(invalidatedAndFetched(QByteArray)),
&loop, SLOT(quit()));

View File

@@ -23,6 +23,7 @@ class QByteArray;
namespace Mirall
{
class Account;
class ShibbolethCredentials;
class ShibbolethRefresher : public QObject
@@ -30,7 +31,7 @@ class ShibbolethRefresher : public QObject
Q_OBJECT
public:
ShibbolethRefresher(ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent = 0);
ShibbolethRefresher(Account *account, ShibbolethCredentials* creds, CSYNC* csync_ctx, QObject* parent = 0);
void refresh();
@@ -38,6 +39,7 @@ private Q_SLOTS:
void onInvalidatedAndFetched(const QByteArray& cookieData);
private:
Account* _account;
ShibbolethCredentials* _creds;
CSYNC* _csync_ctx;
};

View File

@@ -16,17 +16,25 @@
#include <QNetworkCookie>
#include <QWebFrame>
#include <QWebPage>
#include <QMessageBox>
#include "creds/shibboleth/shibbolethcookiejar.h"
#include "creds/shibboleth/shibbolethwebview.h"
#include "mirall/account.h"
#include "mirall/mirallaccessmanager.h"
#include "mirall/theme.h"
namespace Mirall
{
void ShibbolethWebView::setup(const QUrl& url, ShibbolethCookieJar* jar)
void ShibbolethWebView::setup(Account *account, ShibbolethCookieJar* jar)
{
MirallAccessManager* nm = new MirallAccessManager(this);
// we need our own QNAM, but the we offload the SSL error handling to
// the account object, which already can do this
connect(nm, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
account, SLOT(slotHandleErrors(QNetworkReply*,QList<QSslError>)));
QWebPage* page = new QWebPage(this);
jar->setParent(this);
@@ -35,18 +43,19 @@ void ShibbolethWebView::setup(const QUrl& url, ShibbolethCookieJar* jar)
connect(page, SIGNAL(loadStarted()),
this, SLOT(slotLoadStarted()));
connect(page, SIGNAL(loadFinished(bool)),
this, SLOT(slotLoadFinished()));
this, SLOT(slotLoadFinished(bool)));
nm->setCookieJar(jar);
page->setNetworkAccessManager(nm);
page->mainFrame ()->load (url);
this->setPage (page);
page->mainFrame()->load(account->url());
this->setPage(page);
setWindowTitle(tr("%1 - Authenticate").arg(Theme::instance()->appNameGUI()));
}
ShibbolethWebView::ShibbolethWebView(const QUrl& url, QWidget* parent)
ShibbolethWebView::ShibbolethWebView(Account* account, QWidget* parent)
: QWebView(parent)
{
setup(url, new ShibbolethCookieJar(this));
setup(account, new ShibbolethCookieJar(this));
}
ShibbolethWebView::~ShibbolethWebView()
@@ -54,10 +63,10 @@ ShibbolethWebView::~ShibbolethWebView()
slotLoadFinished();
}
ShibbolethWebView::ShibbolethWebView(const QUrl& url, ShibbolethCookieJar* jar, QWidget* parent)
ShibbolethWebView::ShibbolethWebView(Account* account, ShibbolethCookieJar* jar, QWidget* parent)
: QWebView(parent)
{
setup(url, jar);
setup(account, jar);
}
void ShibbolethWebView::onNewCookiesForUrl (const QList<QNetworkCookie>& cookieList, const QUrl& url)
@@ -96,9 +105,20 @@ void ShibbolethWebView::slotLoadStarted()
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
}
void ShibbolethWebView::slotLoadFinished()
void ShibbolethWebView::slotLoadFinished(bool success)
{
QApplication::restoreOverrideCursor();
if (!title().isNull()) {
setWindowTitle(tr("%1 - %2").arg(Theme::instance()->appNameGUI(), title()));
}
if (!success) {
QMessageBox::critical(this, tr("Error loading IdP login page"),
tr("Could not load Shibboleth login page to log you in.\n"
"Please ensure that your network connection is working."));
}
}
} // ns Mirall

View File

@@ -24,14 +24,15 @@ namespace Mirall
{
class ShibbolethCookieJar;
class Account;
class ShibbolethWebView : public QWebView
{
Q_OBJECT
public:
ShibbolethWebView(const QUrl& url, QWidget* parent = 0);
ShibbolethWebView(const QUrl& url, ShibbolethCookieJar* jar, QWidget* parent = 0);
ShibbolethWebView(Account *account, QWidget* parent = 0);
ShibbolethWebView(Account *account, ShibbolethCookieJar* jar, QWidget* parent = 0);
~ShibbolethWebView();
protected:
@@ -45,10 +46,10 @@ Q_SIGNALS:
private Q_SLOTS:
void onNewCookiesForUrl(const QList<QNetworkCookie>& cookieList, const QUrl& url);
void slotLoadStarted();
void slotLoadFinished();
void slotLoadFinished(bool success = true);
private:
void setup(const QUrl& url, ShibbolethCookieJar* jar);
void setup(Account *account, ShibbolethCookieJar* jar);
};
} // ns Mirall

View File

@@ -12,14 +12,16 @@
* for more details.
*/
#include <QDebug>
#include <QMutex>
#include "creds/shibbolethcredentials.h"
#include "creds/shibboleth/shibbolethaccessmanager.h"
#include "creds/shibboleth/shibbolethwebview.h"
#include "creds/shibboleth/shibbolethrefresher.h"
#include "creds/shibboleth/shibbolethconfigfile.h"
#include "creds/credentialscommon.h"
#include "mirall/owncloudinfo.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/account.h"
namespace Mirall
{
@@ -44,15 +46,16 @@ int shibboleth_redirect_callback(CSYNC* csync_ctx,
QMutex mutex;
QMutexLocker locker(&mutex);
MirallConfigFile cfg;
ShibbolethCredentials* creds = dynamic_cast< ShibbolethCredentials* > (cfg.getCredentials());
Account *account = AccountManager::instance()->account();
ShibbolethCredentials* creds = qobject_cast<ShibbolethCredentials*>(account->credentials());
if (!creds) {
qDebug() << "Not a Shibboleth creds instance!";
return 1;
}
ShibbolethRefresher refresher(creds, csync_ctx);
ShibbolethRefresher refresher(account, creds, csync_ctx);
// blocks
refresher.refresh();
@@ -63,7 +66,9 @@ int shibboleth_redirect_callback(CSYNC* csync_ctx,
} // ns
ShibbolethCredentials::ShibbolethCredentials()
: _shibCookie(),
: AbstractCredentials(),
_url(),
_shibCookie(),
_ready(false),
_browser(0),
_otherCookies()
@@ -88,7 +93,7 @@ QByteArray ShibbolethCredentials::prepareCookieData() const
// have any way to get "session_key" module property from
// csync. Had we have it, then we could just append shibboleth
// cookies to the "session_key" value and set it in csync module.
QList<QNetworkCookie> cookies(ownCloudInfo::instance()->getLastAuthCookies());
QList<QNetworkCookie> cookies(AccountManager::instance()->account()->lastAuthCookies());
QMap<QString, QString> uniqueCookies;
cookies << _shibCookie;
@@ -142,6 +147,14 @@ QString ShibbolethCredentials::authType() const
return QString::fromLatin1("shibboleth");
}
QString ShibbolethCredentials::user() const
{
// ### TODO: If we had a way to extract the currently authenticated user
// somehow, we could return its id token (email) here (stored in REMOTE_USER)
// The server doesn't return it by default
return QString();
}
QNetworkCookie ShibbolethCredentials::cookie() const
{
return _shibCookie;
@@ -161,14 +174,16 @@ bool ShibbolethCredentials::ready() const
return _ready;
}
void ShibbolethCredentials::fetch()
void ShibbolethCredentials::fetch(Account *account)
{
if (_ready) {
Q_EMIT fetched();
} else {
ShibbolethConfigFile cfg;
_browser = new ShibbolethWebView(QUrl(cfg.ownCloudUrl()), cfg.createCookieJar());
if (account) {
_url = account->url();
}
_browser = new ShibbolethWebView(account, cfg.createCookieJar());
connect(_browser, SIGNAL(shibbolethCookieReceived(QNetworkCookie)),
this, SLOT(onShibbolethCookieReceived(QNetworkCookie)));
connect(_browser, SIGNAL(viewHidden()),
@@ -177,13 +192,33 @@ void ShibbolethCredentials::fetch()
}
}
void ShibbolethCredentials::persistForUrl(const QString& /*url*/)
bool ShibbolethCredentials::stillValid(QNetworkReply *reply)
{
Q_UNUSED(reply)
return true;
}
bool ShibbolethCredentials::fetchFromUser(Account *account)
{
Q_UNUSED(account)
return false;
}
void ShibbolethCredentials::persist(Account* /*account*/)
{
ShibbolethConfigFile cfg;
cfg.storeCookies(_otherCookies);
}
void ShibbolethCredentials::invalidateToken(Account *account)
{
Q_UNUSED(account)
_shibCookie.setValue("");
// ### access to ctx missing, but might not be required at all
//csync_set_module_property(ctx, "session_key", "");
}
void ShibbolethCredentials::disposeBrowser()
{
disconnect(_browser, SIGNAL(viewHidden()),
@@ -212,12 +247,15 @@ void ShibbolethCredentials::slotBrowserHidden()
Q_EMIT fetched();
}
void ShibbolethCredentials::invalidateAndFetch()
void ShibbolethCredentials::invalidateAndFetch(Account* account)
{
_ready = false;
connect (this, SIGNAL(fetched()),
this, SLOT(onFetched()));
fetch();
// small hack to support the ShibbolethRefresher hack
// we already rand fetch() with a valid account object,
// and hence know the url on refresh
fetch(account);
}
void ShibbolethCredentials::onFetched()

View File

@@ -38,15 +38,19 @@ public:
void syncContextPreStart(CSYNC* ctx);
bool changed(AbstractCredentials* credentials) const;
QString authType() const;
QString user() const;
QNetworkAccessManager* getQNAM() const;
bool ready() const;
void fetch();
void persistForUrl(const QString& url);
void fetch(Account *account);
bool stillValid(QNetworkReply *reply);
virtual bool fetchFromUser(Account *account);
void persist(Account *account);
void invalidateToken(Account *account);
QNetworkCookie cookie() const;
public Q_SLOTS:
void invalidateAndFetch();
void invalidateAndFetch(Account *account);
private Q_SLOTS:
void onShibbolethCookieReceived(const QNetworkCookie& cookie);
@@ -58,6 +62,7 @@ Q_SIGNALS:
void invalidatedAndFetched(const QByteArray& cookieData);
private:
QUrl _url;
QByteArray prepareCookieData() const;
void disposeBrowser();

View File

@@ -11,11 +11,24 @@
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <signal.h>
#include "mirall/application.h"
#include "mirall/theme.h"
#include "mirall/utility.h"
#include <QMessageBox>
#include <QTimer>
void warnSystray()
{
QMessageBox::critical(0, qApp->translate("main.cpp", "System Tray not available"),
qApp->translate("main.cpp", "%1 requires on a working system tray. "
"If you are running XFCE, please follow "
"<a href=\"http://docs.xfce.org/xfce/xfce4-panel/systray\">these instructions</a>. "
"Otherwise, please install a system tray application such as 'trayer' and try again.")
.arg(Mirall::Theme::instance()->appNameGUI()));
}
int main(int argc, char **argv)
{
@@ -23,7 +36,9 @@ int main(int argc, char **argv)
Mirall::Application app(argc, argv);
app.initialize();
#ifndef Q_OS_WIN32
signal(SIGPIPE, SIG_IGN);
#endif
if( app.giveHelp() ) {
app.showHelp();
return 0;
@@ -39,13 +54,17 @@ int main(int argc, char **argv)
}
return 0;
} else {
if (!QSystemTrayIcon::isSystemTrayAvailable() && qgetenv("DESKTOP_SESSION") != "ubuntu") {
QMessageBox::critical(0, qApp->translate("main.cpp", "System Tray not available"),
qApp->translate("main.cpp", "%1 requires on a working system tray. "
"If you are running XFCE, please follow "
"<a href=\"http://docs.xfce.org/xfce/xfce4-panel/systray\">these instructions</a>. "
"Otherwise, please install a system tray application such as 'trayer' and try again.")
.arg(Mirall::Theme::instance()->appNameGUI()));
int attempts = 0;
forever {
if (!QSystemTrayIcon::isSystemTrayAvailable() && qgetenv("DESKTOP_SESSION") != "ubuntu") {
Mirall::Utility::sleep(1);
attempts++;
if (attempts < 30) continue;
} else {
break;
}
warnSystray();
break;
}
}
return app.exec();

326
src/mirall/account.cpp Normal file
View File

@@ -0,0 +1,326 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@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; 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 "mirall/account.h"
#include "mirall/theme.h"
#include "mirall/mirallconfigfile.h"
#include "creds/abstractcredentials.h"
#include "creds/credentialsfactory.h"
#include <QSettings>
#include <QMutex>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QSslSocket>
#include <QNetworkCookieJar>
#include <QDebug>
namespace Mirall {
static const char urlC[] = "url";
static const char authTypeC[] = "authType";
static const char userC[] = "user";
static const char httpUserC[] = "http_user";
AccountManager *AccountManager::_instance = 0;
AccountManager *AccountManager::instance()
{
static QMutex mutex;
if (!_instance)
{
QMutexLocker lock(&mutex);
if (!_instance) {
_instance = new AccountManager;
}
}
return _instance;
}
void AccountManager::setAccount(Account *account)
{
emit accountAboutToChange(account, _account);
std::swap(_account, account);
emit accountChanged(_account, account);
}
Account::Account(AbstractSslErrorHandler *sslErrorHandler, QObject *parent)
: QObject(parent)
, _sslErrorHandler(sslErrorHandler)
, _am(0)
, _credentials(0)
, _treatSslErrorsAsFailure(false)
, _state(Account::Disconnected)
{
}
Account::~Account()
{
}
void Account::save()
{
QScopedPointer<QSettings> settings(settingsWithGroup(Theme::instance()->appName()));
settings->setValue(QLatin1String(urlC), _url.toString());
if (_credentials) {
_credentials->persist(this);
Q_FOREACH(QString key, _settingsMap.keys()) {
settings->setValue(key, _settingsMap.value(key));
}
settings->setValue(QLatin1String(authTypeC), _credentials->authType());
// HACK: Save http_user also as user
if (_settingsMap.contains(httpUserC))
settings->setValue(userC, _settingsMap.value(httpUserC));
}
settings->sync();
// ### TODO port away from MirallConfigFile
MirallConfigFile cfg;
qDebug() << "Saving " << approvedCerts().count() << " unknown certs.";
QByteArray certs;
Q_FOREACH( const QSslCertificate& cert, approvedCerts() ) {
certs += cert.toPem() + '\n';
}
if (!certs.isEmpty()) {
cfg.setCaCerts( certs );
}
}
Account* Account::restore()
{
QScopedPointer<QSettings> settings(settingsWithGroup(Theme::instance()->appName()));
if (!settings->childKeys().isEmpty()) {
Account *acc = new Account;
MirallConfigFile cfg;
acc->setApprovedCerts(QSslCertificate::fromData(cfg.caCerts()));
acc->setUrl(settings->value(QLatin1String(urlC)).toUrl());
acc->setCredentials(CredentialsFactory::create(settings->value(QLatin1String(authTypeC)).toString()));
// We want to only restore settings for that auth type and the user value
acc->_settingsMap.insert(QLatin1String(userC), settings->value(userC));
QString authTypePrefix = settings->value(authTypeC).toString() + "_";
Q_FOREACH(QString key, settings->childKeys()) {
if (!key.startsWith(authTypePrefix))
continue;
acc->_settingsMap.insert(key, settings->value(key));
}
return acc;
}
return 0;
}
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(Account *other, bool ignoreUrlProtocol) const
{
if (!other) {
return false;
}
bool changes = false;
if (ignoreUrlProtocol) {
changes = isEqualExceptProtocol(_url, other->_url);
} else {
changes = (_url == other->_url);
}
if (!changes) {
changes = _credentials->changed(other->_credentials);
}
return changes;
}
AbstractCredentials *Account::credentials() const
{
return _credentials;
}
void Account::setCredentials(AbstractCredentials *cred)
{
_credentials = cred;
// set active credential manager
if (_am) {
_am->deleteLater();
}
_am = _credentials->getQNAM();
connect(_am, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)),
SLOT(slotHandleErrors(QNetworkReply*,QList<QSslError>)));
}
QUrl Account::davUrl() const
{
return concatUrlPath(url(), davPath());
}
QList<QNetworkCookie> Account::lastAuthCookies() const
{
return _am->cookieJar()->cookiesForUrl(_url);
}
QNetworkReply *Account::headRequest(const QString &relPath)
{
return headRequest(concatUrlPath(url(), relPath));
}
QNetworkReply *Account::headRequest(const QUrl &url)
{
QNetworkRequest request(url);
return _am->head(request);
}
QNetworkReply *Account::getRequest(const QString &relPath)
{
return getRequest(concatUrlPath(url(), relPath));
}
QNetworkReply *Account::getRequest(const QUrl &url)
{
QNetworkRequest request(url);
return _am->get(request);
}
QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data)
{
return davRequest(verb, concatUrlPath(davUrl(), relPath), req, data);
}
QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
{
req.setUrl(url);
return _am->sendCustomRequest(req, verb, data);
}
void Account::setCertificateChain(const QList<QSslCertificate> &certs)
{
_certificateChain = certs;
}
void Account::setApprovedCerts(const QList<QSslCertificate> certs)
{
_approvedCerts = certs;
}
void Account::addApprovedCerts(const QList<QSslCertificate> certs)
{
_approvedCerts += certs;
}
void Account::setSslErrorHandler(AbstractSslErrorHandler *handler)
{
_sslErrorHandler.reset(handler);
}
void Account::setUrl(const QUrl &url)
{
_url = url;
}
QUrl Account::concatUrlPath(const QUrl &url, const QString &concatPath)
{
QUrl tmpUrl = url;
QString path = tmpUrl.path();
// avoid '//'
if (path.endsWith('/') && concatPath.startsWith('/')) {
path.chop(1);
} // avoid missing '/'
else if (!path.endsWith('/') && !concatPath.startsWith('/')) {
path += QLatin1Char('/');
}
path += concatPath;
tmpUrl.setPath(path);
return tmpUrl;
}
QString Account::_configFileName;
QSettings *Account::settingsWithGroup(const QString& group)
{
if (_configFileName.isEmpty()) {
// cache file name
MirallConfigFile cfg;
_configFileName = cfg.configFile();
}
QSettings *settings = new QSettings(_configFileName, QSettings::IniFormat);
settings->beginGroup(group);
return settings;
}
QVariant Account::credentialSetting(const QString &key) const
{
if (_credentials) {
QString prefix = _credentials->authType();
QString value = _settingsMap.value(prefix+"_"+key).toString();
if (value.isEmpty()) {
value = _settingsMap.value(key).toString();
}
return value;
}
return QVariant();
}
void Account::setCredentialSetting(const QString &key, const QVariant &value)
{
if (_credentials) {
QString prefix = _credentials->authType();
_settingsMap.insert(prefix+"_"+key, value);
}
}
int Account::state() const
{
return _state;
}
void Account::setState(int state)
{
if (_state != state) {
_state = state;
emit stateChanged(state);
}
}
void Account::slotHandleErrors(QNetworkReply *reply , QList<QSslError> errors)
{
qDebug() << "SSL-Warnings happened for url " << reply->url().toString();
if( _treatSslErrorsAsFailure ) {
// User decided once not to trust. Honor this decision.
qDebug() << "Certs not trusted by user decision, returning.";
return;
}
QList<QSslCertificate> approvedCerts;
if (_sslErrorHandler.isNull() ) {
qDebug() << Q_FUNC_INFO << "called without valid SSL error handler for account" << url();
} else {
if (_sslErrorHandler->handleErrors(errors, &approvedCerts, this)) {
QSslSocket::addDefaultCaCertificates(approvedCerts);
addApprovedCerts(approvedCerts);
// all ssl certs are known and accepted. We can ignore the problems right away.
qDebug() << "Certs are already known and trusted, Warnings are not valid.";
reply->ignoreSslErrors();
} else {
_treatSslErrorsAsFailure = true;
return;
}
}
}
} // namespace Mirall

164
src/mirall/account.h Normal file
View File

@@ -0,0 +1,164 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@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; 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.
*/
#ifndef SERVERCONNECTION_H
#define SERVERCONNECTION_H
#include <QByteArray>
#include <QUrl>
#include <QNetworkCookie>
#include <QNetworkRequest>
#include <QSslCertificate>
#include <QSslError>
class QSettings;
class QNetworkReply;
class QUrl;
class QNetworkAccessManager;
namespace Mirall {
class AbstractCredentials;
class Account;
class AccountManager : public QObject {
Q_OBJECT
public:
static AccountManager *instance();
~AccountManager() {}
void setAccount(Account *account);
Account *account() { return _account; }
Q_SIGNALS:
void accountChanged(Account *newAccount, Account *oldAccount);
void accountAboutToChange(Account *newAccount, Account *oldAccount);
private:
AccountManager() : _account(0) {}
Account *_account;
static AccountManager *_instance;
};
/* Reimplement this to handle SSL errors */
class AbstractSslErrorHandler {
public:
virtual ~AbstractSslErrorHandler() {}
virtual bool handleErrors(QList<QSslError>, QList<QSslCertificate>*, Account*) = 0;
};
/**
* @brief This class represents an account on an ownCloud Server
*/
class Account : public QObject {
Q_OBJECT
public:
enum State { Disconnected = 0, /// no network connection
Connected, /// account is online
SignedOut /// Disconnected + credential token has been discarded
};
static QString davPath() { return "remote.php/webdav/"; }
Account(AbstractSslErrorHandler *sslErrorHandler = 0, QObject *parent = 0);
~Account();
/**
* Saves the account to a given settings file
*/
void save();
/**
* Creates an account object from from a given settings file.
*/
static Account* restore();
/**
* @brief Creates a minimal account object
*
* This will set up a ssl error handler
*
* @return A new Account object
*/
static Account* create(const QUrl &url);
/**
* @brief Checks the Account instance is different from \param other
*
* @returns true, if credentials or url have changed, false otherwise
*/
bool changed(Account *other, bool ignoreUrlProtocol) const;
/** Holds the accounts credentials */
AbstractCredentials* credentials() const;
void setCredentials(AbstractCredentials *cred);
/** Server url of the account */
void setUrl(const QUrl &url);
QUrl url() const { return _url; }
/** Returns webdav entry URL, based on url() */
QUrl davUrl() const;
QList<QNetworkCookie> lastAuthCookies() const;
QNetworkReply* headRequest(const QString &relPath);
QNetworkReply* headRequest(const QUrl &url);
QNetworkReply* getRequest(const QString &relPath);
QNetworkReply* getRequest(const QUrl &url);
QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0);
QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0);
/** The certificates of the account */
QList<QSslCertificate> certificateChain() const { return _certificateChain; }
void setCertificateChain(const QList<QSslCertificate> &certs);
/** The certificates of the account */
QList<QSslCertificate> approvedCerts() const { return _approvedCerts; }
void setApprovedCerts(const QList<QSslCertificate> certs);
void addApprovedCerts(const QList<QSslCertificate> certs);
// pluggable handler
void setSslErrorHandler(AbstractSslErrorHandler *handler);
// static helper function
static QUrl concatUrlPath(const QUrl &url, const QString &concatPath);
static QSettings* settingsWithGroup(const QString &group);
// to be called by credentials only
QVariant credentialSetting(const QString& key) const;
void setCredentialSetting(const QString& key, const QVariant &value);
int state() const;
void setState(int state);
signals:
void stateChanged(int state);
protected Q_SLOTS:
void slotHandleErrors(QNetworkReply*,QList<QSslError>);
private:
QMap<QString, QVariant> _settingsMap;
QUrl _url;
QList<QSslCertificate> _approvedCerts;
QList<QSslCertificate> _certificateChain;
QScopedPointer<AbstractSslErrorHandler> _sslErrorHandler;
QNetworkAccessManager *_am;
AbstractCredentials* _credentials;
bool _treatSslErrorsAsFailure;
int _state;
static QString _configFileName;
};
}
#endif //SERVERCONNECTION_H

View File

@@ -17,7 +17,6 @@
#include "mirall/theme.h"
#include "mirall/folderman.h"
#include "mirall/owncloudinfo.h"
#include "mirall/folderwizard.h"
#include "mirall/folderstatusmodel.h"
#include "mirall/utility.h"
@@ -25,7 +24,8 @@
#include "mirall/owncloudsetupwizard.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/ignorelisteditor.h"
#include "mirall/itemprogressdialog.h"
#include "mirall/account.h"
#include "creds/abstractcredentials.h"
#include <math.h>
@@ -35,6 +35,10 @@
#include <QMessageBox>
#include <QAction>
#include <QKeySequence>
#include <QIcon>
#include <QVariant>
#include "mirall/account.h"
namespace Mirall {
@@ -51,7 +55,8 @@ static const char progressBarStyleC[] =
AccountSettings::AccountSettings(QWidget *parent) :
QWidget(parent),
ui(new Ui::AccountSettings),
_item(0)
_wasDisabledBefore(false),
_account(AccountManager::instance()->account())
{
ui->setupUi(this);
@@ -65,20 +70,23 @@ AccountSettings::AccountSettings(QWidget *parent) :
ui->_folderList->setMinimumWidth( 300 );
ui->_folderList->setEditTriggers( QAbstractItemView::NoEditTriggers );
ui->_ButtonRemove->setEnabled(false);
ui->_ButtonEnable->setEnabled(false);
ui->_ButtonInfo->setEnabled(false);
ui->_ButtonAdd->setEnabled(true);
ui->_buttonRemove->setEnabled(false);
ui->_buttonEnable->setEnabled(false);
ui->_buttonAdd->setEnabled(true);
QAction *resetFolderAction = new QAction(this);
resetFolderAction->setShortcut(QKeySequence(Qt::Key_F5));
connect(resetFolderAction, SIGNAL(triggered()), SLOT(slotResetCurrentFolder()));
addAction(resetFolderAction);
connect(ui->_ButtonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveCurrentFolder()));
connect(ui->_ButtonEnable, SIGNAL(clicked()), this, SLOT(slotEnableCurrentFolder()));
connect(ui->_ButtonInfo, SIGNAL(clicked()), this, SLOT(slotInfoAboutCurrentFolder()));
connect(ui->_ButtonAdd, SIGNAL(clicked()), this, SLOT(slotAddFolder()));
QAction *syncNowAction = new QAction(this);
syncNowAction->setShortcut(QKeySequence(Qt::Key_F6));
connect(syncNowAction, SIGNAL(triggered()), SLOT(slotSyncCurrentFolderNow()));
addAction(syncNowAction);
connect(ui->_buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveCurrentFolder()));
connect(ui->_buttonEnable, SIGNAL(clicked()), this, SLOT(slotEnableCurrentFolder()));
connect(ui->_buttonAdd, SIGNAL(clicked()), this, SLOT(slotAddFolder()));
connect(ui->modifyAccountButton, SIGNAL(clicked()), SLOT(slotOpenAccountWizard()));
connect(ui->ignoredFilesButton, SIGNAL(clicked()), SLOT(slotIgnoreFilesEditor()));;
@@ -87,32 +95,43 @@ AccountSettings::AccountSettings(QWidget *parent) :
QColor color = palette().highlight().color();
ui->quotaProgressBar->setStyleSheet(QString::fromLatin1(progressBarStyleC).arg(color.name()));
ownCloudInfo *ocInfo = ownCloudInfo::instance();
slotUpdateQuota(ocInfo->lastQuotaTotalBytes(), ocInfo->lastQuotaUsedBytes());
connect(ocInfo, SIGNAL(quotaUpdated(qint64,qint64)), SLOT(slotUpdateQuota(qint64,qint64)));
ui->connectLabel->setWordWrap(true);
ui->connectLabel->setOpenExternalLinks(true);
ui->quotaLabel->setWordWrap(true);
ui->connectLabel->setWordWrap( true );
ui->connectLabel->setText(tr("No account configured."));
ui->_buttonAdd->setEnabled(false);
if (_account) {
connect(_account, SIGNAL(stateChanged(int)), SLOT(slotAccountStateChanged(int)));
slotAccountStateChanged(_account->state());
}
setFolderList(FolderMan::instance()->map());
slotCheckConnection();
}
void AccountSettings::slotFolderActivated( const QModelIndex& indx )
{
bool state = indx.isValid();
ui->_ButtonRemove->setEnabled( state );
ui->_ButtonEnable->setEnabled( state );
ui->_ButtonInfo->setEnabled( state );
bool haveFolders = ui->_folderList->model()->rowCount() > 0;
ui->_buttonRemove->setEnabled(state);
if( Theme::instance()->singleSyncFolder() ) {
// only one folder synced folder allowed.
ui->_buttonAdd->setVisible(!haveFolders);
} else {
ui->_buttonAdd->setVisible(true);
ui->_buttonAdd->setEnabled( state );
}
ui->_buttonEnable->setEnabled( state );
if ( state ) {
bool folderEnabled = _model->data( indx, FolderStatusDelegate::FolderSyncEnabled).toBool();
qDebug() << "folder is sync enabled: " << folderEnabled;
if ( folderEnabled ) {
ui->_ButtonEnable->setText( tr( "Pause" ) );
ui->_buttonEnable->setText( tr( "Pause" ) );
} else {
ui->_ButtonEnable->setText( tr( "Resume" ) );
ui->_buttonEnable->setText( tr( "Resume" ) );
}
}
}
@@ -155,7 +174,7 @@ void AccountSettings::slotFolderWizardAccepted()
folderMan->slotScheduleAllFolders();
emit folderChanged();
}
buttonsSetEnabled();
slotButtonsSetEnabled();
}
void AccountSettings::slotFolderWizardRejected()
@@ -179,35 +198,24 @@ void AccountSettings::slotAddFolder( Folder *folder )
QStandardItem *item = new QStandardItem();
folderToModelItem( item, folder );
_model->appendRow( item );
slotCheckConnection();
// in order to update the enabled state of the "Sync now" button
connect(folder, SIGNAL(syncStateChange()), this, SLOT(slotButtonsSetEnabled()), Qt::UniqueConnection);
}
void AccountSettings::buttonsSetEnabled()
void AccountSettings::slotButtonsSetEnabled()
{
bool haveFolders = ui->_folderList->model()->rowCount() > 0;
ui->_ButtonRemove->setEnabled(false);
if( Theme::instance()->singleSyncFolder() ) {
// only one folder synced folder allowed.
ui->_ButtonAdd->setVisible(!haveFolders);
} else {
ui->_ButtonAdd->setVisible(true);
ui->_ButtonAdd->setEnabled(true);
}
QModelIndex selected = ui->_folderList->currentIndex();
bool isSelected = selected.isValid();
ui->_ButtonEnable->setEnabled(isSelected);
ui->_ButtonRemove->setEnabled(isSelected);
ui->_ButtonInfo->setEnabled(isSelected);
if (isSelected) {
slotFolderActivated(selected);
}
}
void AccountSettings::setListWidgetItem( QListWidgetItem *item )
void AccountSettings::setGeneralErrors( const QStringList& errors )
{
_item = item;
_generalErrors = errors;
}
void AccountSettings::folderToModelItem( QStandardItem *item, Folder *f )
@@ -215,7 +223,7 @@ void AccountSettings::folderToModelItem( QStandardItem *item, Folder *f )
if( ! item || !f ) return;
item->setData( f->nativePath(), FolderStatusDelegate::FolderPathRole );
item->setData( f->secondPath(), FolderStatusDelegate::FolderSecondPathRole );
item->setData( f->remotePath(), FolderStatusDelegate::FolderSecondPathRole );
item->setData( f->alias(), FolderStatusDelegate::FolderAliasRole );
item->setData( f->syncEnabled(), FolderStatusDelegate::FolderSyncEnabled );
@@ -223,27 +231,40 @@ void AccountSettings::folderToModelItem( QStandardItem *item, Folder *f )
SyncResult::Status status = res.status();
QStringList errorList = res.errorStrings();
QString errors;
if( ! errorList.isEmpty() ) {
errors = res.errorStrings().join(QLatin1String("<br/>"));
}
Theme *theme = Theme::instance();
item->setData( theme->statusHeaderText( status ), Qt::ToolTipRole );
if( f->syncEnabled() ) {
item->setData( theme->syncStateIcon( status ), FolderStatusDelegate::FolderStatusIconRole );
if( status == SyncResult::SyncPrepare ) {
if( _wasDisabledBefore ) {
// if the folder was disabled before, set the sync icon
item->setData( theme->syncStateIcon( SyncResult::SyncRunning), FolderStatusDelegate::FolderStatusIconRole );
} // we keep the previous icon for the SyncPrepare state.
} else {
// kepp the previous icon for the prepare phase.
if( status == SyncResult::Problem) {
item->setData( theme->syncStateIcon( SyncResult::Success), FolderStatusDelegate::FolderStatusIconRole );
} else {
item->setData( theme->syncStateIcon( status ), FolderStatusDelegate::FolderStatusIconRole );
}
}
} else {
item->setData( theme->folderDisabledIcon( ), FolderStatusDelegate::FolderStatusIconRole ); // size 48 before
item->setData( theme->folderDisabledIcon( ), FolderStatusDelegate::FolderStatusIconRole ); // size 48 before
_wasDisabledBefore = false;
}
item->setData( theme->statusHeaderText( status ), FolderStatusDelegate::FolderStatus );
item->setData( errors, FolderStatusDelegate::FolderErrorMsg );
item->setData( theme->statusHeaderText( status ), FolderStatusDelegate::FolderStatus );
if( errors.isEmpty() && (status == SyncResult::Error ||
status == SyncResult::SetupError ||
status == SyncResult::Unavailable )) {
item->setData( theme->statusHeaderText(status), FolderStatusDelegate::FolderErrorMsg);
if( errorList.isEmpty() ) {
if( (status == SyncResult::Error ||
status == SyncResult::SetupError ||
status == SyncResult::SyncAbortRequested ||
status == SyncResult::Unavailable)) {
errorList << theme->statusHeaderText(status);
}
}
item->setData( errorList, FolderStatusDelegate::FolderErrorMsg);
bool ongoing = false;
item->setData( QVariant(res.warnCount()), FolderStatusDelegate::WarningCount );
if( status == SyncResult::SyncRunning ) {
@@ -257,6 +278,8 @@ void AccountSettings::slotRemoveCurrentFolder()
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
if( selected.isValid() ) {
int row = selected.row();
QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString();
qDebug() << "Remove Folder alias " << alias;
if( !alias.isEmpty() ) {
@@ -270,11 +293,23 @@ void AccountSettings::slotRemoveCurrentFolder()
if( ret == QMessageBox::No ) {
return;
}
/* Remove the selected item from the timer hash. */
QStandardItem *item = NULL;
if( selected.isValid() )
item = _model->itemFromIndex(selected);
if( selected.isValid() && item && _hideProgressTimers.contains(item) ) {
QTimer *t = _hideProgressTimers[item];
t->stop();
_hideProgressTimers.remove(item);
delete(t);
}
FolderMan *folderMan = FolderMan::instance();
folderMan->slotRemoveFolder( alias );
setFolderList(folderMan->map());
_model->removeRow(row);
emit folderChanged();
slotCheckConnection();
}
}
}
@@ -293,7 +328,7 @@ void AccountSettings::slotResetCurrentFolder()
if( ret == QMessageBox::Yes ) {
FolderMan *folderMan = FolderMan::instance();
Folder *f = folderMan->folder(alias);
f->slotTerminateSync();
f->slotTerminateSync(true);
f->wipe();
folderMan->slotScheduleAllFolders();
}
@@ -308,29 +343,33 @@ void AccountSettings::slotDoubleClicked( const QModelIndex& indx )
emit openFolderAlias( alias );
}
void AccountSettings::slotCheckConnection()
void AccountSettings::showConnectionLabel( const QString& message, const QString& tooltip )
{
if( ownCloudInfo::instance()->isConfigured() ) {
connect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(const QString&, const QString&, const QString&, const QString&)),
this, SLOT(slotOCInfo( const QString&, const QString&, const QString&, const QString& )));
connect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotOCInfoFail(QNetworkReply*)));
ui->connectLabel->setText( tr("Checking %1 connection...").arg(Theme::instance()->appNameGUI()));
qDebug() << "Check status.php from statusdialog.";
ownCloudInfo::instance()->checkInstallation();
const QString errStyle = QLatin1String("color:#ffffff; background-color:#bb4d4d;padding:5px;"
"border-width: 1px; border-style: solid; border-color: #aaaaaa;"
"border-radius:5px;");
if( _generalErrors.isEmpty() ) {
ui->connectLabel->setText( message );
ui->connectLabel->setToolTip(tooltip);
} else {
// ownCloud is not yet configured.
ui->connectLabel->setText( tr("No %1 connection configured.").arg(Theme::instance()->appNameGUI()));
ui->_ButtonAdd->setEnabled( false);
const QString msg = _generalErrors.join(QLatin1String("\n"));
ui->connectLabel->setText( msg );
ui->connectLabel->setToolTip(QString());
ui->connectLabel->setStyleSheet(errStyle);
}
}
void AccountSettings::setFolderList( const Folder::Map &folders )
{
_model->clear();
foreach(QTimer *t, _hideProgressTimers) {
t->stop();
delete t;
}
_hideProgressTimers.clear();
foreach( Folder *f, folders ) {
qDebug() << "Folder: " << f;
slotAddFolder( f );
}
@@ -338,7 +377,7 @@ void AccountSettings::setFolderList( const Folder::Map &folders )
if (idx.isValid()) {
ui->_folderList->setCurrentIndex(idx);
}
buttonsSetEnabled();
slotButtonsSetEnabled();
}
@@ -395,10 +434,15 @@ void AccountSettings::slotEnableCurrentFolder()
// message box can return at any time while the thread keeps running,
// so better check again after the user has responded.
if ( f->isBusy() && terminate )
folderMan->terminateSyncProcess( alias );
if ( f->isBusy() && terminate ) {
f->slotTerminateSync(false);
}
folderMan->slotEnableFolder( alias, !folderEnabled );
// keep state for the icon setting.
if( !folderEnabled ) _wasDisabledBefore = true;
slotUpdateFolderState (f);
// set the button text accordingly.
slotFolderActivated( selected );
@@ -406,6 +450,20 @@ void AccountSettings::slotEnableCurrentFolder()
}
}
void AccountSettings::slotSyncCurrentFolderNow()
{
QModelIndex selected = ui->_folderList->selectionModel()->currentIndex();
if( !selected.isValid() )
return;
QString alias = _model->data( selected, FolderStatusDelegate::FolderAliasRole ).toString();
FolderMan *folderMan = FolderMan::instance();
Folder *f = folderMan->folder( alias );
if (!f)
return;
f->evaluateSync(QStringList());
}
void AccountSettings::slotUpdateFolderState( Folder *folder )
{
QStandardItem *item = 0;
@@ -428,52 +486,45 @@ void AccountSettings::slotUpdateFolderState( Folder *folder )
} else {
// the dialog is not visible.
}
slotCheckConnection();
}
void AccountSettings::slotOCInfo( const QString& url, const QString& versionStr, const QString& version, const QString& )
{
#ifdef Q_OS_WIN32
// work around a bug in QDesktopServices on Win32, see i-net
QString filePath = url;
//void AccountSettings::slotOCInfo( const QString& url, const QString& versionStr, const QString& version, const QString& )
//{
//#ifdef Q_OS_WIN32
// // work around a bug in QDesktopServices on Win32, see i-net
// QString filePath = url;
if (filePath.startsWith("\\\\") || filePath.startsWith("//"))
_OCUrl.setUrl(QDir::toNativeSeparators(filePath));
else
_OCUrl = QUrl::fromLocalFile(filePath);
#else
_OCUrl = QUrl::fromLocalFile(url);
#endif
// if (filePath.startsWith("\\\\") || filePath.startsWith("//"))
// _OCUrl.setUrl(QDir::toNativeSeparators(filePath));
// else
// _OCUrl = QUrl::fromLocalFile(filePath);
//#else
// _OCUrl = QUrl::fromLocalFile(url);
//#endif
qDebug() << "#-------# oC found on " << url;
/* enable the open button */
ui->connectLabel->setOpenExternalLinks(true);
QUrl safeUrl(url);
safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
ui->connectLabel->setText( tr("Connected to <a href=\"%1\">%2</a>.").arg(url, safeUrl.toString()) );
ui->connectLabel->setToolTip( tr("Version: %1 (%2)").arg(versionStr).arg(version));
ui->_ButtonAdd->setEnabled(true);
// qDebug() << "#-------# oC found on " << url;
// /* enable the open button */
// ui->connectLabel->setOpenExternalLinks(true);
// QUrl safeUrl(url);
// safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
// showConnectionLabel( tr("Connected to <a href=\"%1\">%2</a>.").arg(url, safeUrl.toString()),
// tr("Version: %1 (%2)").arg(versionStr).arg(version) );
// ui->_buttonAdd->setEnabled(true);
disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(const QString&, const QString&, const QString&, const QString&)),
this, SLOT(slotOCInfo( const QString&, const QString&, const QString&, const QString& )));
disconnect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotOCInfoFail(QNetworkReply*)));
}
// disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(const QString&, const QString&, const QString&, const QString&)),
// this, SLOT(slotOCInfo( const QString&, const QString&, const QString&, const QString& )));
// disconnect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
// this, SLOT(slotOCInfoFail(QNetworkReply*)));
//}
void AccountSettings::slotOCInfoFail( QNetworkReply *reply)
{
QString errStr = tr("unknown problem.");
if( reply ) errStr = reply->errorString();
//void AccountSettings::slotOCInfoFail( QNetworkReply *reply)
//{
// QString errStr = tr("unknown problem.");
// if( reply ) errStr = reply->errorString();
ui->connectLabel->setText( tr("<p>Failed to connect to %1: <tt>%2</tt></p>").arg(Theme::instance()->appNameGUI()).arg(errStr) );
ui->_ButtonAdd->setEnabled( false);
disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(const QString&, const QString&, const QString&, const QString&)),
this, SLOT(slotOCInfo( const QString&, const QString&, const QString&, const QString& )));
disconnect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotOCInfoFail(QNetworkReply*)));
}
// showConnectionLabel( tr("<p>Failed to connect to %1: <tt>%2</tt></p>").arg(Theme::instance()->appNameGUI()).arg(errStr) );
// ui->_buttonAdd->setEnabled( false);
//}
void AccountSettings::slotOpenOC()
{
@@ -516,9 +567,8 @@ QString AccountSettings::shortenFilename( const QString& folder, const QString&
// rip off the whole ownCloud URL.
Folder *f = FolderMan::instance()->folder(folder);
if( f ) {
QString remotePathUrl = ownCloudInfo::instance()->webdavUrl() + QLatin1Char('/') + f->secondPath();
QString remotePathUrl = f->remoteUrl().toString();
shortFile.remove(Utility::toCSyncScheme(remotePathUrl));
}
}
return shortFile;
@@ -538,7 +588,7 @@ void AccountSettings::slotProgressProblem(const QString& folder, const Progress:
void AccountSettings::slotSetProgress(const QString& folder, const Progress::Info &progress )
{
// qDebug() << "================================> Progress for folder " << folder << " file " << file << ": "<< p1;
// qDebug() << "================================> Progress for folder " << folder << " progress " << Progress::asResultString(progress.kind);
QStandardItem *item = itemForFolder( folder );
qint64 prog1 = progress.current_file_bytes;
qint64 prog2 = progress.file_size;
@@ -552,6 +602,11 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf
qDebug() << "================================> INVALID Progress for folder " << folder;
return;
}
if( (progress.kind == Progress::StartSync)
&& progress.overall_file_count == 0 ) {
// do not show progress if nothing is transmitted.
return;
}
QString itemFileName = shortenFilename(folder, progress.current_file);
QString syncFileProgressString;
@@ -578,6 +633,7 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf
case Progress::StartDownload:
case Progress::StartUpload:
case Progress::StartDelete:
case Progress::StartRename:
syncFileProgressString = tr("Start");
if( _hideProgressTimers.contains(item) ) {
// The timer is still running.
@@ -592,6 +648,7 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf
case Progress::EndDownload:
case Progress::EndUpload:
case Progress::EndDelete:
case Progress::EndRename:
break;
case Progress::EndSync:
syncFileProgressString = tr("Completely");
@@ -613,7 +670,9 @@ void AccountSettings::slotSetProgress(const QString& folder, const Progress::Inf
case Progress::Download:
case Progress::Upload:
case Progress::Inactive:
case Progress::Error:
case Progress::SoftError:
case Progress::NormalError:
case Progress::FatalError:
break;
}
@@ -654,12 +713,21 @@ void AccountSettings::slotHideProgress()
while (i != _hideProgressTimers.constEnd()) {
if( i.value() == send_timer ) {
QStandardItem *item = i.key();
item->setData( QVariant(false), FolderStatusDelegate::AddProgressSpace );
item->setData( QVariant(QString::null), FolderStatusDelegate::SyncProgressOverallString );
item->setData( QVariant(QString::null), FolderStatusDelegate::SyncProgressItemString );
item->setData( 0, FolderStatusDelegate::SyncProgressOverallPercent );
ui->_folderList->repaint();
/* Check if this item is still existing */
bool ok = false;
for( int r = 0; !ok && r < _model->rowCount(); r++) {
if( item == _model->item(r,0) ) {
ok = true;
}
}
if( ok ) {
item->setData( QVariant(false), FolderStatusDelegate::AddProgressSpace );
item->setData( QVariant(QString::null), FolderStatusDelegate::SyncProgressOverallString );
item->setData( QVariant(QString::null), FolderStatusDelegate::SyncProgressItemString );
item->setData( 0, FolderStatusDelegate::SyncProgressOverallPercent );
}
_hideProgressTimers.remove(item);
break;
}
@@ -682,7 +750,9 @@ void AccountSettings::slotUpdateQuota(qint64 total, qint64 used)
ui->quotaProgressBar->setValue(qVal);
QString usedStr = Utility::octetsToString(used);
QString totalStr = Utility::octetsToString(total);
ui->quotaLabel->setText(tr("%1 of %2 in use.").arg(usedStr, totalStr));
double percent = used/(double)total*100;
QString percentStr = Utility::compactFormatDouble(percent, 1);
ui->quotaLabel->setText(tr("%1 of %2 (%3%) in use.").arg(usedStr, totalStr, percentStr));
} else {
ui->quotaProgressBar->setVisible(false);
ui->quotaInfoLabel->setVisible(false);
@@ -701,9 +771,35 @@ void AccountSettings::slotIgnoreFilesEditor()
}
}
void AccountSettings::slotInfoAboutCurrentFolder()
void AccountSettings::slotAccountStateChanged(int state)
{
emit(openProgressDialog());
if (_account) {
QUrl safeUrl(_account->url());
safeUrl.setPassword(QString()); // Remove the password from the URL to avoid showing it in the UI
ui->_buttonAdd->setEnabled(state == Account::Connected);
if (state == Account::Connected) {
QString user;
if (AbstractCredentials *cred = _account->credentials()) {
user = cred->user();
}
if (user.isEmpty()) {
showConnectionLabel( tr("Connected to <a href=\"%1\">%2</a>.").arg(_account->url().toString(), safeUrl.toString())
/*, tr("Version: %1 (%2)").arg(versionStr).arg(version) */ );
} else {
showConnectionLabel( tr("Connected to <a href=\"%1\">%2</a> as <i>%3</i>.").arg(_account->url().toString(), safeUrl.toString(), user)
/*, tr("Version: %1 (%2)").arg(versionStr).arg(version) */ );
}
} else {
showConnectionLabel( tr("No connection to %1 at <a href=\"%1\">%2</a>.")
.arg(Theme::instance()->appNameGUI(),
_account->url().toString(),
safeUrl.toString()) );
}
} else {
// ownCloud is not yet configured.
showConnectionLabel( tr("No %1 connection configured.").arg(Theme::instance()->appNameGUI()) );
ui->_buttonAdd->setEnabled( false);
}
}
AccountSettings::~AccountSettings()

View File

@@ -23,7 +23,6 @@
#include "mirall/folder.h"
#include "mirall/progressdispatcher.h"
#include "mirall/itemprogressdialog.h"
class QStandardItemModel;
class QModelIndex;
@@ -38,8 +37,8 @@ class AccountSettings;
}
class FolderMan;
class ItemProgressDialog;
class IgnoreListEditor;
class Account;
class AccountSettings : public QWidget
{
@@ -50,12 +49,10 @@ public:
~AccountSettings();
void setFolderList( const Folder::Map& );
void buttonsSetEnabled();
void setListWidgetItem(QListWidgetItem* item);
signals:
void folderChanged();
void openProgressDialog();
void openProtocol();
void openFolderAlias( const QString& );
void infoFolderAlias( const QString& );
@@ -63,23 +60,24 @@ public slots:
void slotFolderActivated( const QModelIndex& );
void slotOpenOC();
void slotUpdateFolderState( Folder* );
void slotCheckConnection();
void slotOCInfo( const QString&, const QString&, const QString&, const QString& );
void slotOCInfoFail( QNetworkReply* );
void slotDoubleClicked( const QModelIndex& );
void slotFolderOpenAction( const QString& );
void slotSetProgress(const QString&, const Progress::Info& progress);
void slotProgressProblem(const QString& folder, const Progress::SyncProblem& problem);
void slotButtonsSetEnabled();
void slotUpdateQuota( qint64,qint64 );
void slotIgnoreFilesEditor();
void slotAccountStateChanged(int state);
void setGeneralErrors( const QStringList& errors );
protected slots:
void slotAddFolder();
void slotAddFolder( Folder* );
void slotEnableCurrentFolder();
void slotSyncCurrentFolderNow();
void slotRemoveCurrentFolder();
void slotInfoAboutCurrentFolder();
void slotResetCurrentFolder();
void slotFolderWizardAccepted();
void slotFolderWizardRejected();
@@ -90,16 +88,17 @@ private:
QString shortenFilename( const QString& folder, const QString& file ) const;
void folderToModelItem( QStandardItem *, Folder * );
QStandardItem* itemForFolder(const QString& );
void showConnectionLabel( const QString& message, const QString& tooltip = QString() );
Ui::AccountSettings *ui;
QPointer<ItemProgressDialog> _fileItemDialog;
QPointer<IgnoreListEditor> _ignoreEditor;
QStandardItemModel *_model;
QListWidgetItem *_item;
QUrl _OCUrl;
QHash<QStandardItem*, QTimer*> _hideProgressTimers;
QString _kindContext;
QStringList _generalErrors;
bool _wasDisabledBefore;
Account *_account;
};
} // namespace Mirall

View File

@@ -43,7 +43,7 @@
<item row="0" column="0" colspan="2">
<widget class="QGroupBox" name="syncStatusGroupBox">
<property name="title">
<string>Sync Status</string>
<string>Account to Synchronize</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
@@ -59,21 +59,21 @@
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="_ButtonAdd">
<widget class="QPushButton" name="_buttonAdd">
<property name="text">
<string>Add Folder...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonEnable">
<widget class="QPushButton" name="_buttonEnable">
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="_ButtonRemove">
<widget class="QPushButton" name="_buttonRemove">
<property name="text">
<string>Remove</string>
</property>
@@ -92,13 +92,6 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="_ButtonInfo">
<property name="text">
<string>Info...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -121,6 +114,9 @@
<property name="value">
<number>-1</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>

View File

@@ -19,25 +19,18 @@
#include "config.h"
#include "mirall/application.h"
#include "mirall/systray.h"
#include "mirall/folder.h"
#include "mirall/folderman.h"
#include "mirall/folderwatcher.h"
#include "mirall/folderwizard.h"
#include "mirall/networklocation.h"
#include "mirall/folder.h"
#include "mirall/owncloudsetupwizard.h"
#include "mirall/owncloudinfo.h"
#include "mirall/sslerrordialog.h"
#include "mirall/theme.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/updatedetector.h"
#include "mirall/logger.h"
#include "mirall/settingsdialog.h"
#include "mirall/itemprogressdialog.h"
#include "mirall/utility.h"
#include "mirall/inotify.h"
#include "mirall/connectionvalidator.h"
#include "mirall/socketapi.h"
#include "mirall/account.h"
#include "creds/abstractcredentials.h"
@@ -45,26 +38,14 @@
#include <windows.h>
#endif
#include <QtCore>
#include <QtGui>
#include <QHash>
#include <QHashIterator>
#include <QUrl>
#include <QDesktopServices>
#include <QTranslator>
#include <QNetworkProxy>
#include <QNetworkProxyFactory>
#include <QMenu>
#include <QMessageBox>
namespace Mirall {
// application logging handler.
void mirallLogCatcher(QtMsgType type, const char *msg)
{
Q_UNUSED(type)
// qDebug() exports to local8Bit, which is not always UTF-8
Logger::instance()->mirallLog( QString::fromLocal8Bit(msg) );
}
namespace {
static const char optionsC[] =
@@ -83,7 +64,7 @@ static const char optionsC[] =
QString applicationTrPath()
{
#ifdef Q_OS_LINUX
return QString::fromLatin1(DATADIR"/i18n/");
return QString::fromLatin1(DATADIR"/"APPLICATION_EXECUTABLE"/i18n/");
#endif
#ifdef Q_OS_MAC
return QApplication::applicationDirPath()+QLatin1String("/../Resources/Translations"); // path defaults to app dir.
@@ -98,17 +79,14 @@ QString applicationTrPath()
Application::Application(int &argc, char **argv) :
SharedTools::QtSingleApplication(argc, argv),
_tray(0),
_networkMgr(new QNetworkConfigurationManager(this)),
_sslErrorDialog(0),
_contextMenu(0),
_recentActionsMenu(0),
_gui(0),
_theme(Theme::instance()),
_logBrowser(0),
_logExpire(0),
_helpOnly(false),
_startupNetworkError(false),
_showLogWindow(false),
_logExpire(0),
_logFlush(false),
_helpOnly(false)
_userTriggeredConnect(false)
{
setApplicationName( _theme->appNameGUI() );
setWindowIcon( _theme->applicationIcon() );
@@ -117,54 +95,42 @@ Application::Application(int &argc, char **argv) :
//no need to waste time;
if ( _helpOnly ) return;
setupLogBrowser();
setupLogging();
setupTranslations();
connect( this, SIGNAL(messageReceived(QString)), SLOT(slotParseOptions(QString)));
connect( Logger::instance(), SIGNAL(guiLog(QString,QString)),
this, SLOT(slotShowTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(optionalGuiLog(QString,QString)),
this, SLOT(slotShowOptionalTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(guiMessage(QString,QString)),
this, SLOT(slotShowGuiMessage(QString,QString)));
ProgressDispatcher *pd = ProgressDispatcher::instance();
connect( pd, SIGNAL(progressInfo(QString,Progress::Info)), this,
SLOT(slotUpdateProgress(QString,Progress::Info)) );
connect( pd, SIGNAL(progressSyncProblem(QString,Progress::SyncProblem)),
SLOT(slotProgressSyncProblem(QString,Progress::SyncProblem)));
Account *account = Account::restore();
if (account) {
account->setSslErrorHandler(new SslDialogErrorHandler);
AccountManager::instance()->setAccount(account);
}
// create folder manager for sync folder management
FolderMan *folderMan = FolderMan::instance();
connect( folderMan, SIGNAL(folderSyncStateChange(QString)),
this,SLOT(slotSyncStateChange(QString)));
folderMan->setSyncEnabled(false);
/* use a signal mapper to map the open requests to the alias names */
_folderOpenActionMapper = new QSignalMapper(this);
connect(_folderOpenActionMapper, SIGNAL(mapped(const QString &)),
this, SLOT(slotFolderOpenAction(const QString &)));
FolderMan::instance()->setSyncEnabled(false);
setQuitOnLastWindowClosed(false);
qRegisterMetaType<Progress::Kind>("Progress::Kind");
qRegisterMetaType<Progress::Info>("Progress::Info");
#if 0
qDebug() << "* Network is" << (_networkMgr->isOnline() ? "online" : "offline");
foreach (const QNetworkConfiguration& netCfg, _networkMgr->allConfigurations(QNetworkConfiguration::Active)) {
//qDebug() << "Network:" << netCfg.identifier();
}
#endif
qRegisterMetaType<Progress::SyncProblem>("Progress::SyncProblem");
MirallConfigFile cfg;
_theme->setSystrayUseMonoIcons(cfg.monoIcons());
connect (_theme, SIGNAL(systrayUseMonoIconsChanged(bool)), SLOT(slotUseMonoIconsChanged(bool)));
setupActions();
setupSystemTray();
slotSetupProxy();
FolderMan::instance()->setupFolders();
slotSetupProxy(); // folders have to be defined first.
folderMan->setupFolders();
_gui = new ownCloudGui(this);
if( _showLogWindow ) {
_gui->slotToggleLogBrowser(); // _showLogWindow is set in parseOptions.
}
connect( _gui, SIGNAL(setupProxy()), SLOT(slotSetupProxy()));
if (account) {
connect(account, SIGNAL(stateChanged(int)), _gui, SLOT(slotAccountStateChanged()));
}
connect(AccountManager::instance(), SIGNAL(accountChanged(Account*,Account*)),
this, SLOT(slotAccountChanged(Account*,Account*)));
// startup procedure.
QTimer::singleShot( 0, this, SLOT( slotCheckConnection() ));
@@ -173,25 +139,61 @@ Application::Application(int &argc, char **argv) :
QTimer::singleShot( 3000, this, SLOT( slotStartUpdateDetector() ));
}
connect( ownCloudInfo::instance(), SIGNAL(sslFailed(QNetworkReply*, QList<QSslError>)),
this,SLOT(slotSSLFailed(QNetworkReply*, QList<QSslError>)));
connect (this, SIGNAL(aboutToQuit()), SLOT(slotCleanup()));
connect( ownCloudInfo::instance(), SIGNAL(quotaUpdated(qint64,qint64)),
SLOT(slotRefreshQuotaDisplay(qint64, qint64)));
_socketApi = new SocketApi(this, cfg.configPathWithAppName().append(QLatin1String("socket")));
qDebug() << "Network Location: " << NetworkLocation::currentLocation().encoded();
}
Application::~Application()
{
if (_settingsDialog) {
delete _settingsDialog.data();
// qDebug() << "* Mirall shutdown";
}
void Application::slotLogin()
{
Account *a = AccountManager::instance()->account();
if (a) {
FolderMan::instance()->setupFolders();
_userTriggeredConnect = true;
slotCheckConnection();
}
}
delete _logBrowser;
delete _tray; // needed, see ctor
void Application::slotLogout()
{
Account *a = AccountManager::instance()->account();
if (a) {
// invalidate & forget token/password
a->credentials()->invalidateToken(a);
// terminate all syncs and unload folders
FolderMan *folderMan = FolderMan::instance();
folderMan->setSyncEnabled(false);
folderMan->terminateSyncProcess();
folderMan->unloadAllFolders();
a->setState(Account::SignedOut);
// show result
_gui->slotComputeOverallSyncStatus();
}
}
qDebug() << "* Mirall shutdown";
void Application::slotAccountChanged(Account *newAccount, Account *oldAccount)
{
disconnect(oldAccount, SIGNAL(stateChanged(int)), _gui, SLOT(slotOnlineStateChanged()));
connect(newAccount, SIGNAL(stateChanged(int)), _gui, SLOT(slotOnlineStateChanged()));
}
void Application::slotCleanup()
{
// explicitly close windows. This is somewhat of a hack to ensure
// that saving the geometries happens ASAP during a OS shutdown
Account *account = AccountManager::instance()->account();
if (account) {
account->save();
}
_gui->slotShutdown();
_gui->deleteLater();
}
void Application::slotStartUpdateDetector()
@@ -202,36 +204,35 @@ void Application::slotStartUpdateDetector()
void Application::slotCheckConnection()
{
if( checkConfigExists(false) ) {
MirallConfigFile cfg;
AbstractCredentials* credentials(cfg.getCredentials());
Account *account = AccountManager::instance()->account();
if( account ) {
AbstractCredentials* credentials(account->credentials());
if (! credentials->ready()) {
connect( credentials, SIGNAL(fetched()),
this, SLOT(slotCredentialsFetched()));
credentials->fetch();
credentials->fetch(account);
} else {
runValidator();
}
} else {
// the call to checkConfigExists opens the setup wizard
// if the config does not exist. Nothing to do here.
// let gui open the setup wizard
_gui->slotOpenSettingsDialog( true );
}
}
void Application::slotCredentialsFetched()
{
MirallConfigFile cfg;
AbstractCredentials* credentials(cfg.getCredentials());
disconnect(credentials, SIGNAL(fetched()),
Account *account = AccountManager::instance()->account();
disconnect(account->credentials(), SIGNAL(fetched()),
this, SLOT(slotCredentialsFetched()));
runValidator();
}
void Application::runValidator()
{
_conValidator = new ConnectionValidator();
_conValidator = new ConnectionValidator(AccountManager::instance()->account());
connect( _conValidator, SIGNAL(connectionResult(ConnectionValidator::Status)),
this, SLOT(slotConnectionValidatorResult(ConnectionValidator::Status)) );
_conValidator->checkConnection();
@@ -240,66 +241,38 @@ void Application::runValidator()
void Application::slotConnectionValidatorResult(ConnectionValidator::Status status)
{
qDebug() << "Connection Validator Result: " << _conValidator->statusString(status);
QStringList startupFails;
if( status == ConnectionValidator::Connected ) {
FolderMan *folderMan = FolderMan::instance();
qDebug() << "######## Connection and Credentials are ok!";
folderMan->setSyncEnabled(true);
_tray->setIcon( _theme->syncStateIcon( SyncResult::NotYetStarted, true ) );
_tray->show();
int cnt = folderMan->map().size();
slotShowTrayMessage(tr("%1 Sync Started").arg(_theme->appNameGUI()),
tr("Sync started for %n configured sync folder(s).","", cnt));
// queue up the sync for all folders.
folderMan->slotScheduleAllFolders();
computeOverallSyncStatus();
setupContextMenu();
} else if( status == ConnectionValidator::NotConfigured ) {
// this can not happen, it should be caught in first step of startup.
} else {
// What else?
}
_conValidator->deleteLater();
}
void Application::slotSSLFailed( QNetworkReply *reply, QList<QSslError> errors )
{
qDebug() << "SSL-Warnings happened for url " << reply->url().toString();
if( ownCloudInfo::instance()->certsUntrusted() ) {
// User decided once to untrust. Honor this decision.
qDebug() << "Untrusted by user decision, returning.";
return;
}
QString configHandle = ownCloudInfo::instance()->configHandle(reply);
// make the ssl dialog aware of the custom config. It loads known certs.
if( ! _sslErrorDialog ) {
_sslErrorDialog = new SslErrorDialog;
}
_sslErrorDialog->setCustomConfigHandle( configHandle );
if( _sslErrorDialog->setErrorList( errors ) ) {
// all ssl certs are known and accepted. We can ignore the problems right away.
qDebug() << "Certs are already known and trusted, Warnings are not valid.";
reply->ignoreSslErrors();
} else {
if( _sslErrorDialog->exec() == QDialog::Accepted ) {
if( _sslErrorDialog->trustConnection() ) {
reply->ignoreSslErrors();
} else {
// User does not want to trust.
ownCloudInfo::instance()->setCertsUntrusted(true);
}
} else {
ownCloudInfo::instance()->setCertsUntrusted(true);
if(!_connectionMsgBox.isNull()) {
_connectionMsgBox->close();
}
} else {
// if we have problems here, it's unlikely that syncing will work.
FolderMan::instance()->setSyncEnabled(false);
startupFails = _conValidator->errors();
_startupNetworkError = _conValidator->networkError();
if (_userTriggeredConnect) {
if(_connectionMsgBox.isNull()) {
_connectionMsgBox = new QMessageBox(QMessageBox::Warning, tr("Connection failed"),
_conValidator->errors().join(". ").append('.'), QMessageBox::Ok, 0);
_connectionMsgBox->setAttribute(Qt::WA_DeleteOnClose);
_connectionMsgBox->open();
_userTriggeredConnect = false;
}
}
QTimer::singleShot(30*1000, this, SLOT(slotCheckConnection()));
}
_gui->startupConnected( (status == ConnectionValidator::Connected), startupFails);
_conValidator->deleteLater();
}
void Application::slotownCloudWizardDone( int res )
@@ -320,126 +293,15 @@ void Application::slotownCloudWizardDone( int res )
}
void Application::setupActions()
{
_actionOpenoC = new QAction(tr("Open %1 in browser").arg(_theme->appNameGUI()), this);
QObject::connect(_actionOpenoC, SIGNAL(triggered(bool)), SLOT(slotOpenOwnCloud()));
_actionQuota = new QAction(tr("Calculating quota..."), this);
_actionQuota->setEnabled( false );
_actionStatus = new QAction(tr("Unknown status"), this);
_actionStatus->setEnabled( false );
_actionSettings = new QAction(tr("Settings..."), this);
_actionRecent = new QAction(tr("Details..."), this);
_actionRecent->setEnabled( true );
QObject::connect(_actionRecent, SIGNAL(triggered(bool)), SLOT(slotItemProgressDialog()));
QObject::connect(_actionSettings, SIGNAL(triggered(bool)), SLOT(slotSettings()));
_actionHelp = new QAction(tr("Help"), this);
QObject::connect(_actionHelp, SIGNAL(triggered(bool)), SLOT(slotHelp()));
_actionQuit = new QAction(tr("Quit %1").arg(_theme->appNameGUI()), this);
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), SLOT(quit()));
}
void Application::setupSystemTray()
{
// Setting a parent heres will crash on X11 since by the time qapp runs
// its childrens dtors, the X11->screen variable queried for is gone -> crash
_tray = new Systray();
_tray->setIcon( _theme->syncStateIcon( SyncResult::NotYetStarted, true ) );
connect(_tray,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(slotTrayClicked(QSystemTrayIcon::ActivationReason)));
setupContextMenu();
_tray->show();
}
void Application::setupContextMenu()
{
bool isConfigured = ownCloudInfo::instance()->isConfigured();
FolderMan *folderMan = FolderMan::instance();
_actionOpenoC->setEnabled(isConfigured);
if( _contextMenu ) {
_contextMenu->clear();
_recentActionsMenu->clear();
_recentActionsMenu->addAction(tr("None."));
_recentActionsMenu->addAction(_actionRecent);
} else {
_contextMenu = new QMenu();
_recentActionsMenu = _contextMenu->addMenu(tr("Recent Changes"));
// this must be called only once after creating the context menu, or
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
_tray->setContextMenu(_contextMenu);
}
_contextMenu->setTitle(_theme->appNameGUI() );
_contextMenu->addAction(_actionOpenoC);
int folderCnt = folderMan->map().size();
// add open actions for all sync folders to the tray menu
if( _theme->singleSyncFolder() ) {
// there should be exactly one folder. No sync-folder add action will be shown.
QStringList li = folderMan->map().keys();
if( li.size() == 1 ) {
Folder *folder = folderMan->map().value(li.first());
if( folder ) {
// if there is singleFolder mode, a generic open action is displayed.
QAction *action = new QAction( tr("Open %1 folder").arg(_theme->appNameGUI()), this);
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
} else {
// show a grouping with more than one folder.
if ( folderCnt > 1) {
_contextMenu->addAction(tr("Managed Folders:"))->setDisabled(true);
}
foreach (Folder *folder, folderMan->map() ) {
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->alias()), this );
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
_contextMenu->addSeparator();
_contextMenu->addAction(_actionQuota);
_contextMenu->addSeparator();
_contextMenu->addAction(_actionStatus);
_contextMenu->addMenu(_recentActionsMenu);
_contextMenu->addSeparator();
_contextMenu->addAction(_actionSettings);
if (!_theme->helpUrl().isEmpty()) {
_contextMenu->addAction(_actionHelp);
}
_contextMenu->addSeparator();
_contextMenu->addAction(_actionQuit);
}
void Application::setupLogBrowser()
void Application::setupLogging()
{
// might be called from second instance
if (!_logBrowser) {
// init the log browser.
qInstallMsgHandler( mirallLogCatcher );
_logBrowser = new LogBrowser;
// ## TODO: allow new log name maybe?
if (!_logDirectory.isEmpty()) {
enterNextLogFile();
} else if (!_logFile.isEmpty()) {
qDebug() << "Logging into logfile: " << _logFile << " with flush " << _logFlush;
_logBrowser->setLogFile( _logFile, _logFlush );
}
}
Logger::instance()->setLogFile(_logFile);
Logger::instance()->setLogDir(_logDir);
Logger::instance()->setLogExpire(_logExpire);
Logger::instance()->setLogFlush(_logFlush);
if (_showLogWindow)
slotOpenLogBrowser();
Logger::instance()->enterNextLogFile();
qDebug() << QString::fromLatin1( "################## %1 %2 (%3) %4").arg(_theme->appName())
.arg( QLocale::system().name() )
@@ -448,37 +310,6 @@ void Application::setupLogBrowser()
}
void Application::enterNextLogFile()
{
if (_logBrowser && !_logDirectory.isEmpty()) {
QDir dir(_logDirectory);
if (!dir.exists()) {
dir.mkpath(".");
}
// Find out what is the file with the highest nymber if any
QStringList files = dir.entryList(QStringList("owncloud.log.*"),
QDir::Files);
QRegExp rx("owncloud.log.(\\d+)");
uint maxNumber = 0;
QDateTime now = QDateTime::currentDateTime();
foreach(const QString &s, files) {
if (rx.exactMatch(s)) {
maxNumber = qMax(maxNumber, rx.cap(1).toUInt());
if (_logExpire > 0) {
QFileInfo fileInfo = dir.absoluteFilePath(s);
if (fileInfo.lastModified().addSecs(60*60 * _logExpire) < now) {
dir.remove(s);
}
}
}
}
QString filename = _logDirectory + "/owncloud.log." + QString::number(maxNumber+1);
_logBrowser->setLogFile(filename , _logFlush);
}
}
QNetworkProxy proxyFromConfig(const MirallConfigFile& cfg)
{
QNetworkProxy proxy;
@@ -503,6 +334,7 @@ void Application::slotSetupProxy()
switch(proxyType) {
case QNetworkProxy::NoProxy:
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy);
break;
case QNetworkProxy::DefaultProxy:
@@ -510,10 +342,12 @@ void Application::slotSetupProxy()
break;
case QNetworkProxy::Socks5Proxy:
proxy.setType(QNetworkProxy::Socks5Proxy);
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy(proxy);
break;
case QNetworkProxy::HttpProxy:
proxy.setType(QNetworkProxy::HttpProxy);
QNetworkProxyFactory::setUseSystemConfiguration(false);
QNetworkProxy::setApplicationProxy(proxy);
break;
default:
@@ -524,238 +358,16 @@ void Application::slotSetupProxy()
FolderMan::instance()->slotScheduleAllFolders();
}
void Application::slotRefreshQuotaDisplay( qint64 total, qint64 used )
{
if (total == 0) {
_actionQuota->setText(tr("Quota n/a"));
return;
}
double percent = used/(double)total*100;
QString percentFormatted = Utility::compactFormatDouble(percent, 1);
QString totalFormatted = Utility::octetsToString(total);
_actionQuota->setText(tr("%1% of %2 in use").arg(percentFormatted).arg(totalFormatted));
}
void Application::slotUseMonoIconsChanged(bool)
{
computeOverallSyncStatus();
}
void Application::slotProgressSyncProblem(const QString& folder, const Progress::SyncProblem& problem)
{
Q_UNUSED(folder);
Q_UNUSED(problem);
// display a warn icon if warnings happend.
QIcon warnIcon(":/mirall/resources/warning-16");
_actionRecent->setIcon(warnIcon);
rebuildRecentMenus();
}
void Application::rebuildRecentMenus()
{
_recentActionsMenu->clear();
const QList<Progress::Info>& progressInfoList = ProgressDispatcher::instance()->recentChangedItems(5);
if( progressInfoList.size() == 0 ) {
_recentActionsMenu->addAction(tr("No items synced recently"));
} else {
QListIterator<Progress::Info> i(progressInfoList);
while(i.hasNext()) {
Progress::Info info = i.next();
QString kindStr = Progress::asResultString(info.kind);
QString timeStr = info.timestamp.toString("hh:mm");
QString actionText = tr("%1 (%2, %3)").arg(info.current_file).arg(kindStr).arg(timeStr);
_recentActionsMenu->addAction( actionText );
}
}
// add a more... entry.
_recentActionsMenu->addAction(_actionRecent);
}
void Application::slotUpdateProgress(const QString &folder, const Progress::Info& progress)
{
Q_UNUSED(folder);
// shows an entry in the context menu.
QString curAmount = Utility::octetsToString(progress.overall_current_bytes);
QString totalAmount = Utility::octetsToString(progress.overall_transmission_size);
_actionStatus->setText(tr("Syncing %1 of %2 (%3 of %4) ").arg(progress.current_file_no)
.arg(progress.overall_file_count).arg(curAmount, totalAmount));
// wipe the problem list at start of sync.
if( progress.kind == Progress::StartSync ) {
_actionRecent->setIcon( QIcon() ); // Fixme: Set a "in-progress"-item eventually.
}
// If there was a change in the file list, redo the progress menu.
if( progress.kind == Progress::EndDownload || progress.kind == Progress::EndUpload ||
progress.kind == Progress::EndDelete ) {
rebuildRecentMenus();
}
if (progress.kind == Progress::EndSync) {
rebuildRecentMenus(); // show errors.
QTimer::singleShot(2000, this, SLOT(slotDisplayIdle()));
}
}
void Application::slotDisplayIdle()
{
_actionStatus->setText(tr("Up to date"));
}
void Application::slotHelp()
{
QDesktopServices::openUrl(QUrl(_theme->helpUrl()));
}
/*
* open the folder with the given Alais
*/
void Application::slotFolderOpenAction( const QString& alias )
{
Folder *f = FolderMan::instance()->folder(alias);
qDebug() << "opening local url " << f->path();
if( f ) {
QUrl url(f->path(), QUrl::TolerantMode);
url.setScheme( QLatin1String("file") );
#ifdef Q_OS_WIN32
// work around a bug in QDesktopServices on Win32, see i-net
QString filePath = f->path();
if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//")))
url.setUrl(QDir::toNativeSeparators(filePath));
else
url = QUrl::fromLocalFile(filePath);
#endif
QDesktopServices::openUrl(url);
}
}
void Application::slotOpenOwnCloud()
{
MirallConfigFile cfgFile;
QString url = cfgFile.ownCloudUrl();
QDesktopServices::openUrl( url );
}
void Application::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
{
// A click on the tray icon should only open the status window on Win and
// Linux, not on Mac. They want a menu entry.
#if defined Q_WS_WIN || defined Q_WS_X11
if( reason == QSystemTrayIcon::Trigger ) {
checkConfigExists(true); // start settings if config is existing.
}
#endif
}
bool Application::checkConfigExists(bool openSettings)
{
// if no config file is there, start the configuration wizard.
MirallConfigFile cfgFile;
if( cfgFile.exists() ) {
if( openSettings ) {
slotSettings();
}
return true;
} else {
qDebug() << "No configured folders yet, starting setup wizard";
OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int)));
return false;
}
}
void Application::slotOpenLogBrowser()
{
_logBrowser->show();
_logBrowser->raise();
}
// slot hit when a folder gets changed in the settings dialog.
void Application::slotFoldersChanged()
{
computeOverallSyncStatus();
setupContextMenu();
}
void Application::slotSettings()
{
if (_settingsDialog.isNull()) {
_settingsDialog = new SettingsDialog(this);
_settingsDialog->setAttribute( Qt::WA_DeleteOnClose, true );
_settingsDialog->show();
}
Utility::raiseDialog(_settingsDialog);
}
void Application::slotItemProgressDialog()
{
if (_progressDialog.isNull()) {
_progressDialog = new ItemProgressDialog(this);
_progressDialog->setAttribute( Qt::WA_DeleteOnClose, true );
_progressDialog->setupList();
_progressDialog->show();
}
Utility::raiseDialog(_progressDialog);
_gui->slotComputeOverallSyncStatus();
}
void Application::slotParseOptions(const QString &opts)
{
QStringList options = opts.split(QLatin1Char('|'));
parseOptions(options);
setupLogBrowser();
}
void Application::slotShowTrayMessage(const QString &title, const QString &msg)
{
if( _tray )
_tray->showMessage(title, msg);
else
qDebug() << "Tray not ready: " << msg;
}
void Application::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
{
MirallConfigFile cfg;
if (cfg.optionalDesktopNotifications())
slotShowTrayMessage(title, msg);
}
void Application::slotShowGuiMessage(const QString &title, const QString &message)
{
QMessageBox *msgBox = new QMessageBox;
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setText(message);
msgBox->setWindowTitle(title);
msgBox->setIcon(QMessageBox::Information);
msgBox->open();
}
void Application::slotSyncStateChange( const QString& alias )
{
FolderMan *folderMan = FolderMan::instance();
const SyncResult& result = folderMan->syncResult( alias );
emit folderStateChanged( folderMan->folder(alias) );
computeOverallSyncStatus();
qDebug() << "Sync state changed for folder " << alias << ": " << result.statusString();
if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
enterNextLogFile();
}
if( _progressDialog ) {
_progressDialog->setSyncResult(result);
}
setupLogging();
}
void Application::parseOptions(const QStringList &options)
@@ -771,17 +383,17 @@ void Application::parseOptions(const QStringList &options)
setHelp();
break;
} else if (option == QLatin1String("--logwindow") ||
option == QLatin1String("-l")) {
option == QLatin1String("-l")) {
_showLogWindow = true;
} else if (option == QLatin1String("--logfile")) {
if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) {
_logFile = it.next();
_logFile = it.next();
} else {
setHelp();
}
} else if (option == QLatin1String("--logdir")) {
if (it.hasNext() && !it.peekNext().startsWith(QLatin1String("--"))) {
_logDirectory = it.next();
_logDir = it.next();
} else {
setHelp();
}
@@ -804,35 +416,6 @@ void Application::parseOptions(const QStringList &options)
setHelp();
break;
}
}
}
void Application::computeOverallSyncStatus()
{
// display the info of the least successful sync (eg. not just display the result of the latest sync
QString trayMessage;
FolderMan *folderMan = FolderMan::instance();
Folder::Map map = folderMan->map();
SyncResult overallResult = FolderMan::accountStatus(map.values());
// create the tray blob message, check if we have an defined state
if( overallResult.status() != SyncResult::Undefined ) {
QStringList allStatusStrings;
foreach(Folder* folder, map.values()) {
qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncEnabled());
allStatusStrings += tr("Folder %1: %2").arg(folder->alias(), folderMessage);
}
if( ! allStatusStrings.isEmpty() )
trayMessage = allStatusStrings.join(QLatin1String("\n"));
else
trayMessage = tr("No sync folders configured.");
QIcon statusIcon = _theme->syncStateIcon( overallResult.status(), true);
_tray->setIcon( statusIcon );
_tray->setToolTip(trayMessage);
}
}
@@ -875,7 +458,7 @@ void Application::showHelp()
<< QLatin1String(optionsC);
if (_theme->appName() == QLatin1String("ownCloud"))
stream << endl << "For more information, see http://www.owncloud.org" << endl;
stream << endl << "For more information, see http://www.owncloud.org" << endl << endl;
displayHelpText(helpText);
}

View File

@@ -16,8 +16,6 @@
#define APPLICATION_H
#include <QApplication>
#include <QNetworkReply>
#include <QSslError>
#include <QPointer>
#include <QQueue>
@@ -25,26 +23,18 @@
#include "mirall/syncresult.h"
#include "mirall/logbrowser.h"
#include "mirall/systray.h"
#include "mirall/owncloudgui.h"
#include "mirall/connectionvalidator.h"
#include "mirall/progressdispatcher.h"
class QAction;
class QMenu;
class QMessageBox;
class QSystemTrayIcon;
class QNetworkConfigurationManager;
class QSignalMapper;
class QNetworkReply;
namespace Mirall {
class Theme;
class Folder;
class FolderWatcher;
class FolderWizard;
class ownCloudInfo;
class SslErrorDialog;
class SettingsDialog;
class ItemProgressDialog;
class SocketApi;
class Application : public SharedTools::QtSingleApplication
{
@@ -63,16 +53,10 @@ public slots:
protected:
void parseOptions(const QStringList& );
void setupTranslations();
void setupActions();
void setupSystemTray();
void setupContextMenu();
void setupLogBrowser();
void setupLogging();
void enterNextLogFile();
bool checkConfigExists(bool openSettings);
//folders have to be disabled while making config changes
void computeOverallSyncStatus();
// reimplemented
#if defined(Q_WS_WIN)
bool winEventFilter( MSG * message, long * result );
@@ -83,68 +67,42 @@ signals:
void folderStateChanged(Folder*);
protected slots:
void slotFoldersChanged();
void slotSettings();
void slotItemProgressDialog();
void slotParseOptions( const QString& );
void slotShowTrayMessage(const QString&, const QString&);
void slotShowOptionalTrayMessage(const QString&, const QString&);
void slotShowGuiMessage(const QString& title, const QString& message);
void slotCheckConnection();
void slotConnectionValidatorResult(ConnectionValidator::Status);
void slotSyncStateChange( const QString& );
void slotTrayClicked( QSystemTrayIcon::ActivationReason );
void slotFolderOpenAction(const QString & );
void slotOpenOwnCloud();
void slotOpenLogBrowser();
void slotSSLFailed( QNetworkReply *reply, QList<QSslError> errors );
void slotStartUpdateDetector();
void slotSetupProxy();
void slotRefreshQuotaDisplay( qint64 total, qint64 used );
void slotUseMonoIconsChanged( bool );
void slotUpdateProgress(const QString&, const Progress::Info&);
void slotProgressSyncProblem(const QString& folder, const Progress::SyncProblem &problem);
void slotDisplayIdle();
void slotHelp();
void slotCredentialsFetched();
void slotLogin();
void slotLogout();
void slotCleanup();
void slotAccountChanged(Account *newAccount, Account *oldAccount);
private:
void setHelp();
void raiseDialog( QWidget* );
void rebuildRecentMenus();
void runValidator();
Systray *_tray;
QAction *_actionOpenoC;
QAction *_actionSettings;
QAction *_actionQuota;
QAction *_actionStatus;
QAction *_actionRecent;
QAction *_actionHelp;
QAction *_actionQuit;
QPointer<ownCloudGui> _gui;
QPointer<SocketApi> _socketApi;
QNetworkConfigurationManager *_networkMgr;
QPointer<FolderWizard> _folderWizard;
SslErrorDialog *_sslErrorDialog;
ConnectionValidator *_conValidator;
// tray's menu
QMenu *_contextMenu;
QMenu *_recentActionsMenu;
Theme *_theme;
QSignalMapper *_folderOpenActionMapper;
LogBrowser *_logBrowser;
QPointer<SettingsDialog> _settingsDialog;
QPointer<ItemProgressDialog> _progressDialog;
QString _logFile;
QString _logDirectory;
int _logExpire;
bool _showLogWindow;
bool _logFlush;
bool _helpOnly;
bool _startupNetworkError;
// options from command line:
bool _showLogWindow;
QString _logFile;
QString _logDir;
int _logExpire;
bool _logFlush;
bool _userTriggeredConnect;
QPointer<QMessageBox> _connectionMsgBox;
friend class ownCloudGui; // for _startupNetworkError
};
} // namespace Mirall

View File

@@ -12,25 +12,21 @@
*/
#include <QtCore>
#include <QNetworkReply>
#include "mirall/connectionvalidator.h"
#include "mirall/owncloudinfo.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/theme.h"
#include "mirall/account.h"
#include "mirall/networkjobs.h"
namespace Mirall {
ConnectionValidator::ConnectionValidator(QObject *parent) :
QObject(parent)
{
}
ConnectionValidator::ConnectionValidator(const QString& connection, QObject *parent)
ConnectionValidator::ConnectionValidator(Account *account, QObject *parent)
: QObject(parent),
_connection(connection)
_account(account),
_networkError(QNetworkReply::NoError)
{
ownCloudInfo::instance()->setCustomConfigHandle(_connection);
}
QStringList ConnectionValidator::errors() const
@@ -38,6 +34,11 @@ QStringList ConnectionValidator::errors() const
return _errors;
}
bool ConnectionValidator::networkError() const
{
return _networkError;
}
QString ConnectionValidator::statusString( Status stat ) const
{
QString re;
@@ -79,38 +80,30 @@ QString ConnectionValidator::statusString( Status stat ) const
void ConnectionValidator::checkConnection()
{
if( ownCloudInfo::instance()->isConfigured() ) {
connect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
SLOT(slotStatusFound(QString,QString,QString,QString)));
connect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
SLOT(slotNoStatusFound(QNetworkReply*)));
// checks for status.php
ownCloudInfo::instance()->checkInstallation();
if( _account ) {
CheckServerJob *checkJob = new CheckServerJob(_account, false, this);
checkJob->setIgnoreCredentialFailure(true);
connect(checkJob, SIGNAL(instanceFound(QUrl,QVariantMap)), SLOT(slotStatusFound(QUrl,QVariantMap)));
connect(checkJob, SIGNAL(networkError(QNetworkReply*)), SLOT(slotNoStatusFound(QNetworkReply*)));
checkJob->start();
} else {
_errors << tr("No ownCloud account configured");
emit connectionResult( NotConfigured );
}
}
void ConnectionValidator::slotStatusFound( const QString& url, const QString& versionStr, const QString& version, const QString& /*edition*/)
void ConnectionValidator::slotStatusFound(const QUrl&url, const QVariantMap &info)
{
// status.php was found.
qDebug() << "** Application: ownCloud found: " << url << " with version " << versionStr << "(" << version << ")";
qDebug() << "** Application: ownCloud found: "
<< url << " with version "
<< CheckServerJob::versionString(info)
<< "(" << CheckServerJob::version(info) << ")";
// now check the authentication
MirallConfigFile cfgFile(_connection);
cfgFile.setOwnCloudVersion( version );
// disconnect from ownCloudInfo
disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
this, SLOT(slotStatusFound(QString,QString,QString,QString)));
disconnect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotNoStatusFound(QNetworkReply*)));
if( version.startsWith("4.0") ) {
_errors.append( tr("<p>The configured server for this client is too old.</p>"
"<p>Please update to the latest server and restart the client.</p>"));
if( CheckServerJob::version(info).startsWith("4.0") ) {
_errors.append( tr("The configured server for this client is too old") );
_errors.append( tr("Please update to the latest server and restart the client.") );
emit connectionResult( ServerVersionMismatch );
return;
}
@@ -121,46 +114,58 @@ void ConnectionValidator::slotStatusFound( const QString& url, const QString& ve
// status.php could not be loaded.
void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply)
{
// disconnect from ownCloudInfo
disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
this, SLOT(slotStatusFound(QString,QString,QString,QString)));
disconnect( ownCloudInfo::instance(),SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotNoStatusFound(QNetworkReply*)));
_account->setState(Account::Disconnected);
// ### TODO
_errors.append(tr("Unable to connect to %1").arg(_account->url().toString()));
_errors.append( reply->errorString() );
_networkError = (reply->error() != QNetworkReply::NoError);
emit connectionResult( StatusNotFound );
}
void ConnectionValidator::slotCheckAuthentication()
{
connect( ownCloudInfo::instance(), SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this, SLOT(slotAuthCheck(QString,QNetworkReply*)));
qDebug() << "# checking for authentication settings.";
ownCloudInfo::instance()->getWebDAVPath(QLatin1String("/") ); // this call needs to be authenticated.
// simply GET the webdav root, will fail if credentials are wrong.
// continue in slotAuthCheck here :-)
PropfindJob *job = new PropfindJob(_account, "/", this);
job->setIgnoreCredentialFailure(true);
job->setProperties(QList<QByteArray>() << "getlastmodified");
connect(job, SIGNAL(result(QVariantMap)), SLOT(slotAuthSuccess()));
connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotAuthFailed(QNetworkReply*)));
job->start();
qDebug() << "# checking for authentication settings.";
}
void ConnectionValidator::slotAuthCheck( const QString& ,QNetworkReply *reply )
void ConnectionValidator::slotAuthFailed(QNetworkReply *reply)
{
Status stat = Connected;
Status stat = StatusNotFound;
if( reply->error() == QNetworkReply::AuthenticationRequiredError ||
reply->error() == QNetworkReply::OperationCanceledError ) { // returned if the user is wrong.
reply->error() == QNetworkReply::OperationCanceledError ) { // returned if the user/pwd is wrong.
qDebug() << reply->error() << reply->errorString();
qDebug() << "******** Password is wrong!";
_errors << "The provided credentials are wrong.";
_errors << tr("The provided credentials are not correct");
stat = CredentialsWrong;
}
switch (_account->state()) {
case Account::SignedOut:
_account->setState(Account::SignedOut);
break;
default:
_account->setState(Account::Disconnected);
}
// disconnect from ownCloud Info signals
disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this,SLOT(slotAuthCheck(QString,QNetworkReply*)));
} else if( reply->error() != QNetworkReply::NoError ) {
_errors << reply->errorString();
}
emit connectionResult( stat );
}
void ConnectionValidator::slotAuthSuccess()
{
_account->setState(Account::Connected);
emit connectionResult(Connected);
}
} // namespace Mirall

View File

@@ -16,17 +16,18 @@
#include <QObject>
#include <QStringList>
class QNetworkReply;
#include <QVariantMap>
#include <QNetworkReply>
namespace Mirall {
class Account;
class ConnectionValidator : public QObject
{
Q_OBJECT
public:
explicit ConnectionValidator(QObject *parent = 0);
explicit ConnectionValidator(const QString& connection, QObject *parent = 0);
explicit ConnectionValidator(Account *account, QObject *parent = 0);
enum Status {
Undefined,
@@ -42,6 +43,7 @@ public:
};
QStringList errors() const;
bool networkError() const;
void checkConnection();
@@ -55,15 +57,17 @@ signals:
public slots:
protected slots:
void slotStatusFound( const QString&, const QString&, const QString&, const QString& );
void slotNoStatusFound(QNetworkReply *);
void slotStatusFound(const QUrl&url, const QVariantMap &info);
void slotNoStatusFound(QNetworkReply *reply);
void slotCheckAuthentication();
void slotAuthCheck( const QString& ,QNetworkReply * );
void slotAuthFailed(QNetworkReply *reply);
void slotAuthSuccess();
private:
QStringList _errors;
QString _connection;
Account *_account;
bool _networkError;
};
}

View File

@@ -14,10 +14,13 @@
*/
#include "mirall/csyncthread.h"
#include "mirall/account.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/theme.h"
#include "mirall/logger.h"
#include "mirall/owncloudinfo.h"
#include "owncloudpropagator.h"
#include "syncjournaldb.h"
#include "syncjournalfilerecord.h"
#include "creds/abstractcredentials.h"
#ifdef Q_OS_WIN
@@ -42,16 +45,28 @@
namespace Mirall {
/* static variables to hold the credentials */
void csyncLogCatcher(int /*verbosity*/,
const char */*function*/,
const char *buffer,
void */*userdata*/)
{
Logger::instance()->csyncLog( QString::fromUtf8(buffer) );
}
/* static variables to hold the credentials */
QMutex CSyncThread::_mutex;
QMutex CSyncThread::_syncMutex;
CSyncThread::CSyncThread(CSYNC *csync)
CSyncThread::CSyncThread(CSYNC *csync, const QString &localPath, const QString &remotePath, SyncJournalDb *journal)
{
_mutex.lock();
_localPath = localPath;
_remotePath = remotePath;
_csync_ctx = csync;
_journal = journal;
_mutex.unlock();
qRegisterMetaType<SyncFileItem>("SyncFileItem");
qRegisterMetaType<SyncFileItem::Status>("SyncFileItem::Status");
}
CSyncThread::~CSyncThread()
@@ -61,110 +76,173 @@ CSyncThread::~CSyncThread()
//Convert an error code from csync to a user readable string.
// Keep that function thread safe as it can be called from the sync thread or the main thread
QString CSyncThread::csyncErrorToString( CSYNC_ERROR_CODE err, const char *errString )
QString CSyncThread::csyncErrorToString(CSYNC_STATUS err)
{
QString errStr;
switch( err ) {
case CSYNC_ERR_NONE:
case CSYNC_STATUS_OK:
errStr = tr("Success.");
break;
case CSYNC_ERR_LOG:
errStr = tr("CSync Logging setup failed.");
break;
case CSYNC_ERR_LOCK:
case CSYNC_STATUS_NO_LOCK:
errStr = tr("CSync failed to create a lock file.");
break;
case CSYNC_ERR_STATEDB_LOAD:
case CSYNC_STATUS_STATEDB_LOAD_ERROR:
errStr = tr("CSync failed to load the state db.");
break;
case CSYNC_ERR_MODULE:
case CSYNC_STATUS_STATEDB_WRITE_ERROR:
errStr = tr("CSync failed to write the state db.");
break;
case CSYNC_STATUS_NO_MODULE:
errStr = tr("<p>The %1 plugin for csync could not be loaded.<br/>Please verify the installation!</p>").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_ERR_TIMESKEW:
case CSYNC_STATUS_TIMESKEW:
errStr = tr("The system time on this client is different than the system time on the server. "
"Please use a time synchronization service (NTP) on the server and client machines "
"so that the times remain the same.");
break;
case CSYNC_ERR_FILESYSTEM:
case CSYNC_STATUS_FILESYSTEM_UNKNOWN:
errStr = tr("CSync could not detect the filesystem type.");
break;
case CSYNC_ERR_TREE:
case CSYNC_STATUS_TREE_ERROR:
errStr = tr("CSync got an error while processing internal trees.");
break;
case CSYNC_ERR_MEM:
case CSYNC_STATUS_MEMORY_ERROR:
errStr = tr("CSync failed to reserve memory.");
break;
case CSYNC_ERR_PARAM:
case CSYNC_STATUS_PARAM_ERROR:
errStr = tr("CSync fatal parameter error.");
break;
case CSYNC_ERR_UPDATE:
case CSYNC_STATUS_UPDATE_ERROR:
errStr = tr("CSync processing step update failed.");
break;
case CSYNC_ERR_RECONCILE:
case CSYNC_STATUS_RECONCILE_ERROR:
errStr = tr("CSync processing step reconcile failed.");
break;
case CSYNC_ERR_PROPAGATE:
case CSYNC_STATUS_PROPAGATE_ERROR:
errStr = tr("CSync processing step propagate failed.");
break;
case CSYNC_ERR_ACCESS_FAILED:
case CSYNC_STATUS_REMOTE_ACCESS_ERROR:
errStr = tr("<p>The target directory does not exist.</p><p>Please check the sync setup.</p>");
break;
case CSYNC_ERR_REMOTE_CREATE:
case CSYNC_ERR_REMOTE_STAT:
case CSYNC_STATUS_REMOTE_CREATE_ERROR:
case CSYNC_STATUS_REMOTE_STAT_ERROR:
errStr = tr("A remote file can not be written. Please check the remote access.");
break;
case CSYNC_ERR_LOCAL_CREATE:
case CSYNC_ERR_LOCAL_STAT:
case CSYNC_STATUS_LOCAL_CREATE_ERROR:
case CSYNC_STATUS_LOCAL_STAT_ERROR:
errStr = tr("The local filesystem can not be written. Please check permissions.");
break;
case CSYNC_ERR_PROXY:
case CSYNC_STATUS_PROXY_ERROR:
errStr = tr("CSync failed to connect through a proxy.");
break;
case CSYNC_ERR_LOOKUP:
case CSYNC_STATUS_PROXY_AUTH_ERROR:
errStr = tr("CSync could not authenticate at the proxy.");
break;
case CSYNC_STATUS_LOOKUP_ERROR:
errStr = tr("CSync failed to lookup proxy or server.");
break;
case CSYNC_ERR_AUTH_SERVER:
case CSYNC_STATUS_SERVER_AUTH_ERROR:
errStr = tr("CSync failed to authenticate at the %1 server.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_ERR_AUTH_PROXY:
errStr = tr("CSync failed to authenticate at the proxy.");
break;
case CSYNC_ERR_CONNECT:
case CSYNC_STATUS_CONNECT_ERROR:
errStr = tr("CSync failed to connect to the network.");
break;
case CSYNC_ERR_TIMEOUT:
case CSYNC_STATUS_TIMEOUT:
errStr = tr("A network connection timeout happend.");
break;
case CSYNC_ERR_HTTP:
case CSYNC_STATUS_HTTP_ERROR:
errStr = tr("A HTTP transmission error happened.");
break;
case CSYNC_ERR_PERM:
errStr = tr("CSync: Permission deniend.");
case CSYNC_STATUS_PERMISSION_DENIED:
errStr = tr("CSync failed due to not handled permission deniend.");
break;
case CSYNC_ERR_NOT_FOUND:
errStr = tr("CSync: File not found.");
case CSYNC_STATUS_NOT_FOUND:
errStr = tr("CSync failed to access "); // filename gets added.
break;
case CSYNC_ERR_EXISTS:
errStr = tr("CSync: Directory already exists.");
case CSYNC_STATUS_FILE_EXISTS:
errStr = tr("CSync tried to create a directory that already exists.");
break;
case CSYNC_ERR_NOSPC:
errStr = tr("CSync: No space left on %1 server.").arg(Theme::instance()->appNameGUI());
case CSYNC_STATUS_OUT_OF_SPACE:
errStr = tr("CSync: No space on %1 server available.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_STATUS_QUOTA_EXCEEDED:
errStr = tr("CSync: No space on %1 server available.").arg(Theme::instance()->appNameGUI());
break;
case CSYNC_STATUS_UNSUCCESSFUL:
errStr = tr("CSync unspecified error.");
break;
case CSYNC_STATUS_ABORTED:
errStr = tr("Aborted by the user");
break;
case CSYNC_ERR_UNSPEC:
errStr = tr("CSync: unspecified error.");
default:
errStr = tr("An internal error number %1 happend.").arg( (int) err );
}
if( errString ) {
errStr += tr("<br/>Backend Message: ")+QString::fromUtf8(errString);
}
return errStr;
}
bool CSyncThread::checkBlacklisting( SyncFileItem *item )
{
bool re = false;
if( !_journal ) {
qWarning() << "Journal is undefined!";
return false;
}
SyncJournalBlacklistRecord entry = _journal->blacklistEntry(item->_file);
item->_blacklistedInDb = false;
// if there is a valid entry in the blacklist table and the retry count is
// already null or smaller than 0, the file is blacklisted.
if( entry.isValid() ) {
item->_blacklistedInDb = true;
if( entry._retryCount <= 0 ) {
re = true;
}
// if the retryCount is 0, but the etag for downloads or the mtime for uploads
// has changed, it is tried again
// note that if the retryCount is -1 we never try again.
if( entry._retryCount == 0 ) {
if( item->_dir == SyncFileItem::Up ) { // check the modtime
if(item->_modtime == 0 || entry._lastTryModtime == 0) {
re = false;
} else {
if( item->_modtime != entry._lastTryModtime ) {
re = false;
qDebug() << item->_file << " is blacklisted, but has changed mtime!";
}
}
} else {
// download, check the etag.
if( item->_etag.isEmpty() || entry._lastTryEtag.isEmpty() ) {
qDebug() << item->_file << "one ETag is empty, no blacklisting";
return false;
} else {
if( item->_etag != entry._lastTryEtag ) {
re = false;
qDebug() << item->_file << " is blacklisted, but has changed etag!";
}
}
}
}
if( re ) {
qDebug() << "Item is on blacklist: " << entry._file << "retries:" << entry._retryCount;
item->_instruction = CSYNC_INSTRUCTION_IGNORE;
item->_errorString = tr("The item is not synced because of previous errors.");
slotProgress( Progress::SoftError, *item );
}
}
return re;
}
int CSyncThread::treewalkLocal( TREE_WALK_FILE* file, void *data )
{
return static_cast<CSyncThread*>(data)->treewalkFile( file, false );
@@ -175,53 +253,76 @@ int CSyncThread::treewalkRemote( TREE_WALK_FILE* file, void *data )
return static_cast<CSyncThread*>(data)->treewalkFile( file, true );
}
int CSyncThread::walkFinalize(TREE_WALK_FILE* file, void *data )
{
return static_cast<CSyncThread*>(data)->treewalkError( file);
}
int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
{
if( ! file ) return -1;
SyncFileItem item;
item._file = QString::fromUtf8( file->path );
item._originalFile = item._file;
item._instruction = file->instruction;
item._dir = SyncFileItem::None;
item._fileId = QString::fromUtf8(file->file_id);
// record the seen files to be able to clean the journal later
_seenFiles[item._file] = QString();
if(file->error_string) {
item._errorString = QString::fromUtf8(file->error_string);
}
item._isDirectory = file->type == CSYNC_FTW_TYPE_DIR;
item._modtime = file->modtime;
item._etag = file->etag;
item._size = file->size;
item._should_update_etag = file->should_update_etag;
SyncFileItem::Direction dir;
int re = 0;
if (file->instruction != CSYNC_INSTRUCTION_IGNORE
&& file->instruction != CSYNC_INSTRUCTION_REMOVE) {
_hasFiles = true;
}
switch(file->instruction) {
case CSYNC_INSTRUCTION_NONE:
case CSYNC_INSTRUCTION_IGNORE:
break;
case CSYNC_INSTRUCTION_NEW:
case CSYNC_INSTRUCTION_SYNC:
case CSYNC_INSTRUCTION_CONFLICT:
case CSYNC_INSTRUCTION_RENAME:
case CSYNC_INSTRUCTION_REMOVE:
_progressInfo.overall_file_count++;
_progressInfo.overall_transmission_size += file->size;
//fall trough
default:
if (!_needsUpdate)
_needsUpdate = true;
_needsUpdate = true;
}
switch(file->instruction) {
case CSYNC_INSTRUCTION_UPDATED:
// We need to update the database.
_journal->setFileRecord(SyncJournalFileRecord(item, _localPath + item._file));
item._instruction = CSYNC_INSTRUCTION_NONE;
// fall trough
case CSYNC_INSTRUCTION_NONE:
// No need to do anything.
return re;
if (item._isDirectory && remote) {
// Because we want still to update etags of directories
dir = SyncFileItem::None;
} else {
// No need to do anything.
return re;
}
break;
case CSYNC_INSTRUCTION_RENAME:
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
item._renameTarget = QString::fromUtf8( file->rename_path );
if (item._isDirectory)
_renamedFolders.insert(item._file, item._renameTarget);
break;
case CSYNC_INSTRUCTION_REMOVE:
dir = !remote ? SyncFileItem::Down : SyncFileItem::Up;
break;
break;
case CSYNC_INSTRUCTION_CONFLICT:
case CSYNC_INSTRUCTION_IGNORE:
case CSYNC_INSTRUCTION_ERROR:
//
slotProgress(Progress::SoftError, item, 0, 0);
dir = SyncFileItem::None;
break;
case CSYNC_INSTRUCTION_EVAL:
@@ -229,7 +330,6 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
case CSYNC_INSTRUCTION_SYNC:
case CSYNC_INSTRUCTION_STAT_ERROR:
case CSYNC_INSTRUCTION_DELETED:
case CSYNC_INSTRUCTION_UPDATED:
default:
dir = remote ? SyncFileItem::Down : SyncFileItem::Up;
break;
@@ -250,65 +350,48 @@ int CSyncThread::treewalkFile( TREE_WALK_FILE *file, bool remote )
}
item._dir = dir;
_mutex.lock();
// check for blacklisting of this item.
// if the item is on blacklist, the instruction was set to IGNORE
checkBlacklisting( &item );
if (file->instruction != CSYNC_INSTRUCTION_IGNORE
&& file->instruction != CSYNC_INSTRUCTION_REMOVE) {
_hasFiles = true;
}
_syncedItems.append(item);
_mutex.unlock();
return re;
}
int CSyncThread::treewalkError(TREE_WALK_FILE* file)
{
SyncFileItem item; // only used for search.
item._file= QString::fromUtf8(file->path);
int indx = _syncedItems.indexOf(item);
if ( indx == -1 )
return 0;
if( file &&
(file->instruction == CSYNC_INSTRUCTION_STAT_ERROR ||
file->instruction == CSYNC_INSTRUCTION_ERROR) ) {
_mutex.lock();
_syncedItems[indx]._instruction = file->instruction;
_syncedItems[indx]._errorString = QString::fromUtf8(file->error_string);
_mutex.unlock();
}
return 0;
}
struct CSyncRunScopeHelper {
CSyncRunScopeHelper(CSYNC *ctx, CSyncThread *parent)
: _ctx(ctx), _parent(parent)
{
_t.start();
}
~CSyncRunScopeHelper() {
csync_commit(_ctx);
qDebug() << "CSync run took " << _t.elapsed() << " Milliseconds";
emit(_parent->finished());
_parent->_syncMutex.unlock();
}
CSYNC *_ctx;
QTime _t;
CSyncThread *_parent;
};
void CSyncThread::handleSyncError(CSYNC *ctx, const char *state) {
CSYNC_ERROR_CODE err = csync_get_error( ctx );
const char *errMsg = csync_get_error_string( ctx );
QString errStr = csyncErrorToString(err, errMsg);
CSYNC_STATUS err = csync_get_status( ctx );
const char *errMsg = csync_get_status_string( ctx );
QString errStr = csyncErrorToString(err);
if( errMsg ) {
if( !errStr.endsWith(" ")) {
errStr.append(" ");
}
errStr += QString::fromUtf8(errMsg);
}
// if there is csyncs url modifier in the error message, replace it.
if( errStr.contains("ownclouds://") ) errStr.replace("ownclouds://", "https://");
if( errStr.contains("owncloud://") ) errStr.replace("owncloud://", "http://");
qDebug() << " #### ERROR during "<< state << ": " << errStr;
switch (err) {
case CSYNC_ERR_SERVICE_UNAVAILABLE:
case CSYNC_ERR_CONNECT:
if( CSYNC_STATUS_IS_EQUAL( err, CSYNC_STATUS_ABORTED) ) {
qDebug() << "Update phase was aborted by user!";
} else if( CSYNC_STATUS_IS_EQUAL( err, CSYNC_STATUS_SERVICE_UNAVAILABLE ) ||
CSYNC_STATUS_IS_EQUAL( err, CSYNC_STATUS_CONNECT_ERROR )) {
emit csyncUnavailable();
break;
default:
} else {
emit csyncError(errStr);
}
csync_commit(_csync_ctx);
emit finished();
_syncMutex.unlock();
thread()->quit();
}
void CSyncThread::startSync()
@@ -323,44 +406,63 @@ void CSyncThread::startSync()
}
qDebug() << Q_FUNC_INFO << "Sync started";
qDebug() << "starting to sync " << qApp->thread() << QThread::currentThread();
_syncedItems.clear();
_mutex.lock();
_syncedItems.clear();
_needsUpdate = false;
if (!_abortRequested.fetchAndAddRelease(0)) {
csync_resume(_csync_ctx);
}
_mutex.unlock();
// cleans up behind us and emits finished() to ease error handling
CSyncRunScopeHelper helper(_csync_ctx, this);
// maybe move this somewhere else where it can influence a running sync?
MirallConfigFile cfg;
int downloadLimit = 0;
if (cfg.useDownloadLimit()) {
downloadLimit = cfg.downloadLimit() * 1000;
}
csync_set_module_property(_csync_ctx, "bandwidth_limit_download", &downloadLimit);
if (!_journal->exists()) {
qDebug() << "=====sync looks new (no DB exists), activating recursive PROPFIND if csync supports it";
bool no_recursive_propfind = false;
csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind);
} else {
// retrieve the file count from the db and close it afterwards because
// csync_update also opens the database.
int fileRecordCount = 0;
fileRecordCount = _journal->getFileRecordCount();
_journal->close();
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
csync_set_module_property(_csync_ctx, "bandwidth_limit_upload", &uploadLimit);
if( fileRecordCount == -1 ) {
qDebug() << "No way to create a sync journal!";
emit csyncError(tr("Unable to initialize a sync journal."));
csync_set_progress_callback( _csync_ctx, cb_progress );
csync_commit(_csync_ctx);
emit finished();
_syncMutex.unlock();
thread()->quit();
return;
// database creation error!
} else if ( fileRecordCount < 50 ) {
qDebug() << "=====sync DB has only" << fileRecordCount << "items, enable recursive PROPFIND if csync supports it";
bool no_recursive_propfind = false;
csync_set_module_property(_csync_ctx, "no_recursive_propfind", &no_recursive_propfind);
} else {
qDebug() << "=====sync with existing DB";
}
}
csync_set_module_property(_csync_ctx, "csync_context", _csync_ctx);
csync_set_userdata(_csync_ctx, this);
// TODO: This should be a part of this method, but we don't have
// any way to get "session_key" module property from csync. Had we
// have it, then we could keep this code and remove it from
// AbstractCredentials implementations.
cfg.getCredentials()->syncContextPreStart(_csync_ctx);
if (Account *account = AccountManager::instance()->account()) {
account->credentials()->syncContextPreStart(_csync_ctx);
} else {
qDebug() << Q_FUNC_INFO << "No default Account object, huh?";
}
// if (_lastAuthCookies.length() > 0) {
// // Stuff cookies inside csync, then we can avoid the intermediate HTTP 401 reply
// // when https://github.com/owncloud/core/pull/4042 is merged.
@@ -375,23 +477,34 @@ void CSyncThread::startSync()
// }
// csync_set_auth_callback( _csync_ctx, getauth );
csync_set_log_callback( csyncLogCatcher );
csync_set_log_level( 11 );
_syncTime.start();
QElapsedTimer updateTime;
updateTime.start();
qDebug() << "#### Update start #################################################### >>";
if( csync_update(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "csync_update");
return;
}
qDebug() << "<<#### Update end ###########################################################";
qDebug() << "<<#### Update end #################################################### " << updateTime.elapsed();
if( csync_reconcile(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "csync_reconcile");
return;
}
slotProgress(Progress::StartSync, SyncFileItem(), 0, 0);
_progressInfo = Progress::Info();
_hasFiles = false;
bool walkOk = true;
_seenFiles.clear();
if( csync_walk_local_tree(_csync_ctx, &treewalkLocal, 0) < 0 ) {
qDebug() << "Error in local treewalk.";
walkOk = false;
@@ -400,9 +513,15 @@ void CSyncThread::startSync()
qDebug() << "Error in remote treewalk.";
}
// Adjust the paths for the renames.
for (SyncFileItemVector::iterator it = _syncedItems.begin();
it != _syncedItems.end(); ++it) {
it->_file = adjustRenamedPath(it->_file);
}
if (!_hasFiles && !_syncedItems.isEmpty()) {
qDebug() << Q_FUNC_INFO << "All the files are going to be removed, asking the user";
bool cancel = true;
bool cancel = false;
emit aboutToRemoveAllFiles(_syncedItems.first()._dir, &cancel);
if (cancel) {
qDebug() << Q_FUNC_INFO << "Abort sync";
@@ -413,95 +532,152 @@ void CSyncThread::startSync()
if (_needsUpdate)
emit(started());
if( csync_propagate(_csync_ctx) < 0 ) {
handleSyncError(_csync_ctx, "cysnc_reconcile");
ne_session_s *session = 0;
// that call to set property actually is a get which will return the session
// FIXME add a csync_get_module_property to csync
csync_set_module_property(_csync_ctx, "get_dav_session", &session);
Q_ASSERT(session);
_propagator.reset(new OwncloudPropagator (session, _localPath, _remotePath,
_journal, &_abortRequested));
connect(_propagator.data(), SIGNAL(completed(SyncFileItem)),
this, SLOT(transferCompleted(SyncFileItem)), Qt::QueuedConnection);
connect(_propagator.data(), SIGNAL(progress(Progress::Kind,SyncFileItem,quint64,quint64)),
this, SLOT(slotProgress(Progress::Kind,SyncFileItem,quint64,quint64)));
connect(_propagator.data(), SIGNAL(finished()), this, SLOT(slotFinished()));
int downloadLimit = 0;
if (cfg.useDownloadLimit()) {
downloadLimit = cfg.downloadLimit() * 1000;
}
_propagator->_downloadLimit = downloadLimit;
int uploadLimit = -75; // 75%
int useUpLimit = cfg.useUploadLimit();
if ( useUpLimit >= 1) {
uploadLimit = cfg.uploadLimit() * 1000;
} else if (useUpLimit == 0) {
uploadLimit = 0;
}
_propagator->_uploadLimit = uploadLimit;
_propagator->start(_syncedItems);
}
void CSyncThread::transferCompleted(const SyncFileItem &item)
{
qDebug() << Q_FUNC_INFO << item._file << item._status << item._errorString;
/* Update the _syncedItems vector */
int idx = _syncedItems.indexOf(item);
if (idx >= 0) {
_syncedItems[idx]._instruction = item._instruction;
_syncedItems[idx]._errorString = item._errorString;
_syncedItems[idx]._status = item._status;
} else {
qWarning() << Q_FUNC_INFO << "Could not find index in synced items!";
}
if (item._status == SyncFileItem::FatalError) {
emit csyncError(item._errorString);
}
}
void CSyncThread::slotFinished()
{
// emit the treewalk results.
if( ! _journal->postSyncCleanup( _seenFiles ) ) {
qDebug() << "Cleaning of synced ";
}
_journal->commit("All Finished.", false);
emit treeWalkResult(_syncedItems);
csync_commit(_csync_ctx);
qDebug() << "CSync run took " << _syncTime.elapsed() << " Milliseconds";
slotProgress(Progress::EndSync,SyncFileItem(), 0 , 0);
emit finished();
_propagator.reset(0);
_syncMutex.unlock();
thread()->quit();
}
void CSyncThread::progressProblem(Progress::Kind kind, const SyncFileItem& item)
{
Progress::SyncProblem problem;
problem.kind = kind;
problem.current_file = item._file;
problem.error_message = item._errorString;
problem.error_code = item._httpErrorCode;
problem.timestamp = QDateTime::currentDateTime();
// connected to something in folder.
emit transmissionProblem( problem );
}
void CSyncThread::slotProgress(Progress::Kind kind, const SyncFileItem& item, quint64 curr, quint64 total)
{
if( Progress::isErrorKind(kind) ) {
progressProblem(kind, item);
return;
}
if( walkOk ) {
if( csync_walk_local_tree(_csync_ctx, &walkFinalize, 0) < 0 ||
csync_walk_remote_tree(_csync_ctx, &walkFinalize, 0 ) < 0 ) {
qDebug() << "Error in finalize treewalk.";
} else {
// emit the treewalk results.
emit treeWalkResult(_syncedItems);
if( kind == Progress::StartSync ) {
QMutexLocker lock(&_mutex);
_currentFileNo = 0;
_lastOverallBytes = 0;
}
if( kind == Progress::StartDelete ||
kind == Progress::StartDownload ||
kind == Progress::StartRename ||
kind == Progress::StartUpload ) {
QMutexLocker lock(&_mutex);
_currentFileNo += 1;
}
if( kind == Progress::EndUpload ||
kind == Progress::EndDownload ||
kind == Progress::EndRename ||
kind == Progress::EndDelete ) {
QMutexLocker lock(&_mutex);
_lastOverallBytes += total;
curr = 0;
}
Progress::Info pInfo(_progressInfo);
pInfo.kind = kind;
pInfo.current_file = item._file;
pInfo.file_size = total;
pInfo.current_file_bytes = curr;
pInfo.current_file_no = _currentFileNo;
pInfo.timestamp = QDateTime::currentDateTime();
pInfo.overall_current_bytes = _lastOverallBytes + curr;
// Connect to something in folder!
emit transmissionProgress( pInfo );
}
/* Given a path on the remote, give the path as it is when the rename is done */
QString CSyncThread::adjustRenamedPath(const QString& original)
{
int slashPos = original.size();
while ((slashPos = original.lastIndexOf('/' , slashPos - 1)) > 0) {
QHash< QString, QString >::const_iterator it = _renamedFolders.constFind(original.left(slashPos));
if (it != _renamedFolders.constEnd()) {
return *it + original.mid(slashPos);
}
}
qDebug() << Q_FUNC_INFO << "Sync finished";
return original;
}
Progress::Kind CSyncThread::csyncToProgressKind( enum csync_notify_type_e kind )
void CSyncThread::abort()
{
Progress::Kind pKind = Progress::Invalid;
switch(kind) {
case CSYNC_NOTIFY_INVALID:
pKind = Progress::Invalid;
break;
case CSYNC_NOTIFY_START_SYNC_SEQUENCE:
pKind = Progress::StartSync;
break;
case CSYNC_NOTIFY_START_DOWNLOAD:
pKind = Progress::StartDownload;
break;
case CSYNC_NOTIFY_START_UPLOAD:
pKind = Progress::StartUpload;
break;
case CSYNC_NOTIFY_PROGRESS:
pKind = Progress::Context;
break;
case CSYNC_NOTIFY_FINISHED_DOWNLOAD:
pKind = Progress::EndDownload;
break;
case CSYNC_NOTIFY_FINISHED_UPLOAD:
pKind = Progress::EndUpload;
break;
case CSYNC_NOTIFY_FINISHED_SYNC_SEQUENCE:
pKind = Progress::EndSync;
break;
case CSYNC_NOTIFY_START_DELETE:
pKind = Progress::StartDelete;
break;
case CSYNC_NOTIFY_END_DELETE:
pKind = Progress::EndDelete;
break;
case CSYNC_NOTIFY_ERROR:
pKind = Progress::Error;
break;
default:
pKind = Progress::Invalid;
break;
}
return pKind;
QMutexLocker locker(&_mutex);
csync_request_abort(_csync_ctx);
_abortRequested = true;
}
void CSyncThread::cb_progress( CSYNC_PROGRESS *progress, void *userdata )
{
if( !progress ) {
qDebug() << "No progress block in progress callback found!";
return;
}
if( !userdata ) {
qDebug() << "No thread given in progress callback!";
return;
}
Progress::Info pInfo;
CSyncThread *thread = static_cast<CSyncThread*>(userdata);
pInfo.kind = thread->csyncToProgressKind( progress->kind );
pInfo.current_file = QUrl::fromEncoded( progress->path ).toString();
pInfo.file_size = progress->file_size;
pInfo.current_file_bytes = progress->curr_bytes;
pInfo.overall_file_count = progress->overall_file_count;
pInfo.current_file_no = progress->current_file_no;
pInfo.overall_transmission_size = progress->overall_transmission_size;
pInfo.overall_current_bytes = progress->current_overall_bytes;
pInfo.timestamp = QDateTime::currentDateTime();
// Connect to something in folder!
thread->transmissionProgress( pInfo );
}
} // ns Mirall

View File

@@ -21,6 +21,8 @@
#include <QMutex>
#include <QThread>
#include <QString>
#include <qelapsedtimer.h>
#include <qstack.h>
#include <QNetworkProxy>
#include <QNetworkCookie>
@@ -33,26 +35,41 @@ class QProcess;
namespace Mirall {
class SyncJournalFileRecord;
class SyncJournalDb;
class OwncloudPropagator;
void csyncLogCatcher(int /*verbosity*/,
const char */*function*/,
const char *buffer,
void */*userdata*/);
class CSyncThread : public QObject
{
Q_OBJECT
public:
CSyncThread(CSYNC *);
CSyncThread(CSYNC *, const QString &localPath, const QString &remotePath, SyncJournalDb *journal);
~CSyncThread();
static QString csyncErrorToString( CSYNC_ERROR_CODE, const char * );
static QString csyncErrorToString( CSYNC_STATUS);
Q_INVOKABLE void startSync();
/* Abort the sync. Called from the main thread */
void abort();
signals:
void fileReceived( const QString& );
void fileRemoved( const QString& );
void csyncError( const QString& );
void csyncWarning( const QString& );
void csyncUnavailable();
void treeWalkResult(const SyncFileItemVector&);
void transmissionProgress( const Progress::Info& progress );
void transmissionProblem( const Progress::SyncProblem& problem );
void csyncStateDbFile( const QString& );
void wipeDb();
@@ -61,20 +78,19 @@ signals:
void aboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *cancel);
private slots:
void transferCompleted(const SyncFileItem& item);
void slotFinished();
void slotProgress(Progress::Kind kind, const SyncFileItem &item, quint64 curr = 0, quint64 total = 0);
private:
void handleSyncError(CSYNC *ctx, const char *state);
static void cb_progress( CSYNC_PROGRESS *progress, void *userdata );
void progressProblem(Progress::Kind kind, const SyncFileItem& item);
static int treewalkLocal( TREE_WALK_FILE*, void *);
static int treewalkRemote( TREE_WALK_FILE*, void *);
int treewalkFile( TREE_WALK_FILE*, bool );
int treewalkError( TREE_WALK_FILE* );
Progress::Kind csyncToProgressKind( enum csync_notify_type_e kind );
static int walkFinalize(TREE_WALK_FILE*, void* );
bool checkBlacklisting( SyncFileItem *item );
static QMutex _mutex;
static QMutex _syncMutex;
@@ -82,8 +98,28 @@ private:
CSYNC *_csync_ctx;
bool _needsUpdate;
QString _localPath;
QString _remotePath;
SyncJournalDb *_journal;
QScopedPointer <OwncloudPropagator> _propagator;
QElapsedTimer _syncTime;
QString _lastDeleted; // if the last item was a path and it has been deleted
QHash <QString, QString> _seenFiles;
// maps the origin and the target of the folders that have been renamed
QHash<QString, QString> _renamedFolders;
QString adjustRenamedPath(const QString &original);
bool _hasFiles; // true if there is at least one file that is not ignored or removed
Progress::Info _progressInfo;
int _downloadLimit;
int _uploadLimit;
qint64 _currentFileNo;
qint64 _overallFileCount;
quint64 _lastOverallBytes;
QAtomicInt _abortRequested;
friend struct CSyncRunScopeHelper;
};

View File

@@ -15,16 +15,35 @@
*/
#include "config.h"
#include "mirall/account.h"
#include "mirall/folder.h"
#include "mirall/folderman.h"
#include "mirall/folderwatcher.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/syncresult.h"
#include "mirall/logger.h"
#include "mirall/owncloudinfo.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/networkjobs.h"
#include "mirall/syncjournalfilerecord.h"
#include "mirall/syncresult.h"
#include "mirall/utility.h"
#include "folderman.h"
#include "creds/abstractcredentials.h"
extern "C" {
enum csync_exclude_type_e {
CSYNC_NOT_EXCLUDED = 0,
CSYNC_FILE_SILENTLY_EXCLUDED,
CSYNC_FILE_EXCLUDE_AND_REMOVE,
CSYNC_FILE_EXCLUDE_LIST,
CSYNC_FILE_EXCLUDE_INVALID_CHAR
};
typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE;
CSYNC_EXCLUDE_TYPE csync_excluded(CSYNC *ctx, const char *path, int filetype);
}
#include <QDebug>
#include <QTimer>
#include <QUrl>
@@ -35,25 +54,19 @@
namespace Mirall {
void csyncLogCatcher(CSYNC */*ctx*/,
int /*verbosity*/,
const char */*function*/,
const char *buffer,
void */*userdata*/)
{
Logger::instance()->csyncLog( QString::fromUtf8(buffer) );
}
Folder::Folder(const QString &alias, const QString &path, const QString& secondPath, QObject *parent)
: QObject(parent)
, _path(path)
, _secondPath(secondPath)
, _remotePath(secondPath)
, _alias(alias)
, _enabled(true)
, _thread(0)
, _csync(0)
, _csyncError(false)
, _csyncUnavail(false)
, _wipeDb(false)
, _proxyDirty(true)
, _journal(path)
, _csync_ctx(0)
{
qsrand(QTime::currentTime().msec());
@@ -84,7 +97,7 @@ Folder::Folder(const QString &alias, const QString &path, const QString& secondP
bool Folder::init()
{
QString url = Utility::toCSyncScheme(ownCloudInfo::instance()->webdavUrl() + secondPath());
QString url = Utility::toCSyncScheme(remoteUrl().toString());
QString localpath = path();
if( csync_create( &_csync_ctx, localpath.toUtf8().data(), url.toUtf8().data() ) < 0 ) {
@@ -92,30 +105,40 @@ bool Folder::init()
slotCSyncError(tr("Unable to create csync-context"));
_csync_ctx = 0;
} else {
csync_set_log_callback( _csync_ctx, csyncLogCatcher );
csync_set_log_verbosity(_csync_ctx, 11);
csync_set_log_callback( csyncLogCatcher );
csync_set_log_level( 11 );
MirallConfigFile cfgFile;
csync_set_config_dir( _csync_ctx, cfgFile.configPath().toUtf8() );
csync_enable_conflictcopys(_csync_ctx);
setIgnoredFiles();
cfgFile.getCredentials()->syncContextPreInit(_csync_ctx);
if (Account *account = AccountManager::instance()->account()) {
account->credentials()->syncContextPreInit(_csync_ctx);
} else {
qDebug() << Q_FUNC_INFO << "No default Account object, huh?";
}
if( csync_init( _csync_ctx ) < 0 ) {
qDebug() << "Could not initialize csync!" << csync_get_error(_csync_ctx) << csync_get_error_string(_csync_ctx);
slotCSyncError(CSyncThread::csyncErrorToString(csync_get_error(_csync_ctx), csync_get_error_string(_csync_ctx)));
qDebug() << "Could not initialize csync!" << csync_get_status(_csync_ctx) << csync_get_status_string(_csync_ctx);
QString errStr = CSyncThread::csyncErrorToString(CSYNC_STATUS(csync_get_status(_csync_ctx)));
const char *errMsg = csync_get_status_string(_csync_ctx);
if( errMsg ) {
errStr += QLatin1String("<br/>");
errStr += QString::fromUtf8(errMsg);
}
slotCSyncError(errStr);
csync_destroy(_csync_ctx);
_csync_ctx = 0;
}
}
return _csync_ctx;
}
Folder::~Folder()
{
if( _thread ) {
_thread->quit();
csync_request_abort(_csync_ctx);
_csync->abort();
_thread->wait();
}
delete _csync;
@@ -178,9 +201,23 @@ bool Folder::isBusy() const
return ( _thread && _thread->isRunning() );
}
QString Folder::secondPath() const
QString Folder::remotePath() const
{
return _secondPath;
return _remotePath;
}
QUrl Folder::remoteUrl() const
{
Account *account = AccountManager::instance()->account();
QUrl url = account->davUrl();
QString path = url.path();
if (!path.endsWith('/')) {
path.append('/');
}
path.append(_remotePath);
url.setPath(path);
qDebug() << url;
return url;
}
QString Folder::nativePath() const
@@ -196,15 +233,13 @@ bool Folder::syncEnabled() const
void Folder::setSyncEnabled( bool doit )
{
_enabled = doit;
_watcher->setEventsEnabled( doit );
qDebug() << "setSyncEnabled - ############################ " << doit;
if( doit ) {
// undefined until next sync
_syncResult.setStatus( SyncResult::NotYetStarted);
_syncResult.clearErrors();
evaluateSync( QStringList() );
// qDebug() << "Syncing enabled on folder " << name();
} else {
// do not stop or start the watcher here, that is done internally by
// folder class. Even if the watcher fires, the folder does not
// schedule itself because it checks the var. _enabled before.
_pollTimer.stop();
}
}
@@ -227,22 +262,25 @@ void Folder::evaluateSync(const QStringList &/*pathList*/)
}
_syncResult.setStatus( SyncResult::NotYetStarted );
_syncResult.clearErrors();
emit scheduleToSync( alias() );
}
void Folder::slotPollTimerTimeout()
{
qDebug() << "* Polling" << alias() << "for changes. (time since next sync:" << (_timeSinceLastSync.elapsed() / 1000) << "s)";
qDebug() << "* Polling" << alias() << "for changes. (time since last sync:" << (_timeSinceLastSync.elapsed() / 1000) << "s)";
if (quint64(_timeSinceLastSync.elapsed()) > MirallConfigFile().forceSyncInterval()) {
qDebug() << "* Force Sync now";
if (quint64(_timeSinceLastSync.elapsed()) > MirallConfigFile().forceSyncInterval() ||
_syncResult.status() != SyncResult::Success ) {
qDebug() << "** Force Sync now";
evaluateSync(QStringList());
} else {
RequestEtagJob* job = new RequestEtagJob(secondPath(), this);
RequestEtagJob* job = new RequestEtagJob(AccountManager::instance()->account(), remotePath(), this);
// check if the etag is different
QObject::connect(job, SIGNAL(etagRetreived(QString)), this, SLOT(etagRetreived(QString)));
QObject::connect(job, SIGNAL(networkError()), this, SLOT(slotNetworkUnavailable()));
QObject::connect(job, SIGNAL(networkError(QNetworkReply*)), this, SLOT(slotNetworkUnavailable()));
job->start();
}
}
@@ -261,6 +299,7 @@ void Folder::etagRetreived(const QString& etag)
void Folder::slotNetworkUnavailable()
{
AccountManager::instance()->account()->setState(Account::Disconnected);
_syncResult.setStatus(SyncResult::Unavailable);
emit syncStateChange();
}
@@ -278,17 +317,18 @@ void Folder::bubbleUpSyncResult()
int removedItems = 0;
int updatedItems = 0;
int ignoredItems = 0;
int renamedItems = 0;
SyncFileItem firstItemNew;
SyncFileItem firstItemDeleted;
SyncFileItem firstItemUpdated;
SyncFileItem firstItemRenamed;
Logger *logger = Logger::instance();
foreach (const SyncFileItem &item, _syncResult.syncFileItemVector() ) {
if( item._instruction == CSYNC_INSTRUCTION_ERROR ) {
if( item._status == SyncFileItem::FatalError || item._status == SyncFileItem::NormalError ) {
slotCSyncError( tr("File %1: %2").arg(item._file).arg(item._errorString) );
logger->postGuiLog(tr("File %1").arg(item._file), item._errorString);
logger->postOptionalGuiLog(tr("File %1").arg(item._file), item._errorString);
} else {
if (item._dir == SyncFileItem::Down) {
@@ -313,7 +353,8 @@ void Folder::bubbleUpSyncResult()
}
break;
case CSYNC_INSTRUCTION_UPDATED:
case CSYNC_INSTRUCTION_CONFLICT:
case CSYNC_INSTRUCTION_SYNC:
updatedItems++;
if (firstItemUpdated.isEmpty())
firstItemUpdated = item;
@@ -321,6 +362,12 @@ void Folder::bubbleUpSyncResult()
case CSYNC_INSTRUCTION_ERROR:
qDebug() << "Got Instruction ERROR. " << _syncResult.errorString();
break;
case CSYNC_INSTRUCTION_RENAME:
if (firstItemRenamed.isEmpty()) {
firstItemRenamed = item;
}
renamedItems++;
break;
default:
// nothing.
break;
@@ -335,33 +382,50 @@ void Folder::bubbleUpSyncResult()
_syncResult.setWarnCount(ignoredItems);
createGuiLog( firstItemNew._file, tr("downloaded"), newItems );
createGuiLog( firstItemDeleted._file, tr("removed"), removedItems );
createGuiLog( firstItemUpdated._file, tr("updated"), updatedItems );
if( !firstItemRenamed.isEmpty() ) {
QString renameVerb = tr("renamed");
// if the path changes it's rather a move
QDir renTarget = QFileInfo(firstItemRenamed._renameTarget).dir();
QDir renSource = QFileInfo(firstItemRenamed._file).dir();
if(renTarget != renSource) {
renameVerb = tr("moved");
}
createGuiLog( firstItemRenamed._file, tr("%1 to %2").arg(renameVerb).arg(firstItemRenamed._renameTarget), renamedItems );
}
qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status());
if (newItems > 0) {
QString file = QDir::toNativeSeparators(firstItemNew._file);
if (newItems == 1)
logger->postGuiLog(tr("New file available"), tr("'%1' has been synced to this machine.").arg(file));
else
logger->postGuiLog(tr("New files available"), tr("'%1' and %n other file(s) have been synced to this machine.",
"", newItems-1).arg(file));
}
if (removedItems > 0) {
QString file = QDir::toNativeSeparators(firstItemDeleted._file);
if (removedItems == 1)
logger->postGuiLog(tr("File removed"), tr("'%1' has been removed.").arg(file));
else
logger->postGuiLog(tr("Files removed"), tr("'%1' and %n other file(s) have been removed.",
"", removedItems-1).arg(file));
}
if (updatedItems > 0) {
QString file = QDir::toNativeSeparators(firstItemUpdated._file);
if (updatedItems == 1)
logger->postGuiLog(tr("File updated"), tr("'%1' has been updated.").arg(file));
else
logger->postGuiLog(tr("Files updated"), tr("'%1' and %n other file(s) have been updated.",
"", updatedItems-1).arg(file));
}
void Folder::createGuiLog( const QString& filename, const QString& verb, int count )
{
if(count > 0) {
Logger *logger = Logger::instance();
QString file = QDir::toNativeSeparators(filename);
if (count == 1) {
logger->postOptionalGuiLog(tr("File %1").arg(verb), tr("'%1' has been %2.").arg(file).arg(verb));
} else {
logger->postOptionalGuiLog(tr("Files %1").arg(verb),
tr("'%1' and %2 other files have been %3.").arg(file).arg(count-1).arg(verb));
}
}
}
int Folder::blackListEntryCount()
{
return _journal.blackListEntryCount();
}
int Folder::slotWipeBlacklist()
{
return _journal.wipeBlacklist();
}
void Folder::slotLocalPathChanged( const QString& dir )
{
QDir notifiedDir(dir);
@@ -399,39 +463,31 @@ void Folder::slotThreadTreeWalkResult(const SyncFileItemVector& items)
void Folder::slotCatchWatcherError(const QString& error)
{
Logger::instance()->postGuiLog(tr("Error"), error);
Logger::instance()->postOptionalGuiLog(tr("Error"), error);
}
void Folder::slotTerminateSync()
void Folder::slotTerminateSync(bool block)
{
qDebug() << "folder " << alias() << " Terminating!";
MirallConfigFile cfg;
QString configDir = cfg.configPath();
qDebug() << "csync's Config Dir: " << configDir;
if( _thread && _csync ) {
csync_request_abort(_csync_ctx);
_thread->quit();
_csync->abort();
// Do not display an error message, user knows his own actions.
// _errors.append( tr("The CSync thread terminated.") );
// _csyncError = true;
if (!block) {
setSyncState(SyncResult::SyncAbortRequested);
return;
}
_thread->wait();
_csync->deleteLater();
delete _thread;
_csync = 0;
_thread = 0;
csync_resume(_csync_ctx);
slotCSyncFinished();
}
if( ! configDir.isEmpty() ) {
QFile file( configDir + QLatin1String("/lock"));
if( file.exists() ) {
qDebug() << "After termination, lock file exists and gets removed.";
file.remove();
}
}
_errors.append( tr("The CSync thread terminated.") );
_csyncError = true;
qDebug() << "-> CSync Terminated!";
slotCSyncFinished();
setSyncEnabled(false);
}
// This removes the csync File database if the sync folder definition is removed
@@ -478,31 +534,37 @@ void Folder::setIgnoredFiles()
void Folder::setProxy()
{
if( _csync_ctx ) {
/* Store proxy */
QUrl proxyUrl(ownCloudInfo::instance()->webdavUrl());
QList<QNetworkProxy> proxies = QNetworkProxyFactory::proxyForQuery(proxyUrl);
// We set at least one in Application
Q_ASSERT(proxies.count() > 0);
QNetworkProxy proxy = proxies.first();
if (proxy.type() == QNetworkProxy::NoProxy) {
qDebug() << "Passing NO proxy to csync for" << proxyUrl;
} else {
qDebug() << "Passing" << proxy.hostName() << "of proxy type " << proxy.type()
<< " to csync for" << proxyUrl;
}
int proxyPort = proxy.port();
csync_set_module_property(_csync_ctx, "proxy_type", (char*) proxyTypeToCStr(proxy.type()) );
csync_set_module_property(_csync_ctx, "proxy_host", proxy.hostName().toUtf8().data() );
csync_set_module_property(_csync_ctx, "proxy_port", &proxyPort );
csync_set_module_property(_csync_ctx, "proxy_user", proxy.user().toUtf8().data() );
csync_set_module_property(_csync_ctx, "proxy_pwd" , proxy.password().toUtf8().data() );
FolderMan::instance()->setDirtyProxy(false);
/* Store proxy */
QUrl proxyUrl(AccountManager::instance()->account()->url());
QList<QNetworkProxy> proxies = QNetworkProxyFactory::proxyForQuery(QNetworkProxyQuery(proxyUrl));
// We set at least one in Application
Q_ASSERT(proxies.count() > 0);
QNetworkProxy proxy = proxies.first();
if (proxy.type() == QNetworkProxy::NoProxy) {
qDebug() << "Passing NO proxy to csync for" << proxyUrl;
} else {
qDebug() << "Passing" << proxy.hostName() << "of proxy type " << proxy.type()
<< " to csync for" << proxyUrl;
}
_proxy_type = proxyTypeToCStr(proxy.type());
_proxy_host = proxy.hostName().toUtf8();
_proxy_port = proxy.port();
_proxy_user = proxy.user().toUtf8();
_proxy_pwd = proxy.password().toUtf8();
setProxyDirty(false);
}
void Folder::setProxyDirty(bool value)
{
_proxyDirty = value;
}
bool Folder::proxyDirty()
{
return _proxyDirty;
}
const char* Folder::proxyTypeToCStr(QNetworkProxy::ProxyType type)
{
@@ -530,7 +592,6 @@ void Folder::startSync(const QStringList &pathList)
if (!_csync_ctx) {
// no _csync_ctx yet, initialize it.
init();
setProxy();
if (!_csync_ctx) {
qDebug() << Q_FUNC_INFO << "init failed.";
@@ -538,9 +599,15 @@ void Folder::startSync(const QStringList &pathList)
QMetaObject::invokeMethod(this, "slotCSyncFinished", Qt::QueuedConnection);
return;
}
} else if (FolderMan::instance()->isDirtyProxy()) {
setProxy();
} else if (proxyDirty()) {
setProxy();
}
csync_set_module_property(_csync_ctx, "proxy_type", const_cast<char*>(_proxy_type) );
csync_set_module_property(_csync_ctx, "proxy_host", _proxy_host.data() );
csync_set_module_property(_csync_ctx, "proxy_port", &_proxy_port );
csync_set_module_property(_csync_ctx, "proxy_user", _proxy_user.data() );
csync_set_module_property(_csync_ctx, "proxy_pwd", _proxy_pwd.data() );
if (_thread && _thread->isRunning()) {
qCritical() << "* ERROR csync is still running and new sync requested.";
@@ -561,12 +628,10 @@ void Folder::startSync(const QStringList &pathList)
qDebug() << "*** Start syncing";
_thread = new QThread(this);
_thread->setPriority(QThread::LowPriority);
setIgnoredFiles();
_csync = new CSyncThread( _csync_ctx );
_csync = new CSyncThread( _csync_ctx, path(), remoteUrl().path(), &_journal);
_csync->moveToThread(_thread);
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
@@ -582,8 +647,11 @@ void Folder::startSync(const QStringList &pathList)
connect(_csync, SIGNAL(aboutToRemoveAllFiles(SyncFileItem::Direction,bool*)),
SLOT(slotAboutToRemoveAllFiles(SyncFileItem::Direction,bool*)), Qt::BlockingQueuedConnection);
connect(_csync, SIGNAL(transmissionProgress(Progress::Info)), this, SLOT(slotTransmissionProgress(Progress::Info)));
connect(_csync, SIGNAL(transmissionProblem(Progress::SyncProblem)), this, SLOT(slotTransmissionProblem(Progress::SyncProblem)));
_thread->start();
_thread->setPriority(QThread::LowPriority);
QMetaObject::invokeMethod(_csync, "startSync", Qt::QueuedConnection);
// disable events until syncing is done
@@ -612,7 +680,7 @@ void Folder::slotCsyncUnavailable()
void Folder::slotCSyncFinished()
{
qDebug() << "-> CSync Finished slot with error " << _csyncError;
qDebug() << "-> CSync Finished slot with error " << _csyncError << "warn count" << _syncResult.warnCount();
_watcher->setEventsEnabledDelayed(2000);
_pollTimer.start();
_timeSinceLastSync.restart();
@@ -637,10 +705,35 @@ void Folder::slotCSyncFinished()
_thread->quit();
}
emit syncStateChange();
ownCloudInfo::instance()->getQuotaRequest("/");
emit syncFinished( _syncResult );
}
// the problem comes without a folder and the valid path set. Add that here
// and hand the result over to the progress dispatcher.
void Folder::slotTransmissionProblem( const Progress::SyncProblem& problem )
{
Progress::SyncProblem newProb = problem;
newProb.folder = alias();
if(newProb.current_file.startsWith(QLatin1String("ownclouds://")) ||
newProb.current_file.startsWith(QLatin1String("owncloud://")) ) {
// rip off the whole ownCloud URL.
newProb.current_file.remove(Utility::toCSyncScheme(remoteUrl().toString()));
}
QString localPath = path();
if( newProb.current_file.startsWith(localPath) ) {
// remove the local dir.
newProb.current_file = newProb.current_file.right( newProb.current_file.length() - localPath.length());
}
// Count all error conditions.
_syncResult.setWarnCount( _syncResult.warnCount()+1 );
ProgressDispatcher::instance()->setProgressProblem(alias(), newProb);
}
// the progress comes without a folder and the valid path set. Add that here
// and hand the result over to the progress dispatcher.
void Folder::slotTransmissionProgress(const Progress::Info& progress)
{
Progress::Info newInfo = progress;
@@ -649,8 +742,7 @@ void Folder::slotTransmissionProgress(const Progress::Info& progress)
if(newInfo.current_file.startsWith(QLatin1String("ownclouds://")) ||
newInfo.current_file.startsWith(QLatin1String("owncloud://")) ) {
// rip off the whole ownCloud URL.
QString remotePathUrl = ownCloudInfo::instance()->webdavUrl() + secondPath();
newInfo.current_file.remove(Utility::toCSyncScheme(remotePathUrl));
newInfo.current_file.remove(Utility::toCSyncScheme(remoteUrl().toString()));
}
QString localPath = path();
if( newInfo.current_file.startsWith(localPath) ) {
@@ -662,9 +754,6 @@ void Folder::slotTransmissionProgress(const Progress::Info& progress)
if( newInfo.kind == Progress::StartSync ) {
_syncResult.setWarnCount(0);
}
if( newInfo.kind == Progress::Error ) {
_syncResult.setWarnCount( _syncResult.warnCount()+1 );
}
ProgressDispatcher::instance()->setProgressInfo(alias(), newInfo);
}
@@ -693,6 +782,65 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction direction, bool *
}
}
SyncFileStatus Folder::fileStatus( const QString& fileName )
{
/*
STATUS_NONE,
+ STATUS_EVAL,
STATUS_REMOVE, (invalid for this case because it asks for local files)
STATUS_RENAME,
+ STATUS_NEW,
STATUS_CONFLICT,(probably also invalid as we know the conflict only with server involvement)
+ STATUS_IGNORE,
+ STATUS_SYNC,
+ STATUS_STAT_ERROR,
STATUS_ERROR,
STATUS_UPDATED
*/
// FIXME: Find a way for STATUS_ERROR
SyncFileStatus stat = FILE_STATUS_NONE;
QString file = path() + fileName;
QFileInfo fi(file);
if( !fi.exists() ) {
stat = FILE_STATUS_STAT_ERROR; // not really possible.
}
// file is ignored?
if( fi.isSymLink() ) {
stat = FILE_STATUS_IGNORE;
}
int type = CSYNC_FTW_TYPE_FILE;
if( fi.isDir() ) {
type = CSYNC_FTW_TYPE_DIR;
}
if( stat == FILE_STATUS_NONE ) {
CSYNC_EXCLUDE_TYPE excl = csync_excluded(_csync_ctx, file.toUtf8(), type);
if( excl != CSYNC_NOT_EXCLUDED ) {
stat = FILE_STATUS_IGNORE;
}
}
SyncJournalFileRecord rec = _journal.getFileRecord(fileName);
if( stat == FILE_STATUS_NONE && !rec.isValid() ) {
stat = FILE_STATUS_NEW;
}
// file was locally modified.
if( stat == FILE_STATUS_NONE && fi.lastModified() != rec._modtime ) {
stat = FILE_STATUS_EVAL;
}
if( stat == FILE_STATUS_NONE ) {
stat = FILE_STATUS_SYNC;
}
return stat;
}
} // namespace Mirall

View File

@@ -20,6 +20,7 @@
#include "mirall/syncresult.h"
#include "mirall/progressdispatcher.h"
#include "mirall/csyncthread.h"
#include "mirall/syncjournaldb.h"
#include <QDir>
#include <QHash>
@@ -40,28 +41,27 @@ namespace Mirall {
class FolderWatcher;
typedef enum SyncFileStatus_s {
STATUS_NONE,
STATUS_EVAL,
STATUS_REMOVE,
STATUS_RENAME,
STATUS_NEW,
STATUS_CONFLICT,
STATUS_IGNORE,
STATUS_SYNC,
STATUS_STAT_ERROR,
STATUS_ERROR,
STATUS_UPDATED
FILE_STATUS_NONE,
FILE_STATUS_EVAL,
FILE_STATUS_REMOVE,
FILE_STATUS_RENAME,
FILE_STATUS_NEW,
FILE_STATUS_CONFLICT,
FILE_STATUS_IGNORE,
FILE_STATUS_SYNC,
FILE_STATUS_STAT_ERROR,
FILE_STATUS_ERROR,
FILE_STATUS_UPDATED
} SyncFileStatus;
class Folder : public QObject
{
Q_OBJECT
protected:
friend class FolderMan;
public:
Folder(const QString&, const QString&, const QString& , QObject*parent = 0L);
public:
~Folder();
typedef QHash<QString, Folder*> Map;
@@ -81,10 +81,16 @@ public:
* local folder path
*/
QString path() const;
/**
* remote folder path
*/
QString secondPath() const;
QString remotePath() const;
/**
* remote folder path with server url
*/
QUrl remoteUrl() const;
/**
* local folder path with native separators
@@ -137,8 +143,10 @@ public slots:
/**
* terminate the current sync run
*
* If block is true, this will block synchroniously for the sync thread to finish.
*/
void slotTerminateSync();
void slotTerminateSync(bool block);
void slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool*);
@@ -150,6 +158,18 @@ public slots:
*/
void startSync(const QStringList &pathList = QStringList());
/**
* Starts a sync (calling startSync)
* if the policies allow for it
*/
void evaluateSync(const QStringList &pathList);
void setProxyDirty(bool value);
bool proxyDirty();
int slotWipeBlacklist();
int blackListEntryCount();
private slots:
void slotCSyncStarted();
void slotCSyncError(const QString& );
@@ -157,6 +177,7 @@ private slots:
void slotCSyncFinished();
void slotTransmissionProgress(const Progress::Info& progress);
void slotTransmissionProblem( const Progress::SyncProblem& problem );
void slotPollTimerTimeout();
void etagRetreived(const QString &);
@@ -169,7 +190,7 @@ private slots:
void slotThreadTreeWalkResult(const SyncFileItemVector& );
void slotCatchWatcherError( const QString& );
protected:
private:
bool init();
void setSyncState(SyncResult::Status state);
@@ -180,16 +201,12 @@ protected:
void bubbleUpSyncResult();
/**
* Starts a sync (calling startSync)
* if the policies allow for it
*/
void evaluateSync(const QStringList &pathList);
void checkLocalPath();
void createGuiLog( const QString& filename, const QString& verb, int count );
QString _path;
QString _secondPath;
QString _remotePath;
QString _alias;
QString _configFile;
QFileSystemWatcher *_pathWatcher;
@@ -202,13 +219,22 @@ protected:
bool _csyncError;
bool _csyncUnavail;
bool _wipeDb;
bool _proxyDirty;
Progress::Kind _progressKind;
QTimer _pollTimer;
QString _lastEtag;
QElapsedTimer _timeSinceLastSync;
SyncJournalDb _journal;
CSYNC *_csync_ctx;
const char *_proxy_type;
QByteArray _proxy_host;
int _proxy_port;
QByteArray _proxy_user;
QByteArray _proxy_pwd;
};
}

View File

@@ -18,7 +18,8 @@
#include "mirall/syncresult.h"
#include "mirall/inotify.h"
#include "mirall/theme.h"
#include "owncloudinfo.h"
#include <neon/ne_socket.h>
#ifdef Q_OS_MAC
#include <CoreServices/CoreServices.h>
@@ -37,8 +38,7 @@ FolderMan* FolderMan::_instance = 0;
FolderMan::FolderMan(QObject *parent) :
QObject(parent),
_syncEnabled( true ),
_dirtyProxy( true )
_syncEnabled( true )
{
// if QDir::mkpath would not be so stupid, I would not need to have this
// duplication of folderConfigPath() here
@@ -54,8 +54,10 @@ FolderMan::FolderMan(QObject *parent) :
FolderMan *FolderMan::instance()
{
if(!_instance)
if(!_instance) {
_instance = new FolderMan;
ne_sock_init();
}
return _instance;
}
@@ -63,6 +65,7 @@ FolderMan *FolderMan::instance()
FolderMan::~FolderMan()
{
qDeleteAll(_folderMap);
ne_sock_exit();
}
Mirall::Folder::Map FolderMan::map()
@@ -70,18 +73,6 @@ Mirall::Folder::Map FolderMan::map()
return _folderMap;
}
int FolderMan::setupFolders()
{
// setup a handler to look for configuration changes
return setupKnownFolders();
}
void FolderMan::slotReparseConfiguration()
{
setupKnownFolders();
}
int FolderMan::unloadAllFolders()
{
int cnt = 0;
@@ -96,7 +87,7 @@ int FolderMan::unloadAllFolders()
return cnt;
}
int FolderMan::setupKnownFolders()
int FolderMan::setupFolders()
{
qDebug() << "* Setup folders from " << _folderConfigPath;
@@ -226,7 +217,7 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) {
return folder;
}
QSettings settings( cfgFile.filePath(), QSettings::IniFormat);
QSettings settings( _folderConfigPath + QLatin1Char('/') + escapedAlias, QSettings::IniFormat);
qDebug() << " -> file path: " << settings.fileName();
// Check if the filename is equal to the group setting. If not, use the group
@@ -239,9 +230,10 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) {
settings.beginGroup( escapedAlias ); // read the group with the same name as the file which is the folder alias
QString path = settings.value(QLatin1String("localpath")).toString();
QString path = settings.value(QLatin1String("localPath")).toString();
QString backend = settings.value(QLatin1String("backend")).toString();
QString targetPath = settings.value( QLatin1String("targetPath") ).toString();
QString targetPath = settings.value( QLatin1String("targetPath")).toString();
bool paused = settings.value( QLatin1String("paused"), false).toBool();
// QString connection = settings.value( QLatin1String("connection") ).toString();
QString alias = unescapeAlias( escapedAlias );
@@ -259,6 +251,9 @@ Folder* FolderMan::setupFolderFromConfigFile(const QString &file) {
folder->setConfigFile(file);
qDebug() << "Adding folder to Folder Map " << folder;
_folderMap[alias] = folder;
if (paused) {
_disabledFolders.insert(folder);
}
/* Use a signal mapper to connect the signals to the alias */
connect(folder, SIGNAL(scheduleToSync(const QString&)), SLOT(slotScheduleSync(const QString&)));
@@ -280,6 +275,17 @@ void FolderMan::slotEnableFolder( const QString& alias, bool enable )
Folder *f = _folderMap[alias];
if( f ) {
f->setSyncEnabled(enable);
f->evaluateSync(QStringList());
QSettings settings(_folderConfigPath + QLatin1Char('/') + f->configFile(), QSettings::IniFormat);
settings.beginGroup(escapeAlias(f->alias()));
if (enable) {
settings.remove("paused");
_disabledFolders.remove(f);
} else {
settings.setValue("paused", true);
_disabledFolders.insert(f);
}
}
}
@@ -294,8 +300,7 @@ void FolderMan::terminateSyncProcess( const QString& alias )
if( ! folderAlias.isEmpty() ) {
Folder *f = _folderMap[folderAlias];
if( f ) {
f->slotTerminateSync();
f->slotTerminateSync(true);
if(_currentSyncFolder == folderAlias )
_currentSyncFolder.clear();
}
@@ -361,6 +366,10 @@ void FolderMan::setSyncEnabled( bool enabled )
QTimer::singleShot(200, this, SLOT(slotScheduleFolderSync()));
}
_syncEnabled = enabled;
foreach( Folder *f, _folderMap.values() ) {
f->setSyncEnabled(enabled && !_disabledFolders.contains(f));
}
}
/*
@@ -384,10 +393,9 @@ void FolderMan::slotScheduleFolderSync()
if( ! _scheduleQueue.isEmpty() ) {
const QString alias = _scheduleQueue.dequeue();
if( _folderMap.contains( alias ) ) {
ownCloudInfo::instance()->getQuotaRequest("/");
Folder *f = _folderMap[alias];
_currentSyncFolder = alias;
if (f->syncEnabled()) {
if( f->syncEnabled() ) {
_currentSyncFolder = alias;
f->startSync( QStringList() );
}
}
@@ -425,6 +433,23 @@ void FolderMan::addFolderDefinition(const QString& alias, const QString& sourceF
settings.sync();
}
Folder *FolderMan::folderForPath(const QUrl &path)
{
QString absolutePath = path.toLocalFile();
absolutePath.append("/");
foreach(Folder* folder, map().values())
{
if(absolutePath.startsWith(folder->path()))
{
qDebug() << "found folder: " << folder->path() << " for " << absolutePath;
return folder;
}
}
return 0;
}
void FolderMan::removeAllFolderDefinitions()
{
foreach( Folder *f, _folderMap.values() ) {
@@ -467,7 +492,10 @@ void FolderMan::removeFolder( const QString& alias )
qDebug() << "Remove folder config file " << file.fileName();
file.remove();
}
f->deleteLater();
// FIXME: this is a temporar dirty fix against a crash happening because
// the csync owncloud module still has static components. Activate the
// delete once the module is fixed.
// f->deleteLater();
}
}
@@ -513,6 +541,14 @@ bool FolderMan::startFromScratch( const QString& localFolder )
return false;
}
void FolderMan::setDirtyProxy(bool value)
{
foreach( Folder *f, _folderMap.values() ) {
f->setProxyDirty(value);
}
}
SyncResult FolderMan::accountStatus(const QList<Folder*> &folders)
{
SyncResult overallResult(SyncResult::Undefined);
@@ -538,6 +574,7 @@ SyncResult FolderMan::accountStatus(const QList<Folder*> &folders)
case SyncResult::Unavailable:
overallResult.setStatus( SyncResult::Unavailable );
break;
case SyncResult::Problem: // don't show the problem icon in tray.
case SyncResult::Success:
if( overallResult.status() == SyncResult::Undefined )
overallResult.setStatus( SyncResult::Success );
@@ -549,11 +586,9 @@ SyncResult FolderMan::accountStatus(const QList<Folder*> &folders)
if ( overallResult.status() != SyncResult::Error )
overallResult.setStatus( SyncResult::SetupError );
break;
case SyncResult::Problem:
if ( overallResult.status() != SyncResult::Problem )
overallResult.setStatus( SyncResult::Problem );
case SyncResult::SyncAbortRequested:
break;
// no default case on purpose, check compiler warnings
// no default case on purpose, check compiler warnings
}
}
return overallResult;
@@ -589,6 +624,9 @@ QString FolderMan::statusToString( SyncResult syncStatus, bool enabled ) const
case SyncResult::SetupError:
folderMessage = tr( "Setup Error." );
break;
case SyncResult::SyncAbortRequested:
folderMessage = tr( "User Abort." );
break;
// no default case on purpose, check compiler warnings
}
if( !enabled ) {

View File

@@ -50,6 +50,9 @@ public:
*/
void addFolderDefinition(const QString&, const QString&, const QString& );
/** Returns the folder which the file or directory stored in path is in */
Folder* folderForPath(const QUrl& path);
/** Returns the folder by alias or NULL if no folder with the alias exists. */
Folder *folder( const QString& );
@@ -96,8 +99,6 @@ public slots:
void slotFolderSyncStarted();
void slotFolderSyncFinished( const SyncResult& );
void slotReparseConfiguration();
void terminateSyncProcess( const QString& alias = QString::null );
/* delete all folder objects */
@@ -109,8 +110,7 @@ public slots:
void slotScheduleAllFolders();
bool isDirtyProxy() { return _dirtyProxy; }
void setDirtyProxy(bool value = true) { _dirtyProxy = value; }
void setDirtyProxy(bool value = true);
private slots:
// slot to add a folder to the syncing queue
@@ -122,7 +122,6 @@ private slots:
private:
// finds all folder configuration files
// and create the folders
int setupKnownFolders();
void terminateCurrentSync();
QString getBackupName( const QString& ) const;
@@ -133,6 +132,7 @@ private:
void removeFolder( const QString& );
QSet<Folder*> _disabledFolders;
Folder::Map _folderMap;
QString _folderConfigPath;
QSignalMapper *_folderChangeSignalMapper;

View File

@@ -17,6 +17,9 @@
#include <QtCore>
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
namespace Mirall {
@@ -81,8 +84,9 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem & option ,
h += aliasMargin; // bottom margin
// add some space to show an error condition.
if( ! qvariant_cast<QString>(index.data(FolderErrorMsg)).isEmpty() ) {
h += aliasMargin*2+fm.height();
if( ! qvariant_cast<QStringList>(index.data(FolderErrorMsg)).isEmpty() ) {
QStringList errMsgs = qvariant_cast<QStringList>(index.data(FolderErrorMsg));
h += aliasMargin*2 + errMsgs.count()*fm.height();
}
if( qvariant_cast<bool>(index.data(AddProgressSpace)) ) {
@@ -122,7 +126,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
QString aliasText = qvariant_cast<QString>(index.data(FolderAliasRole));
QString pathText = qvariant_cast<QString>(index.data(FolderPathRole));
QString remotePath = qvariant_cast<QString>(index.data(FolderSecondPathRole));
QString errorText = qvariant_cast<QString>(index.data(FolderErrorMsg));
QStringList errorTexts= qvariant_cast<QStringList>(index.data(FolderErrorMsg));
int overallPercent = qvariant_cast<int>(index.data(SyncProgressOverallPercent));
QString overallString = qvariant_cast<QString>(index.data(SyncProgressOverallString));
@@ -211,12 +215,12 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
// paint an error overlay if there is an error string
int h = iconRect.bottom();
if( !errorText.isEmpty() ) {
if( !errorTexts.isEmpty() ) {
h += aliasMargin;
QRect errorRect = localPathRect;
errorRect.setLeft( iconRect.left());
errorRect.setTop( h );
errorRect.setHeight(subFm.height()+aliasMargin);
errorRect.setHeight(errorTexts.count() * subFm.height()+aliasMargin);
errorRect.setRight( option.rect.right()-aliasMargin );
painter->setBrush( QColor(0xbb, 0x4d, 0x4d) );
@@ -226,15 +230,16 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
painter->setPen( Qt::white );
painter->setFont(errorFont);
QRect errorTextRect = errorRect;
errorTextRect.setLeft( errorTextRect.left()+aliasMargin +16);
errorTextRect.setLeft( errorTextRect.left()+aliasMargin );
errorTextRect.setTop( errorTextRect.top()+aliasMargin/2 );
int linebreak = errorText.indexOf(QLatin1String("<br"));
QString eText = errorText;
if(linebreak) {
eText = errorText.left(linebreak);
int x = errorTextRect.left();
int y = errorTextRect.top()+aliasMargin/2 + subFm.height()/2;
foreach( QString eText, errorTexts ) {
painter->drawText(x, y, subFm.elidedText( eText, Qt::ElideLeft, errorTextRect.width()-2*aliasMargin));
y += subFm.height();
}
painter->drawText(errorTextRect, eText);
h = errorRect.bottom();
}
@@ -249,8 +254,8 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
painter->save();
// Sizes-Text
QRect octetRect = subFm.boundingRect( overallString );
int progressTextWidth = octetRect.width();
QRect octetRect = progressFm.boundingRect( overallString );
int progressTextWidth = octetRect.width() + 2;
// Overall Progress Bar.
QRect pBRect;
@@ -279,8 +284,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
overallProgressRect.setWidth( progressTextWidth );
painter->setFont(progressFont);
QString elidedText = progressFm.elidedText(overallString, Qt::ElideLeft, overallProgressRect.width());
painter->drawText( overallProgressRect, Qt::AlignRight+Qt::AlignVCenter, elidedText);
painter->drawText( overallProgressRect, Qt::AlignRight+Qt::AlignVCenter, overallString);
// painter->drawRect(overallProgressRect);
// Individual File Progress
@@ -289,7 +293,7 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
fileRect.setLeft( iconRect.left());
fileRect.setWidth(overallWidth);
fileRect.setHeight(fileNameTextHeight);
elidedText = progressFm.elidedText(itemString, Qt::ElideLeft, fileRect.width());
QString elidedText = progressFm.elidedText(itemString, Qt::ElideLeft, fileRect.width());
painter->drawText( fileRect, Qt::AlignLeft+Qt::AlignVCenter, elidedText);

View File

@@ -79,7 +79,7 @@ void FolderWatcher::addIgnoreListFile( const QString& file )
while (!infile.atEnd()) {
QString line = QString::fromLocal8Bit( infile.readLine() ).trimmed();
if( !line.startsWith( QLatin1Char('#') ) && line.isEmpty() ) {
if( !(line.startsWith( QLatin1Char('#') ) || line.isEmpty()) ) {
_ignores.append(line);
}
}

View File

@@ -132,7 +132,16 @@ void FolderWatcherPrivate::slotINotifyEvent(int mask, int /*cookie*/, const QStr
//qDebug() << cookie << " OTHER " << mask << " :" << path;
}
foreach (const QString& pattern, _parent->ignores()) {
QStringList ignores = _parent->ignores();
if( path.endsWith(".csync_journal.db.ctmp") ||
path.endsWith(".csync_journal.db.ctmp-journal") ||
path.endsWith(".csync_journal.db")) {
qDebug() << " ** Inotify ignored for " <<path;
return;
}
foreach (const QString& pattern, ignores) {
QRegExp regexp(pattern);
regexp.setPatternSyntax(QRegExp::Wildcard);

View File

@@ -13,9 +13,11 @@
*/
#include "mirall/folderwizard.h"
#include "mirall/owncloudinfo.h"
#include "mirall/folderman.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/theme.h"
#include "mirall/networkjobs.h"
#include "mirall/account.h"
#include <QDebug>
#include <QDesktopServices>
@@ -34,8 +36,24 @@
namespace Mirall
{
QString FormatWarningsWizardPage::formatWarnings(const QStringList &warnings) const
{
QString ret;
if (warnings.count() == 1) {
ret = tr("<b>Warning:</b> ") + warnings.first();
} else if (warnings.count() > 1) {
ret = tr("<b>Warning:</b> ") + "<ul>";
Q_FOREACH(QString warning, warnings) {
ret += QString::fromLatin1("<li>%1</li>").arg(warning);
}
ret += "</ul>";
}
return ret;
}
FolderWizardSourcePage::FolderWizardSourcePage()
: QWizardPage()
: FormatWarningsWizardPage()
{
_ui.setupUi(this);
registerField(QLatin1String("sourceFolder*"), _ui.localFolderLineEdit);
@@ -43,7 +61,7 @@ FolderWizardSourcePage::FolderWizardSourcePage()
_ui.localFolderLineEdit->setText( QDir::toNativeSeparators( defaultPath ) );
registerField(QLatin1String("alias*"), _ui.aliasLineEdit);
_ui.aliasLineEdit->setText( Theme::instance()->appNameGUI() );
_ui.warnLabel->setTextFormat(Qt::RichText);
_ui.warnLabel->hide();
}
@@ -67,16 +85,16 @@ bool FolderWizardSourcePage::isComplete() const
QFileInfo selFile( QDir::fromNativeSeparators(_ui.localFolderLineEdit->text()) );
QString userInput = selFile.canonicalFilePath();
QString warnString;
QStringList warnStrings;
bool isOk = selFile.isDir();
if( !isOk ) {
warnString = tr("No local folder selected!");
warnStrings.append(tr("No valid local folder selected!"));
}
if (isOk && !selFile.isWritable()) {
isOk = false;
warnString += tr("You have no permission to write to the selected folder!");
warnStrings.append(tr("You have no permission to write to the selected folder!"));
}
// check if the local directory isn't used yet in another ownCloud sync
@@ -97,21 +115,40 @@ bool FolderWizardSourcePage::isComplete() const
if( ! folderDir.endsWith(QLatin1Char('/')) ) folderDir.append(QLatin1Char('/'));
qDebug() << "Checking local path: " << folderDir << " <-> " << userInput;
if( QFileInfo( f->path() ) == userInput ) {
if( QDir::cleanPath(f->path()) == QDir::cleanPath(userInput) &&
QDir::cleanPath(QDir(f->path()).canonicalPath()) == QDir(userInput).canonicalPath() ) {
isOk = false;
warnString.append( tr("The local path %1 is already an upload folder.<br/>Please pick another one!")
warnStrings.append( tr("The local path %1 is already an upload folder. Please pick another one!")
.arg(QDir::toNativeSeparators(userInput)) );
}
if( isOk && folderDir.startsWith( userInput )) {
if( isOk && QDir::cleanPath(folderDir).startsWith(QDir::cleanPath(userInput)+'/') ) {
qDebug() << "A already configured folder is child of the current selected";
warnString.append( tr("An already configured folder is contained in the current entry."));
warnStrings.append( tr("An already configured folder is contained in the current entry."));
isOk = false;
}
if( isOk && userInput.startsWith( folderDir ) ) {
QString absCleanUserFolder = QDir::cleanPath(QDir(userInput).canonicalPath())+'/';
if( isOk && QDir::cleanPath(folderDir).startsWith(absCleanUserFolder) ) {
qDebug() << "A already configured folder is child of the current selected";
warnStrings.append( tr("The selected folder is a symbolic link. An already configured"
"folder is contained in the folder this link is pointing to."));
isOk = false;
}
if( isOk && QDir::cleanPath(QString(userInput+'/')).startsWith( QDir::cleanPath(folderDir)) ) {
qDebug() << "An already configured folder is parent of the current selected";
warnString.append( tr("An already configured folder contains the currently entered directory."));
warnStrings.append( tr("An already configured folder contains the currently entered folder."));
isOk = false;
}
if( isOk && absCleanUserFolder.startsWith( QDir::cleanPath(folderDir)) ) {
qDebug() << "The selected folder is a symbolic link. An already configured folder is\n"
"the parent of the current selected contains the folder this link is pointing to.";
warnStrings.append( tr("The selected folder is a symbolic link. An already configured folder "
"is the parent of the current selected contains the folder this link is "
"pointing to."));
isOk = false;
}
i++;
}
}
@@ -119,7 +156,7 @@ bool FolderWizardSourcePage::isComplete() const
// check if the alias is unique.
QString alias = _ui.aliasLineEdit->text();
if( alias.isEmpty() ) {
warnString.append( tr("The alias can not be empty. Please provide a descriptive alias word.") );
warnStrings.append( tr("The alias can not be empty. Please provide a descriptive alias word.") );
isOk = false;
}
@@ -130,7 +167,7 @@ bool FolderWizardSourcePage::isComplete() const
qDebug() << "Checking local alias: " << f->alias();
if( f ) {
if( f->alias() == alias ) {
warnString.append( tr("<br/>The alias <i>%1</i> is already in use. Please pick another alias.").arg(alias) );
warnStrings.append( tr("The alias <i>%1</i> is already in use. Please pick another alias.").arg(alias) );
isOk = false;
goon = false;
}
@@ -138,12 +175,14 @@ bool FolderWizardSourcePage::isComplete() const
i++;
}
_ui.warnLabel->setWordWrap(true);
if( isOk ) {
_ui.warnLabel->hide();
_ui.warnLabel->setText( QString::null );
} else {
_ui.warnLabel->show();
_ui.warnLabel->setText( warnString );
QString warnings = formatWarnings(warnStrings);
_ui.warnLabel->setText( warnings );
}
return isOk;
}
@@ -166,7 +205,8 @@ void FolderWizardSourcePage::on_localFolderLineEdit_textChanged()
// =================================================================================
FolderWizardTargetPage::FolderWizardTargetPage()
: _warnWasVisible(false)
: FormatWarningsWizardPage()
,_warnWasVisible(false)
{
_ui.setupUi(this);
_ui.warnFrame->hide();
@@ -175,6 +215,8 @@ FolderWizardTargetPage::FolderWizardTargetPage()
connect(_ui.refreshButton, SIGNAL(clicked()), SLOT(slotRefreshFolders()));
connect(_ui.folderTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), SIGNAL(completeChanged()));
connect(_ui.folderTreeWidget, SIGNAL(itemActivated(QTreeWidgetItem*,int)), SIGNAL(completeChanged()));
connect(_ui.folderTreeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(slotItemExpanded(QTreeWidgetItem*)));
}
void FolderWizardTargetPage::slotAddRemoteFolder()
@@ -195,24 +237,32 @@ void FolderWizardTargetPage::slotAddRemoteFolder()
dlg->setAttribute(Qt::WA_DeleteOnClose);
}
void FolderWizardTargetPage::slotCreateRemoteFolder(QString folder)
void FolderWizardTargetPage::slotCreateRemoteFolder(const QString &folder)
{
if( folder.isEmpty() ) return;
ownCloudInfo::instance()->mkdirRequest( folder );
MkColJob *job = new MkColJob(AccountManager::instance()->account(), folder, this);
/* check the owncloud configuration file and query the ownCloud */
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)),
SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotHandleNetworkError(QNetworkReply*)));
job->start();
}
void FolderWizardTargetPage::slotCreateRemoteFolderFinished( QNetworkReply::NetworkError error )
void FolderWizardTargetPage::slotCreateRemoteFolderFinished(QNetworkReply::NetworkError error)
{
qDebug() << "** webdav mkdir request finished " << error;
if (error == QNetworkReply::NoError) {
qDebug() << "** webdav mkdir request finished";
showWarn(tr("Folder was successfully created on %1.").arg(Theme::instance()->appNameGUI()));
slotRefreshFolders();
}
}
// the webDAV server seems to return a 202 even if mkdir was successful.
if( error == QNetworkReply::NoError ||
error == QNetworkReply::ContentOperationNotPermittedError) {
showWarn( tr("Folder was successfully created on %1.").arg( Theme::instance()->appNameGUI() ) );
slotRefreshFolders();
} else {
showWarn( tr("Failed to create the folder on %1.<br/>Please check manually.").arg( Theme::instance()->appNameGUI() ) );
}
void FolderWizardTargetPage::slotHandleNetworkError(QNetworkReply *reply)
{
qDebug() << "** webdav mkdir request failed:" << reply->error();
showWarn(tr("Failed to create the folder on %1. Please check manually.")
.arg(Theme::instance()->appNameGUI()));
}
static QTreeWidgetItem* findFirstChild(QTreeWidgetItem *parent, const QString& text)
@@ -226,11 +276,11 @@ static QTreeWidgetItem* findFirstChild(QTreeWidgetItem *parent, const QString& t
return 0;
}
static void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path)
void FolderWizardTargetPage::recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path)
{
QFileIconProvider prov;
QIcon folderIcon = prov.icon(QFileIconProvider::Folder);
if (pathTrail.size() == 0) {
if (pathTrail.size() == 0) {
if (path.endsWith('/')) {
path.chop(1);
}
@@ -242,6 +292,7 @@ static void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QStr
item = new QTreeWidgetItem(parent);
item->setIcon(0, folderIcon);
item->setText(0, pathTrail.first());
item->setData(0, Qt::UserRole, pathTrail.first());
item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
}
@@ -250,19 +301,15 @@ static void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QStr
}
}
void FolderWizardTargetPage::slotUpdateDirectories(QStringList list)
void FolderWizardTargetPage::slotUpdateDirectories(const QStringList &list)
{
QFileIconProvider prov;
QIcon folderIcon = prov.icon(QFileIconProvider::Folder);
QString webdavFolder = QUrl(ownCloudInfo::instance()->webdavUrl()).path();
connect(_ui.folderTreeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), SLOT(slotItemExpanded(QTreeWidgetItem*)));
QString webdavFolder = QUrl(AccountManager::instance()->account()->davUrl()).path();
QTreeWidgetItem *root = _ui.folderTreeWidget->topLevelItem(0);
if (!root) {
root = new QTreeWidgetItem(_ui.folderTreeWidget);
root->setText(0, tr("Root (\"/\")", "root folder"));
root->setIcon(0, folderIcon);
root->setText(0, Theme::instance()->appNameGUI());
root->setIcon(0, Theme::instance()->applicationIcon());
root->setToolTip(0, tr("Choose this to sync the entire account"));
root->setData(0, Qt::UserRole, "/");
}
@@ -277,13 +324,20 @@ void FolderWizardTargetPage::slotUpdateDirectories(QStringList list)
void FolderWizardTargetPage::slotRefreshFolders()
{
ownCloudInfo::instance()->getDirectoryListing("/");
LsColJob *job = new LsColJob(AccountManager::instance()->account(), "/", this);
connect(job, SIGNAL(directoryListing(QStringList)),
SLOT(slotUpdateDirectories(QStringList)));
job->start();
_ui.folderTreeWidget->clear();
}
void FolderWizardTargetPage::slotItemExpanded(QTreeWidgetItem *item)
{
ownCloudInfo::instance()->getDirectoryListing(item->text(0));
QString dir = item->data(0, Qt::UserRole).toString();
LsColJob *job = new LsColJob(AccountManager::instance()->account(), dir, this);
connect(job, SIGNAL(directoryListing(QStringList)),
SLOT(slotUpdateDirectories(QStringList)));
job->start();
}
FolderWizardTargetPage::~FolderWizardTargetPage()
@@ -295,16 +349,36 @@ bool FolderWizardTargetPage::isComplete() const
if (!_ui.folderTreeWidget->currentItem())
return false;
QStringList warnStrings;
QString dir = _ui.folderTreeWidget->currentItem()->data(0, Qt::UserRole).toString();
if (!dir.startsWith(QLatin1Char('/'))) {
dir.prepend(QLatin1Char('/'));
}
wizard()->setProperty("targetPath", dir);
if( dir == QLatin1String("/") ) {
showWarn( tr("If you sync the root folder, you can <b>not</b> configure another sync directory."));
return true;
} else {
showWarn();
return true;
Folder::Map map = _folderMap;
Folder::Map::const_iterator i = map.constBegin();
for(i = map.constBegin();i != map.constEnd(); i++ ) {
Folder *f = static_cast<Folder*>(i.value());
QString curDir = f->remotePath();
if (!curDir.startsWith(QLatin1Char('/'))) {
curDir.prepend(QLatin1Char('/'));
}
if (QDir::cleanPath(dir) == QDir::cleanPath(curDir)) {
warnStrings.append(tr("This folder is already being synced."));
} else if (dir.startsWith(curDir + QLatin1Char('/'))) {
warnStrings.append(tr("You are already syncing <i>%1</i>, which is a parent folder of <i>%2</i>.").arg(curDir).arg(dir));
}
if (curDir == QLatin1String("/")) {
warnStrings.append(tr("You are already syncing all your files. Syncing another folder is <b>not</b> supported. "
"If you want to sync multiple folders, please remove the currently configured "
"root folder sync."));
}
}
showWarn(formatWarnings(warnStrings));
return warnStrings.isEmpty();
}
void FolderWizardTargetPage::cleanupPage()
@@ -315,19 +389,7 @@ void FolderWizardTargetPage::cleanupPage()
void FolderWizardTargetPage::initializePage()
{
showWarn();
/* check the owncloud configuration file and query the ownCloud */
ownCloudInfo *ocInfo = ownCloudInfo::instance();
if( ocInfo->isConfigured() ) {
connect( ocInfo, SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
SLOT(slotDirCheckReply(QString,QNetworkReply*)));
connect( ocInfo, SIGNAL(webdavColCreated(QNetworkReply::NetworkError)),
SLOT(slotCreateRemoteFolderFinished( QNetworkReply::NetworkError )));
connect( ocInfo, SIGNAL(directoryListingUpdated(QStringList)),
SLOT(slotUpdateDirectories(QStringList)));
slotRefreshFolders();
}
slotRefreshFolders();
}
void FolderWizardTargetPage::showWarn( const QString& msg ) const
@@ -352,6 +414,7 @@ FolderWizard::FolderWizard( QWidget *parent )
_folderWizardSourcePage(new FolderWizardSourcePage),
_folderWizardTargetPage(0)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setPage(Page_Source, _folderWizardSourcePage );
if (!Theme::instance()->singleSyncFolder()) {
_folderWizardTargetPage = new FolderWizardTargetPage();
@@ -370,6 +433,7 @@ FolderWizard::~FolderWizard()
void FolderWizard::setFolderMap( const Folder::Map& fm)
{
_folderWizardSourcePage->setFolderMap( fm );
_folderWizardTargetPage->setFolderMap( fm );
}
} // end namespace

View File

@@ -28,10 +28,15 @@ namespace Mirall {
class ownCloudInfo;
class FormatWarningsWizardPage : public QWizardPage {
protected:
QString formatWarnings(const QStringList &warnings) const;
};
/**
* page to ask for the local source folder
*/
class FolderWizardSourcePage : public QWizardPage
class FolderWizardSourcePage : public FormatWarningsWizardPage
{
Q_OBJECT
public:
@@ -57,7 +62,7 @@ private:
* page to ask for the target folder
*/
class FolderWizardTargetPage : public QWizardPage
class FolderWizardTargetPage : public FormatWarningsWizardPage
{
Q_OBJECT
public:
@@ -69,20 +74,25 @@ public:
virtual void initializePage();
virtual void cleanupPage();
void setFolderMap( const Folder::Map &fm ) { _folderMap = fm; }
protected slots:
void showWarn( const QString& = QString() ) const;
void slotAddRemoteFolder();
void slotCreateRemoteFolder(QString);
void slotCreateRemoteFolderFinished( QNetworkReply::NetworkError error );
void slotUpdateDirectories(QStringList);
void slotCreateRemoteFolder(const QString&);
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError error);
void slotHandleNetworkError(QNetworkReply*);
void slotUpdateDirectories(const QStringList&);
void slotRefreshFolders();
void slotItemExpanded(QTreeWidgetItem*);
private:
void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path);
Ui_FolderWizardTargetPage _ui;
ownCloudInfo *_ownCloudDirCheck;
bool _dirChecked;
bool _warnWasVisible;
Folder::Map _folderMap;
};
/**

View File

@@ -145,9 +145,6 @@
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>3</number>
</property>

View File

@@ -109,9 +109,6 @@
<property name="textFormat">
<enum>Qt::AutoText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>

View File

@@ -17,7 +17,7 @@
<item>
<widget class="QGroupBox" name="generalGroupBox">
<property name="title">
<string>General</string>
<string>General Setttings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
@@ -77,7 +77,4 @@
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="proxyButtonGroup"/>
</buttongroups>
</ui>

View File

@@ -20,7 +20,6 @@
#include <QDir>
#include <QListWidget>
#include <QListWidgetItem>
#include <QColorGroup>
#include <QMessageBox>
#include <QInputDialog>
@@ -30,6 +29,7 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent) :
QDialog(parent),
ui(new Ui::IgnoreListEditor)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
ui->setupUi(this);
ui->descriptionLabel->setText(tr("Files or directories matching a pattern will not be synchronized.\n\n"

View File

@@ -88,6 +88,7 @@ void INotify::slotActivated(int /*fd*/)
// with the help of watch descriptor, retrieve, corresponding INotify
if (event == NULL) {
qDebug() << "NULL event";
i += sizeof(struct inotify_event);
continue;
}

View File

@@ -1,333 +0,0 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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 <QtGui>
#include "mirall/itemprogressdialog.h"
#include "mirall/syncresult.h"
#include "mirall/logger.h"
#include "mirall/utility.h"
#include "mirall/theme.h"
#include "mirall/folderman.h"
#include "mirall/syncfileitem.h"
#include "ui_itemprogressdialog.h"
namespace Mirall {
ItemProgressDialog::ItemProgressDialog(Application*, QWidget *parent) :
QDialog(parent),
ErrorIndicatorRole( Qt::UserRole +1 ),
_ui(new Ui::ItemProgressDialog)
{
_ui->setupUi(this);
connect(_ui->_dialogButtonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()),
this, SLOT(accept()));
connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString,Progress::Info)),
this, SLOT(slotProgressInfo(QString,Progress::Info)));
connect(ProgressDispatcher::instance(), SIGNAL(progressSyncProblem(const QString&,const Progress::SyncProblem&)),
this, SLOT(slotProgressErrors(const QString&, const Progress::SyncProblem&)));
QStringList header;
header << tr("Time");
header << tr("File");
header << tr("Folder");
header << tr("Action");
header << tr("Size");
_ui->_treeWidget->setHeaderLabels( header );
_ui->_treeWidget->setColumnWidth(1, 180);
_ui->_treeWidget->setColumnCount(5);
_ui->_treeWidget->setRootIsDecorated(false);
connect(this, SIGNAL(guiLog(QString,QString)), Logger::instance(), SIGNAL(guiLog(QString,QString)));
QPushButton *copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
connect(copyBtn, SIGNAL(clicked()), SLOT(copyToClipboard()));
setWindowTitle(tr("Sync Protocol"));
}
void ItemProgressDialog::setSyncResultStatus(const SyncResult& result )
{
if( result.errorStrings().count() ) {
_ui->_errorLabel->setVisible(true);
_ui->_errorLabel->setTextFormat(Qt::RichText);
QString errStr;
QStringList errors = result.errorStrings();
int cnt = errors.size();
bool appendDots = false;
if( cnt > 3 ) {
cnt = 3;
appendDots = true;
}
for( int i = 0; i < cnt; i++) {
errStr.append(QString("%1<br/>").arg(errors.at(i)));
}
if( appendDots ) {
errStr.append(QString("..."));
}
_ui->_errorLabel->setText(errStr);
} else {
_ui->_errorLabel->setText(QString::null);
_ui->_errorLabel->setVisible(false);
}
}
void ItemProgressDialog::setSyncResult( const SyncResult& result )
{
setSyncResultStatus(result);
const QString& folder = result.folder();
qDebug() << "Setting sync result for folder " << folder;
SyncFileItemVector::const_iterator i;
const SyncFileItemVector& items = result.syncFileItemVector();
QDateTime dt = QDateTime::currentDateTime();
for (i = items.begin(); i != items.end(); ++i) {
const SyncFileItem& item = *i;
QString errMsg;
QString tooltip;
// handle ignored files here.
if( item._instruction == CSYNC_INSTRUCTION_IGNORE
|| item._instruction == CSYNC_INSTRUCTION_CONFLICT ) {
QStringList columns;
QString timeStr = timeString(dt);
QString longTimeStr = timeString(dt, QLocale::LongFormat);
columns << timeStr;
columns << item._file;
columns << folder;
if( item._instruction == CSYNC_INSTRUCTION_IGNORE) {
if( item._type == SyncFileItem::SoftLink ) {
errMsg = tr("Soft Link ignored");
tooltip = tr("Softlinks break the semantics of synchronization.\nPlease do not "
"use them in synced directories");
} else {
QString obj = tr("file");
if( item._type == SyncFileItem::Directory ) {
obj = tr("directory");
}
tooltip = tr("The %1 was ignored because it is listed in the clients ignore list\n"
"or the %1 name contains characters that are not syncable\nin a cross platform "
"environment").arg(obj);
errMsg = tr("Item ignored");
if( item._errorString == QLatin1String("File listed on ignore list.") ) {
errMsg = tr("%1 on ignore list").arg(obj);
tooltip = tr("The %1 was skipped because it is listed on the clients\n"
"list of names to ignore").arg(obj);
} else if( item._errorString == QLatin1String("File contains invalid characters.") ) {
errMsg = tr("Invalid characters");
tooltip = tr("The %1 name contains one or more invalid characters which break\n"
"syncing in a cross platform environment").arg(obj);
}
}
} else if( item._instruction == CSYNC_INSTRUCTION_CONFLICT ) {
errMsg = tr("Conflict file.");
tooltip = tr("The file was changed on server and local repository and as a result it\n"
"created a so called conflict. The local change is copied to the conflict\n"
"file while the file from the server side is available under the original\n"
"name");
} else {
Q_ASSERT(!"unhandled instruction.");
}
columns << errMsg;
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
twitem->setData(0, ErrorIndicatorRole, QVariant(true) );
twitem->setToolTip(0, longTimeStr);
twitem->setToolTip(3, tooltip);
twitem->setIcon(0, Theme::instance()->syncStateIcon(SyncResult::Problem, true));
_ui->_treeWidget->insertTopLevelItem(0, twitem);
}
}
}
void ItemProgressDialog::setupList()
{
// get the folders to set up the top level list.
Folder::Map map = FolderMan::instance()->map();
SyncResult lastResult;
QDateTime dt;
bool haveSyncResult = false;
foreach( Folder *f, map.values() ) {
if( f->syncResult().syncTime() > dt ) {
dt = f->syncResult().syncTime();
lastResult = f->syncResult();
haveSyncResult = true;
}
}
if( haveSyncResult ) {
setSyncResult(lastResult);
}
QList<Progress::Info> progressList = ProgressDispatcher::instance()->recentChangedItems(0); // All.
QHash <QString, int> folderHash;
foreach( Progress::Info info, progressList ) {
slotProgressInfo( info.folder, info );
folderHash[info.folder] = 1;
}
QList<Progress::SyncProblem> problemList = ProgressDispatcher::instance()->recentProblems(0);
foreach( Progress::SyncProblem prob, problemList ) {
slotProgressErrors(prob.folder, prob);
folderHash[prob.folder] = 1;
}
}
ItemProgressDialog::~ItemProgressDialog()
{
delete _ui;
}
void ItemProgressDialog::copyToClipboard()
{
QString text;
QTextStream ts(&text);
int topLevelItems = _ui->_treeWidget->topLevelItemCount();
for (int i = 0; i < topLevelItems; i++) {
QTreeWidgetItem *child = _ui->_treeWidget->topLevelItem(i);
// time stamp
ts << left << qSetFieldWidth(10)
<< child->data(0,Qt::DisplayRole).toString()
// file name
<< qSetFieldWidth(64)
<< child->data(1,Qt::DisplayRole).toString()
<< qSetFieldWidth(0) << ' '
// action
<< qSetFieldWidth(15)
<< child->data(3, Qt::DisplayRole).toString()
// size
<< qSetFieldWidth(10)
<< child->data(4, Qt::DisplayRole).toString()
<< qSetFieldWidth(0)
<< endl;
}
QApplication::clipboard()->setText(text);
emit guiLog(tr("Copied to clipboard"), tr("The sync protocol has been copied to the clipboard."));
}
void ItemProgressDialog::accept()
{
QDialog::accept();
}
void ItemProgressDialog::cleanErrors( const QString& /* folder */ ) // FIXME: Use the folder to detect which errors can be deleted.
{
_problemCounter = 0;
QList<QTreeWidgetItem*> wipeList;
int itemCnt = _ui->_treeWidget->topLevelItemCount();
for( int cnt = 0; cnt < itemCnt; cnt++ ) {
QTreeWidgetItem *item = _ui->_treeWidget->topLevelItem(cnt);
bool isErrorItem = item->data(0, ErrorIndicatorRole).toBool();
if( isErrorItem ) {
wipeList.append(item);
}
}
qDeleteAll(wipeList.begin(), wipeList.end());
}
QString ItemProgressDialog::timeString(QDateTime dt, QLocale::FormatType format) const
{
QLocale loc = QLocale::system();
QString timeStr;
QDate today = QDate::currentDate();
if( format == QLocale::NarrowFormat ) {
if( dt.date().day() == today.day() ) {
timeStr = loc.toString(dt.time(), QLocale::NarrowFormat);
} else {
timeStr = loc.toString(dt, QLocale::NarrowFormat);
}
} else {
timeStr = loc.toString(dt, format);
}
return timeStr;
}
void ItemProgressDialog::slotProgressErrors( const QString& folder, const Progress::SyncProblem& problem )
{
QStringList columns;
QString timeStr = timeString(problem.timestamp);
QString longTimeStr = timeString(problem.timestamp, QLocale::LongFormat);
columns << timeStr;
columns << problem.current_file;
columns << folder;
QString errMsg = tr("Problem: %1").arg(problem.error_message);
#if 0
if( problem.error_code == 507 ) {
errMsg = tr("No more storage space available on server.");
}
#endif
columns << errMsg;
QTreeWidgetItem *item = new QTreeWidgetItem(columns);
item->setData(0, ErrorIndicatorRole, QVariant(true) );
// Maybe we should not set the error icon for all problems but distinguish
// by error_code. A quota problem is considered an error, others might not??
item->setIcon(0, Theme::instance()->syncStateIcon(SyncResult::Error, true));
item->setToolTip(0, longTimeStr);
_ui->_treeWidget->insertTopLevelItem(0, item);
Q_UNUSED(item);
}
void ItemProgressDialog::slotProgressInfo( const QString& folder, const Progress::Info& progress )
{
if( progress.kind == Progress::StartSync ) {
cleanErrors( folder );
}
if( progress.kind == Progress::EndSync ) {
// decorateFolderItem( folder );
}
// Ingore other events than finishing an individual up- or download.
if( !(progress.kind == Progress::EndDownload || progress.kind == Progress::EndUpload || progress.kind == Progress::EndDelete)) {
return;
}
QStringList columns;
QString timeStr = timeString(progress.timestamp);
QString longTimeStr = timeString(progress.timestamp, QLocale::LongFormat);
columns << timeStr;
columns << progress.current_file;
columns << progress.folder;
columns << Progress::asResultString(progress.kind);
columns << Utility::octetsToString( progress.file_size );
QTreeWidgetItem *item = new QTreeWidgetItem(columns);
item->setToolTip(0, longTimeStr);
_ui->_treeWidget->insertTopLevelItem(0, item);
Q_UNUSED(item);
}
}

View File

@@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Mirall::ItemProgressDialog</class>
<widget class="QWidget" name="Mirall::ItemProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>612</width>
<height>543</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Detailed Sync Protocol</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="_treeWidget">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="columnCount">
<number>4</number>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
<column>
<property name="text">
<string>3</string>
</property>
</column>
<column>
<property name="text">
<string>4</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="QLabel" name="_errorLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="_dialogButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -52,9 +52,9 @@ LogWidget::LogWidget(QWidget *parent)
LogBrowser::LogBrowser(QWidget *parent) :
QDialog(parent),
_logWidget( new LogWidget(parent) ),
_doFileFlush(false)
_logWidget( new LogWidget(parent) )
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setObjectName("LogBrowser"); // for save/restoreGeometry()
setWindowTitle(tr("Log Output"));
setMinimumWidth(600);
@@ -127,49 +127,23 @@ LogBrowser::LogBrowser(QWidget *parent) :
}
LogBrowser::~LogBrowser()
{
}
void LogBrowser::closeEvent(QCloseEvent *)
{
MirallConfigFile cfg;
cfg.saveGeometry(this);
}
void LogBrowser::slotNewLog( const QString& msg )
{
if( _logWidget->isVisible() ) {
_logWidget->appendPlainText( msg );
}
if( _logstream ) {
(*_logstream) << msg << endl;
if( _doFileFlush ) _logstream->flush();
}
}
void LogBrowser::setLogFile( const QString & name, bool flush )
{
if( _logstream ) {
_logFile.close();
}
bool openSucceeded = false;
if (name == QLatin1String("-")) {
openSucceeded = _logFile.open(1, QIODevice::WriteOnly);
} else {
_logFile.setFileName( name );
openSucceeded = _logFile.open(QIODevice::WriteOnly);
}
if(!openSucceeded) {
QMessageBox::warning(
this,
tr("Error"),
QString(tr("<nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>"
"The log output can <b>not</b> be saved!</nobr>"))
.arg(name));
return;
}
_doFileFlush = flush;
_logstream.reset(new QTextStream( &_logFile ));
}
void LogBrowser::slotFind()
{

View File

@@ -47,6 +47,9 @@ public:
void setLogFile(const QString& , bool );
protected:
void closeEvent(QCloseEvent *);
protected slots:
void slotNewLog( const QString &msg );
void slotFind();
@@ -61,9 +64,6 @@ private:
QPushButton *_clearBtn;
QLabel *_statusLabel;
QFile _logFile;
bool _doFileFlush;
QScopedPointer<QTextStream> _logstream;
};
} // namespace

View File

@@ -14,8 +14,19 @@
#include "mirall/logger.h"
#include <QDir>
#include <QStringList>
namespace Mirall {
// logging handler.
void mirallLogCatcher(QtMsgType type, const char *msg)
{
Q_UNUSED(type)
// qDebug() exports to local8Bit, which is not always UTF-8
Logger::instance()->mirallLog( QString::fromLocal8Bit(msg) );
}
Logger* Logger::_instance=0;
Logger::Logger( QObject* parent)
@@ -26,7 +37,10 @@ Logger::Logger( QObject* parent)
Logger *Logger::instance()
{
if( !Logger::_instance ) Logger::_instance = new Logger;
if( !Logger::_instance ) {
Logger::_instance = new Logger;
qInstallMsgHandler( mirallLogCatcher );
}
return Logger::_instance;
}
@@ -50,7 +64,7 @@ void Logger::postOptionalGuiLog(const QString &title, const QString &message)
void Logger::postGuiMessage(const QString &title, const QString &message)
{
emit postGuiMessage(title, message);
emit guiMessage(title, message);
}
void Logger::log(Log log)
@@ -68,6 +82,15 @@ void Logger::log(Log log)
msg += log.message;
// _logs.append(log);
// std::cout << qPrintable(log.message) << std::endl;
{
QMutexLocker lock(&_mutex);
if( _logstream ) {
(*_logstream) << msg << endl;
if( _doFileFlush ) _logstream->flush();
}
}
emit newLog(msg);
}
@@ -91,4 +114,83 @@ void Logger::mirallLog( const QString& message )
Logger::instance()->log( log_ );
}
void Logger::setLogFile(const QString & name)
{
QMutexLocker locker(&_mutex);
if( _logstream ) {
_logstream.reset(0);
_logFile.close();
}
if( name.isEmpty() ) {
return;
}
bool openSucceeded = false;
if (name == QLatin1String("-")) {
openSucceeded = _logFile.open(1, QIODevice::WriteOnly);
} else {
_logFile.setFileName( name );
openSucceeded = _logFile.open(QIODevice::WriteOnly);
}
if(!openSucceeded) {
locker.unlock(); // Just in case postGuiMessage has a qDebug()
postGuiMessage( tr("Error"),
QString(tr("<nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>"
"The log output can <b>not</b> be saved!</nobr>"))
.arg(name));
return;
}
_logstream.reset(new QTextStream( &_logFile ));
}
void Logger::setLogExpire( int expire )
{
_logExpire = expire;
}
void Logger::setLogDir( const QString& dir )
{
_logDirectory = dir;
}
void Logger::setLogFlush( bool flush )
{
_doFileFlush = flush;
}
void Logger::enterNextLogFile()
{
if (!_logDirectory.isEmpty()) {
QDir dir(_logDirectory);
if (!dir.exists()) {
dir.mkpath(".");
}
// Find out what is the file with the highest nymber if any
QStringList files = dir.entryList(QStringList("owncloud.log.*"),
QDir::Files);
QRegExp rx("owncloud.log.(\\d+)");
uint maxNumber = 0;
QDateTime now = QDateTime::currentDateTime();
foreach(const QString &s, files) {
if (rx.exactMatch(s)) {
maxNumber = qMax(maxNumber, rx.cap(1).toUInt());
if (_logExpire > 0) {
QFileInfo fileInfo = dir.absoluteFilePath(s);
if (fileInfo.lastModified().addSecs(60*60 * _logExpire) < now) {
dir.remove(s);
}
}
}
}
QString filename = _logDirectory + "/owncloud.log." + QString::number(maxNumber+1);
setLogFile(filename);
}
}
} // namespace Mirall

View File

@@ -18,6 +18,9 @@
#include <QObject>
#include <QList>
#include <QDateTime>
#include <QFile>
#include <QTextStream>
#include <qmutex.h>
namespace Mirall {
@@ -50,12 +53,20 @@ public:
void postOptionalGuiLog(const QString& title, const QString& message);
void postGuiMessage(const QString& title, const QString& message);
void setLogFile( const QString & name );
void setLogExpire( int expire );
void setLogDir( const QString& dir );
void setLogFlush( bool flush );
signals:
void newLog(const QString&);
void guiLog(const QString&, const QString&);
void guiMessage(const QString&, const QString&);
void optionalGuiLog(const QString&, const QString&);
public slots:
void enterNextLogFile();
protected:
Logger(QObject* parent=0);
QList<Log> _logs;
@@ -63,6 +74,14 @@ protected:
bool _doLogging;
static Logger* _instance;
QFile _logFile;
bool _doFileFlush;
int _logExpire;
QScopedPointer<QTextStream> _logstream;
QMutex _mutex;
QString _logDirectory;
};
} // namespace Mirall

View File

@@ -21,14 +21,20 @@ namespace Mirall
MirallAccessManager::MirallAccessManager(QObject* parent)
: QNetworkAccessManager (parent)
{}
{
}
QNetworkReply* MirallAccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest& request, QIODevice* outgoingData)
{
QNetworkRequest newRequest(request);
newRequest.setRawHeader( QByteArray("User-Agent"), Utility::userAgentString());
return QNetworkAccessManager::createRequest (op, newRequest, outgoingData);
newRequest.setRawHeader(QByteArray("User-Agent"), Utility::userAgentString());
QByteArray verb = newRequest.attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
// For PROPFIND (assumed to be a WebDAV op), set xml/utf8 as content type/encoding
// This needs extension
if (verb == "PROPFIND") {
newRequest.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String("text/xml; charset=utf-8"));
}
return QNetworkAccessManager::createRequest(op, newRequest, outgoingData);
}
} // ns Mirall

View File

@@ -15,22 +15,27 @@
#include "config.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/owncloudinfo.h"
#include "mirall/owncloudtheme.h"
#include "mirall/theme.h"
#include "mirall/utility.h"
#include "creds/abstractcredentials.h"
#include "creds/credentialsfactory.h"
#include <QtCore>
#include <QtGui>
#include <QWidget>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QSettings>
#include <QDebug>
#include <QNetworkProxy>
#define DEFAULT_REMOTE_POLL_INTERVAL 30000 // default remote poll time in milliseconds
#define DEFAULT_MAX_LOG_LINES 20000
namespace Mirall {
static const char urlC[] = "url";
static const char authTypeC[] = "authType";
static const char caCertsKeyC[] = "CaCertificates";
static const char remotePollIntervalC[] = "remotePollInterval";
static const char forceSyncIntervalC[] = "forceSyncInterval";
@@ -57,39 +62,18 @@ static const char maxLogLinesC[] = "Logging/maxLogLines";
QString MirallConfigFile::_oCVersion;
QString MirallConfigFile::_confDir = QString::null;
bool MirallConfigFile::_askedUser = false;
QMap< QString, MirallConfigFile::SharedCreds > MirallConfigFile::credentialsPerConfig;
MirallConfigFile::MirallConfigFile( const QString& appendix, bool useOldConfig )
MirallConfigFile::MirallConfigFile()
{
if (useOldConfig && !appendix.isEmpty()) {
QString oldConfigFile = configFile();
_customHandle = appendix;
QString newConfigFile = configFile();
QFile::copy(oldConfigFile, newConfigFile);
} else {
_customHandle = appendix;
}
QSettings::setDefaultFormat(QSettings::IniFormat);
if (! credentialsPerConfig.contains(_customHandle)) {
QString con( _customHandle );
if( _customHandle.isEmpty() ) con = defaultConnection();
const QString config = configFile();
qDebug() << "Loading config: " << config;
const QString config = configFile();
QSettings settings(config, QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
QSettings settings(config, QSettings::IniFormat);
settings.beginGroup( defaultConnection() );
QString type = settings.value( QLatin1String(authTypeC) ).toString();
qDebug() << "Getting credentials of type " << type << " for " << _customHandle;
credentialsPerConfig.insert(_customHandle, SharedCreds(CredentialsFactory::create (type)));
}
qDebug() << "Loading config: " << config << " (URL is " << settings.value("url").toString() << ")";
}
void MirallConfigFile::setConfDir(const QString &value)
@@ -114,14 +98,12 @@ void MirallConfigFile::setConfDir(const QString &value)
bool MirallConfigFile::optionalDesktopNotifications() const
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
return settings.value(QLatin1String(optionalDesktopNoficationsC), true).toBool();
}
void MirallConfigFile::setOptionalDesktopNotifications(bool show)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(QLatin1String(optionalDesktopNoficationsC), show);
settings.sync();
}
@@ -129,14 +111,12 @@ void MirallConfigFile::setOptionalDesktopNotifications(bool show)
QString MirallConfigFile::seenVersion() const
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
return settings.value(QLatin1String(seenVersionC)).toString();
}
void MirallConfigFile::setSeenVersion(const QString &version)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(QLatin1String(seenVersionC), version);
settings.sync();
}
@@ -145,7 +125,6 @@ void MirallConfigFile::saveGeometry(QWidget *w)
{
Q_ASSERT(!w->objectName().isNull());
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(w->objectName());
settings.setValue(QLatin1String(geometryC), w->saveGeometry());
settings.sync();
@@ -159,7 +138,7 @@ void MirallConfigFile::restoreGeometry(QWidget *w)
QString MirallConfigFile::configPath() const
{
if( _confDir.isEmpty() ) {
_confDir = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
_confDir = Utility::dataLocation();
}
QString dir = _confDir;
@@ -167,6 +146,12 @@ QString MirallConfigFile::configPath() const
return dir;
}
QString MirallConfigFile::configPathWithAppName() const
{
//HACK
return QFileInfo( configFile() ).dir().absolutePath().append("/");
}
QString MirallConfigFile::excludeFile(Scope scope) const
{
// prefer sync-exclude.lst, but if it does not exist, check for
@@ -190,14 +175,14 @@ QString MirallConfigFile::excludeFile(Scope scope) const
// Check alternative places...
if( ! fi.isReadable() ) {
#ifdef Q_OS_WIN
fi.setFile( QApplication::applicationDirPath(), exclFile );
fi.setFile( QCoreApplication::applicationDirPath(), exclFile );
#endif
#ifdef Q_OS_UNIX
fi.setFile( QString( SYSCONFDIR "/%1").arg(Theme::instance()->appName()), exclFile );
#endif
#ifdef Q_OS_MAC
// exec path is inside the bundle
fi.setFile( QApplication::applicationDirPath(),
fi.setFile( QCoreApplication::applicationDirPath(),
QLatin1String("../Resources/") + exclFile );
#endif
}
@@ -213,13 +198,7 @@ QString MirallConfigFile::configFile() const
if( qApp->applicationName().isEmpty() ) {
qApp->setApplicationName( Theme::instance()->appNameGUI() );
}
QString file = configPath() + Theme::instance()->configFileName();
if( !_customHandle.isEmpty() ) {
file.append( QLatin1Char('_'));
file.append( _customHandle );
qDebug() << __PRETTY_FUNCTION__ << " OO Custom config file in use: " << file;
}
return file;
return configPath() + Theme::instance()->configFileName();
}
bool MirallConfigFile::exists()
@@ -233,47 +212,10 @@ QString MirallConfigFile::defaultConnection() const
return Theme::instance()->appName();
}
bool MirallConfigFile::connectionExists( const QString& conn )
{
QString con = conn;
if( conn.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(conn);
return settings.contains( QLatin1String(urlC) );
}
void MirallConfigFile::writeOwncloudConfig( const QString& connection,
const QString& url,
AbstractCredentials* credentials)
{
const QString file = configFile();
qDebug() << "*** writing mirall config to " << file;
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( connection );
settings.setValue( QLatin1String(urlC), url );
settings.setValue(QLatin1String(authTypeC), credentials->authType());
credentialsPerConfig.insert(_customHandle, SharedCreds(credentials));
settings.sync();
// check the perms, only read-write for the owner.
QFile::setPermissions( file, QFile::ReadOwner|QFile::WriteOwner );
// Store credentials temporar until the config is finalized.
//ownCloudInfo::instance()->setCredentials( user, passwd, _customHandle );
}
void MirallConfigFile::storeData(const QString& group, const QString& key, const QVariant& value)
{
const QString con(group.isEmpty() ? defaultConnection() : group);
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(con);
settings.setValue(key, value);
@@ -284,7 +226,6 @@ QVariant MirallConfigFile::retrieveData(const QString& group, const QString& key
{
const QString con(group.isEmpty() ? defaultConnection() : group);
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(con);
return settings.value(key);
@@ -294,7 +235,6 @@ void MirallConfigFile::removeData(const QString& group, const QString& key)
{
const QString con(group.isEmpty() ? defaultConnection() : group);
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(con);
settings.remove(key);
@@ -304,7 +244,6 @@ bool MirallConfigFile::dataExists(const QString& group, const QString& key) cons
{
const QString con(group.isEmpty() ? defaultConnection() : group);
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup(con);
return settings.contains(key);
@@ -313,66 +252,23 @@ bool MirallConfigFile::dataExists(const QString& group, const QString& key) cons
QByteArray MirallConfigFile::caCerts( )
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
return settings.value( QLatin1String(caCertsKeyC) ).toByteArray();
}
void MirallConfigFile::setCaCerts( const QByteArray & certs )
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setIniCodec( "UTF-8" );
settings.setValue( QLatin1String(caCertsKeyC), certs );
settings.sync();
}
void MirallConfigFile::removeConnection( const QString& connection )
{
QString con( connection );
if( connection.isEmpty() ) con = defaultConnection();
qDebug() << " removing the config file for connection " << con;
// Currently its just removing the entire config file
// TODO: Eh? Shouldn't it try to load a file under configFile() and set it to INI?
QSettings settings;
settings.setIniCodec( "UTF-8" );
settings.beginGroup( con );
settings.remove(QString::null); // removes all content from the group
settings.sync();
}
/*
* returns the configured owncloud url if its already configured, otherwise an empty
* string.
* The returned url always has a trailing hash.
*/
QString MirallConfigFile::ownCloudUrl( const QString& connection) const
{
QString con( connection );
if( connection.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
QString url = settings.value( QLatin1String(urlC) ).toString();
if( ! url.isEmpty() ) {
if( ! url.endsWith(QLatin1Char('/'))) url.append(QLatin1String("/"));
}
return url;
}
int MirallConfigFile::remotePollInterval( const QString& connection ) const
{
QString con( connection );
if( connection.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
int remoteInterval = settings.value( QLatin1String(remotePollIntervalC), DEFAULT_REMOTE_POLL_INTERVAL ).toInt();
@@ -393,7 +289,6 @@ void MirallConfigFile::setRemotePollInterval(int interval, const QString &connec
return;
}
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
settings.setValue(QLatin1String(remotePollIntervalC), interval );
settings.sync();
@@ -406,7 +301,6 @@ quint64 MirallConfigFile::forceSyncInterval(const QString& connection) const
QString con( connection );
if( connection.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
quint64 interval = settings.value( QLatin1String(forceSyncIntervalC), 10 * pollInterval ).toULongLong();
@@ -435,7 +329,6 @@ bool MirallConfigFile::ownCloudSkipUpdateCheck( const QString& connection ) cons
if( connection.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
bool skipIt = settings.value( QLatin1String(skipUpdateCheckC), false ).toBool();
@@ -449,7 +342,6 @@ void MirallConfigFile::setOwnCloudSkipUpdateCheck( bool skip, const QString& con
if( connection.isEmpty() ) con = defaultConnection();
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup( con );
settings.setValue( QLatin1String(skipUpdateCheckC), QVariant(skip) );
@@ -460,67 +352,16 @@ void MirallConfigFile::setOwnCloudSkipUpdateCheck( bool skip, const QString& con
int MirallConfigFile::maxLogLines() const
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
return settings.value( QLatin1String(maxLogLinesC), DEFAULT_MAX_LOG_LINES ).toInt();
}
void MirallConfigFile::setMaxLogLines( int lines )
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(QLatin1String(maxLogLinesC), lines);
settings.sync();
}
// remove a custom config file.
void MirallConfigFile::cleanupCustomConfig()
{
if( _customHandle.isEmpty() ) {
qDebug() << "SKipping to erase the main configuration.";
return;
}
QString file = configFile();
if( QFile::exists( file ) ) {
QFile::remove( file );
}
}
// accept a config identified by the customHandle as general config.
void MirallConfigFile::acceptCustomConfig()
{
if( _customHandle.isEmpty() ) {
qDebug() << "WRN: Custom Handle is empty. Can not accept.";
return;
}
QString srcConfig = configFile(); // this considers the custom handle
credentialsPerConfig.insert(QString(), credentialsPerConfig[_customHandle]);
credentialsPerConfig.remove(_customHandle);
_customHandle.clear();
QString targetConfig = configFile();
QString targetBak = targetConfig + QLatin1String(".bak");
bool bakOk = false;
// remove an evtl existing old config backup.
if( QFile::exists( targetBak ) ) {
QFile::remove( targetBak );
}
// create a backup of the current config.
bakOk = QFile::rename( targetConfig, targetBak );
// move the custom config to the master place.
if( ! QFile::rename( srcConfig, targetConfig ) ) {
// if the move from custom to master failed, put old backup back.
if( bakOk ) {
QFile::rename( targetBak, targetConfig );
}
}
QFile::remove( targetBak );
credentialsPerConfig[QString()]->persistForUrl(ownCloudUrl());
}
void MirallConfigFile::setProxyType(int proxyType,
const QString& host,
int port, bool needsAuth,
@@ -528,7 +369,6 @@ void MirallConfigFile::setProxyType(int proxyType,
const QString& pass)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(QLatin1String(proxyTypeC), proxyType);
@@ -547,7 +387,6 @@ QVariant MirallConfigFile::getValue(const QString& param, const QString& group,
const QVariant& defaultValue) const
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
if (!group.isEmpty())
settings.beginGroup(group);
@@ -557,7 +396,6 @@ QVariant MirallConfigFile::getValue(const QString& param, const QString& group,
void MirallConfigFile::setValue(const QString& key, const QVariant &value)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(key, value);
}
@@ -636,20 +474,13 @@ void MirallConfigFile::setDownloadLimit(int kbytes)
bool MirallConfigFile::monoIcons() const
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
return settings.value(QLatin1String(monoIconsC), false).toBool();
}
void MirallConfigFile::setMonoIcons(bool useMonoIcons)
{
QSettings settings(configFile(), QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.setValue(QLatin1String(monoIconsC), useMonoIcons);
}
AbstractCredentials* MirallConfigFile::getCredentials() const
{
return credentialsPerConfig[_customHandle].data();
}
}

View File

@@ -28,29 +28,19 @@ class AbstractCredentials;
class MirallConfigFile
{
public:
MirallConfigFile( const QString& appendix = QString(), bool useOldConfig = false );
MirallConfigFile();
enum Scope { UserScope, SystemScope };
QString configPath() const;
QString configPathWithAppName() const;
QString configFile() const;
QString excludeFile(Scope scope) const;
bool exists();
bool connectionExists( const QString& = QString() );
QString defaultConnection() const;
void writeOwncloudConfig( const QString& connection,
const QString& url,
AbstractCredentials* credentials);
AbstractCredentials* getCredentials() const;
void removeConnection( const QString& connection = QString() );
QString ownCloudUrl( const QString& connection = QString() ) const;
// the certs do not depend on a connection.
QByteArray caCerts();
void setCaCerts( const QByteArray& );
@@ -75,11 +65,6 @@ public:
/* Force sync interval, in milliseconds */
quint64 forceSyncInterval(const QString &connection = QString()) const;
// Custom Config: accept the custom config to become the main one.
void acceptCustomConfig();
// Custom Config: remove the custom config file.
void cleanupCustomConfig();
bool monoIcons() const;
void setMonoIcons(bool);
@@ -136,8 +121,6 @@ private:
static bool _askedUser;
static QString _oCVersion;
static QString _confDir;
static QMap< QString, SharedCreds > credentialsPerConfig;
QString _customHandle;
};
}

534
src/mirall/networkjobs.cpp Normal file
View File

@@ -0,0 +1,534 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Daniel Molkentin <danimo@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 <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSslConfiguration>
#include <QBuffer>
#include <QXmlStreamReader>
#include <QStringList>
#include <QStack>
#include <QTimer>
#include <QMutex>
#include <QDebug>
#include "json.h"
#include "mirall/networkjobs.h"
#include "mirall/account.h"
#include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h"
namespace Mirall {
AbstractNetworkJob::AbstractNetworkJob(Account *account, const QString &path, QObject *parent)
: QObject(parent)
, _ignoreCredentialFailure(false)
, _reply(0)
, _account(account)
, _path(path)
, _timer(0)
{
}
void AbstractNetworkJob::setReply(QNetworkReply *reply)
{
if (_reply) {
_reply->deleteLater();
}
_reply = reply;
}
void AbstractNetworkJob::setTimeout(qint64 msec)
{
qDebug() << Q_FUNC_INFO << msec;
if (_timer)
_timer->deleteLater();
_timer = new QTimer(this);
_timer->setSingleShot(true);
connect(_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
_timer->start(msec);
}
void AbstractNetworkJob::resetTimeout()
{
qint64 interval = _timer->interval();
_timer->stop();
_timer->start(interval);
}
void AbstractNetworkJob::setIgnoreCredentialFailure(bool ignore)
{
_ignoreCredentialFailure = ignore;
}
void AbstractNetworkJob::setAccount(Account *account)
{
_account = account;
}
void AbstractNetworkJob::setPath(const QString &path)
{
_path = path;
}
void AbstractNetworkJob::slotError(QNetworkReply::NetworkError)
{
qDebug() << metaObject()->className() << "Error:" << _reply->errorString();
emit networkError(_reply);
}
void AbstractNetworkJob::setupConnections(QNetworkReply *reply)
{
connect(reply, SIGNAL(finished()), SLOT(slotFinished()));
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError(QNetworkReply::NetworkError)));
}
QNetworkReply* AbstractNetworkJob::davRequest(const QByteArray &verb, const QString &relPath,
QNetworkRequest req, QIODevice *data)
{
return _account->davRequest(verb, relPath, req, data);
}
QNetworkReply *AbstractNetworkJob::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
{
return _account->davRequest(verb, url, req, data);
}
QNetworkReply* AbstractNetworkJob::getRequest(const QString &relPath)
{
return _account->getRequest(relPath);
}
QNetworkReply *AbstractNetworkJob::getRequest(const QUrl &url)
{
return _account->getRequest(url);
}
QNetworkReply *AbstractNetworkJob::headRequest(const QString &relPath)
{
return _account->headRequest(relPath);
}
QNetworkReply *AbstractNetworkJob::headRequest(const QUrl &url)
{
return _account->headRequest(url);
}
void AbstractNetworkJob::slotFinished()
{
static QMutex mutex;
AbstractCredentials *creds = _account->credentials();
if (creds->stillValid(_reply) || _ignoreCredentialFailure) {
finished();
} else {
// If other jobs that still were created from before
// the account was put offline by the code below,
// we do want them to fail silently while we
// query the user
if (mutex.tryLock()) {
Account *a = account();
bool fetched = creds->fetchFromUser(a);
if (fetched) {
a->setState(Account::Connected);
}
mutex.unlock();
}
}
deleteLater();
}
AbstractNetworkJob::~AbstractNetworkJob() {
_reply->deleteLater();
}
void AbstractNetworkJob::start()
{
qDebug() << "!!!" << metaObject()->className() << "created for" << account()->url() << "querying" << path();
}
/*********************************************************************************************/
RequestEtagJob::RequestEtagJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void RequestEtagJob::start()
{
QNetworkRequest req;
if (path().isEmpty() || path() == QLatin1String("/")) {
/* For the root directory, we need to query the etags of all the sub directories
* because, at the time I am writing this comment (Owncloud 5.0.9), the etag of the
* root directory is not updated when the sub directories changes */
req.setRawHeader("Depth", "1");
} else {
req.setRawHeader("Depth", "0");
}
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:getetag/>\n"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
// assumes ownership
setReply(davRequest("PROPFIND", path(), req, buf));
buf->setParent(reply());
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) {
qDebug() << "getting etag: request network error: " << reply()->errorString();
}
AbstractNetworkJob::start();
}
void RequestEtagJob::finished()
{
if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QString etag;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("getetag")) {
etag += reader.readElementText();
}
}
}
emit etagRetreived(etag);
}
}
/*********************************************************************************************/
MkColJob::MkColJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void MkColJob::start()
{
// assumes ownership
QNetworkReply *reply = davRequest("MKCOL", path());
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
}
void MkColJob::finished()
{
emit finished(reply()->error());
}
/*********************************************************************************************/
LsColJob::LsColJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void LsColJob::start()
{
QNetworkRequest req;
req.setRawHeader("Depth", "1");
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:resourcetype/>\n"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = davRequest("PROPFIND", path(), req, buf);
buf->setParent(reply);
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
}
void LsColJob::finished()
{
if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QStringList folders;
QString currentItem;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("href")) {
currentItem = reader.readElementText();
} else if (name == QLatin1String("collection") &&
!currentItem.isEmpty()) {
folders.append(QUrl::fromEncoded(currentItem.toLatin1()).path());
currentItem.clear();
}
}
}
emit directoryListing(folders);
}
}
/*********************************************************************************************/
CheckServerJob::CheckServerJob(Account *account, bool followRedirect, QObject *parent)
: AbstractNetworkJob(account, QLatin1String("status.php") , parent)
, _followRedirects(followRedirect)
, _redirectCount(0)
{
}
void CheckServerJob::start()
{
setReply(getRequest(path()));
setupConnections(reply());
AbstractNetworkJob::start();
}
void CheckServerJob::slotTimeout()
{
qDebug() << "TIMEOUT" << Q_FUNC_INFO;
if (reply()->isRunning())
emit timeout(reply()->url());
}
QString CheckServerJob::version(const QVariantMap &info)
{
return info.value(QLatin1String("version")).toString();
}
QString CheckServerJob::versionString(const QVariantMap &info)
{
return info.value(QLatin1String("versionstring")).toString();
}
bool CheckServerJob::installed(const QVariantMap &info)
{
return info.value(QLatin1String("installed")).toBool();
}
void CheckServerJob::finished()
{
account()->setCertificateChain(reply()->sslConfiguration().peerCertificateChain());
// ### the qDebugs here should be exported via displayErrors() so they
// ### can be presented to the user if the job executor has a GUI
QUrl requestedUrl = reply()->request().url();
QUrl redirectUrl = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!redirectUrl.isEmpty()) {
_redirectCount++;
if (requestedUrl.scheme() == QLatin1String("https") &&
redirectUrl.scheme() == QLatin1String("http")) {
qDebug() << Q_FUNC_INFO << "HTTPS->HTTP downgrade detected!";
} else if (requestedUrl == redirectUrl || _redirectCount >= maxRedirects()) {
qDebug() << Q_FUNC_INFO << "Redirect loop detected!";
} else {
resetTimeout();
setReply(getRequest(redirectUrl));
setupConnections(reply());
return;
}
}
bool success = false;
QVariantMap status = QtJson::parse(QString::fromUtf8(reply()->readAll()), success).toMap();
// empty or invalid response
if (!success || status.isEmpty()) {
qDebug() << "status.php from server is not valid JSON!";
}
qDebug() << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply();
if( status.contains("installed")
&& status.contains("version")
&& status.contains("versionstring") ) {
emit instanceFound(reply()->url(), status);
} else {
qDebug() << "No proper answer on " << requestedUrl;
}
}
/*********************************************************************************************/
PropfindJob::PropfindJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void PropfindJob::start()
{
QList<QByteArray> properties = _properties;
if (properties.isEmpty()) {
properties << "allprop";
}
QNetworkRequest req;
req.setRawHeader("Depth", "0");
QByteArray propStr;
foreach (const QByteArray &prop, properties) {
propStr += " <d:" + prop + " />\n";
}
QByteArray xml = "<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
+ propStr +
" </d:prop>\n"
"</d:propfind>\n";
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
setReply(davRequest("PROPFIND", path(), req, buf));
buf->setParent(reply());
setupConnections(reply());
AbstractNetworkJob::start();
}
void PropfindJob::setProperties(QList<QByteArray> properties)
{
_properties = properties;
}
QList<QByteArray> PropfindJob::properties() const
{
return _properties;
}
void PropfindJob::finished()
{
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (http_result_code == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QVariantMap items;
// introduced to nesting is ignored
QStack<QString> curElement;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
if (curElement.isEmpty()) {
curElement.push(reader.name().toString());
items.insert(reader.name().toString(), reader.text().toString());
}
}
if (type == QXmlStreamReader::EndElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
if(curElement.top() == reader.name()) {
curElement.pop();
}
}
}
emit result(items);
} else {
qDebug() << "Quota request *not* successful, http result code is " << http_result_code;
}
}
/*********************************************************************************************/
EntityExistsJob::EntityExistsJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void EntityExistsJob::start()
{
setReply(headRequest(path()));
setupConnections(reply());
AbstractNetworkJob::start();
}
void EntityExistsJob::finished()
{
emit exists(reply());
}
/*********************************************************************************************/
CheckQuotaJob::CheckQuotaJob(Account *account, const QString &path, QObject *parent)
: AbstractNetworkJob(account, path, parent)
{
}
void CheckQuotaJob::start()
{
QNetworkRequest req;
req.setRawHeader("Depth", "0");
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:quota-available-bytes/>\n"
" <d:quota-used-bytes/>\n"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer(this);
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
// assumes ownership
setReply(davRequest("PROPFIND", path(), req, buf));
buf->setParent(reply());
setupConnections(reply());
AbstractNetworkJob::start();
}
void CheckQuotaJob::finished()
{
if (reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
// Parse DAV response
QXmlStreamReader reader(reply());
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
qint64 quotaAvailableBytes = 0;
qint64 quotaUsedBytes = 0;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("quota-available-bytes")) {
quotaAvailableBytes = reader.readElementText().toLongLong();
} else if (name == QLatin1String("quota-used-bytes")) {
quotaUsedBytes = reader.readElementText().toLongLong();
}
}
}
qint64 total = quotaUsedBytes + quotaAvailableBytes;
emit quotaRetrieved(total, quotaUsedBytes);
}
}
} // namespace Mirall

217
src/mirall/networkjobs.h Normal file
View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
* Copyright (C) by Daniel Molkentin <danimo@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.
*/
#ifndef NETWORKJOBS_H
#define NETWORKJOBS_H
#include <QObject>
#include <QNetworkRequest>
#include <QNetworkReply>
class QUrl;
class QTimer;
namespace Mirall {
class Account;
class AbstractSslErrorHandler;
/**
* @brief The AbstractNetworkJob class
*/
class AbstractNetworkJob : public QObject {
Q_OBJECT
public:
explicit AbstractNetworkJob(Account *account, const QString &path, QObject* parent = 0);
virtual ~AbstractNetworkJob();
virtual void start();
void setAccount(Account *account);
Account* account() const { return _account; }
void setPath(const QString &path);
QString path() const { return _path; }
void setReply(QNetworkReply *reply);
QNetworkReply* reply() const { return _reply; }
void setTimeout(qint64 msec);
void resetTimeout();
void setIgnoreCredentialFailure(bool ignore);
bool ignoreCredentialFailure() const { return _ignoreCredentialFailure; }
signals:
void networkError(QNetworkReply *reply);
protected:
void setupConnections(QNetworkReply *reply);
QNetworkReply* davRequest(const QByteArray& verb, const QString &relPath,
QNetworkRequest req = QNetworkRequest(),
QIODevice *data = 0);
QNetworkReply* davRequest(const QByteArray& verb, const QUrl &url,
QNetworkRequest req = QNetworkRequest(),
QIODevice *data = 0);
QNetworkReply* getRequest(const QString &relPath);
QNetworkReply* getRequest(const QUrl &url);
QNetworkReply* headRequest(const QString &relPath);
QNetworkReply* headRequest(const QUrl &url);
int maxRedirects() const { return 10; }
virtual void finished() = 0;
private slots:
void slotFinished();
void slotError(QNetworkReply::NetworkError);
virtual void slotTimeout() {}
private:
bool _ignoreCredentialFailure;
QNetworkReply *_reply;
Account *_account;
QString _path;
QTimer *_timer;
};
/**
* @brief The EntityExistsJob class
*/
class EntityExistsJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit EntityExistsJob(Account *account, const QString &path, QObject* parent = 0);
void start();
signals:
void exists(QNetworkReply*);
private slots:
virtual void finished();
};
/**
* @brief The LsColJob class
*/
class LsColJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit LsColJob(Account *account, const QString &path, QObject *parent = 0);
void start();
signals:
void directoryListing(const QStringList &items);
private slots:
virtual void finished();
};
/**
* @brief The PropfindJob class
*/
class PropfindJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit PropfindJob(Account *account, const QString &path, QObject *parent = 0);
void start();
void setProperties(QList<QByteArray> properties);
QList<QByteArray> properties() const;
signals:
void result(const QVariantMap &values);
private slots:
virtual void finished();
private:
QList<QByteArray> _properties;
};
/**
* @brief The MkColJob class
*/
class MkColJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit MkColJob(Account *account, const QString &path, QObject *parent = 0);
void start();
signals:
void finished(QNetworkReply::NetworkError);
private slots:
virtual void finished();
};
/**
* @brief The CheckServerJob class
*/
class CheckServerJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit CheckServerJob(Account *account, bool followRedirect = false, QObject *parent = 0);
void start();
static QString version(const QVariantMap &info);
static QString versionString(const QVariantMap &info);
static bool installed(const QVariantMap &info);
signals:
void instanceFound(const QUrl&url, const QVariantMap &info);
void timeout(const QUrl&url);
private slots:
virtual void finished();
virtual void slotTimeout();
private:
bool _followRedirects;
int _redirectCount;
};
/**
* @brief The RequestEtagJob class
*/
class RequestEtagJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit RequestEtagJob(Account *account, const QString &path, QObject *parent = 0);
void start();
signals:
void etagRetreived(const QString &etag);
private slots:
virtual void finished();
};
/**
* @brief The CheckQuota class
*/
class CheckQuotaJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit CheckQuotaJob(Account *account, const QString &path, QObject *parent = 0);
void start();
signals:
void quotaRetrieved(qint64 totalBytes, qint64 availableBytes);
private slots:
virtual void finished();
};
} // namespace Mirall
#endif // NETWORKJOBS_H

View File

@@ -1,101 +0,0 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
*
* 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 "mirall/networklocation.h"
#include <QProcess>
namespace Mirall
{
NetworkLocation::NetworkLocation(const QString &encoded)
: _encoded(encoded)
{
}
NetworkLocation::NetworkLocation()
{
}
NetworkLocation::~NetworkLocation()
{
}
/**
* for now our data is just the MAC address of the default gateway
*/
NetworkLocation NetworkLocation::currentLocation()
{
QProcess ip;
ip.start(QLatin1String("/sbin/ip"), QStringList() << QLatin1String("route"));
if (!ip.waitForStarted())
return NetworkLocation();
if (!ip.waitForFinished())
return NetworkLocation();
QByteArray gwIp;
while (ip.canReadLine()) {
QByteArray line = ip.readLine();
if ( line.startsWith("default") ) { // krazy:exclude=strings
QList<QByteArray> parts = line.split(' ');
gwIp = parts[2];
break;
}
}
if (gwIp.isEmpty())
return NetworkLocation();
QProcess arp;
arp.start(QLatin1String("/sbin/arp"), QStringList() << QLatin1String("-a"));
if (!arp.waitForStarted())
return NetworkLocation();
if (!arp.waitForFinished())
return NetworkLocation();
QByteArray gwMAC;
while (arp.canReadLine()) {
QByteArray line = arp.readLine();
if (line.contains(gwIp)) {
QList<QByteArray> parts = line.split(' ');
gwMAC = parts[3];
break;
}
}
if (gwMAC.isEmpty())
return NetworkLocation();
return NetworkLocation(QString::fromLatin1(gwMAC));
}
NetworkLocation::Proximity NetworkLocation::compareWith(const NetworkLocation &location) const
{
if (location.encoded().isEmpty() || encoded().isEmpty())
return Unknown;
if (location.encoded() == encoded())
return Same;
return Different;
}
QString NetworkLocation::encoded() const
{
return _encoded;
}
}

View File

@@ -1,57 +0,0 @@
/*
* Copyright (C) by Duncan Mac-Vicar P. <duncan@kde.org>
*
* 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.
*/
#ifndef MIRALL_NETWORK_LOCATION_H
#define MIRALL_NETWORK_LOCATION_H
#include <QString>
namespace Mirall {
class NetworkLocation
{
public:
enum Proximity {
Unknown,
Same,
Different
};
/**
* constructs a location from its encoded
* form
*/
NetworkLocation(const QString &encoded);
/**
* Unknown location
*/
NetworkLocation();
~NetworkLocation();
QString encoded() const;
static NetworkLocation currentLocation();
Proximity compareWith(const NetworkLocation &location) const;
private:
QString _encoded;
};
}
#endif

View File

@@ -5,7 +5,7 @@
#include <QString>
#include <QDomElement>
#include <QtXml/QXmlStreamWriter>
#include <QXmlStreamWriter>
namespace Mirall {

528
src/mirall/owncloudgui.cpp Normal file
View File

@@ -0,0 +1,528 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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 "mirall/application.h"
#include "mirall/owncloudgui.h"
#include "mirall/theme.h"
#include "mirall/folderman.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/utility.h"
#include "mirall/progressdispatcher.h"
#include "mirall/owncloudsetupwizard.h"
#include "mirall/settingsdialog.h"
#include "mirall/logger.h"
#include "mirall/logbrowser.h"
#include "mirall/account.h"
#include "creds/abstractcredentials.h"
#include <QDesktopServices>
#include <QMessageBox>
#include <QSignalMapper>
namespace Mirall {
ownCloudGui::ownCloudGui(Application *parent) :
QObject(parent),
_tray(0),
_settingsDialog(0),
_logBrowser(0),
_contextMenu(0),
_recentActionsMenu(0),
_quotaInfo(0),
_folderOpenActionMapper(new QSignalMapper(this)),
_recentItemsMapper(new QSignalMapper(this)),
_app(parent)
{
_tray = new Systray();
_tray->setParent(this);
_tray->setIcon( Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true ) );
connect(_tray.data(), SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
SLOT(slotTrayClicked(QSystemTrayIcon::ActivationReason)));
setupActions();
setupContextMenu();
_tray->show();
/* use a signal mapper to map the open requests to the alias names */
connect(_folderOpenActionMapper, SIGNAL(mapped(QString)),
this, SLOT(slotFolderOpenAction(QString)));
connect(_recentItemsMapper, SIGNAL(mapped(QString)),
this, SLOT(slotOpenPath(QString)));
ProgressDispatcher *pd = ProgressDispatcher::instance();
connect( pd, SIGNAL(progressInfo(QString,Progress::Info)), this,
SLOT(slotUpdateProgress(QString,Progress::Info)) );
connect( pd, SIGNAL(progressSyncProblem(QString,Progress::SyncProblem)),
SLOT(slotProgressSyncProblem(QString,Progress::SyncProblem)));
FolderMan *folderMan = FolderMan::instance();
connect( folderMan, SIGNAL(folderSyncStateChange(QString)),
this,SLOT(slotSyncStateChange(QString)));
connect( Logger::instance(), SIGNAL(guiLog(QString,QString)),
SLOT(slotShowTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(optionalGuiLog(QString,QString)),
SLOT(slotShowOptionalTrayMessage(QString,QString)));
connect( Logger::instance(), SIGNAL(guiMessage(QString,QString)),
SLOT(slotShowGuiMessage(QString,QString)));
}
// This should rather be in application.... or rather in MirallConfigFile?
void ownCloudGui::slotOpenSettingsDialog( bool openSettings )
{
// if account is set up, start the configuration wizard.
if( AccountManager::instance()->account() ) {
if( openSettings ) {
if (_settingsDialog.isNull()) {
slotShowSettings();
} else {
_settingsDialog->close();
}
}
} else {
qDebug() << "No configured folders yet, starting setup wizard";
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)));
}
}
QuotaInfo *ownCloudGui::quotaInfo() const
{
return _quotaInfo;
}
void ownCloudGui::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
{
// A click on the tray icon should only open the status window on Win and
// Linux, not on Mac. They want a menu entry.
#if !defined Q_OS_MAC
if( reason == QSystemTrayIcon::Trigger ) {
slotOpenSettingsDialog(true); // start settings if config is existing.
}
#endif
}
void ownCloudGui::slotSyncStateChange( const QString& alias )
{
FolderMan *folderMan = FolderMan::instance();
const SyncResult& result = folderMan->syncResult( alias );
slotComputeOverallSyncStatus();
qDebug() << "Sync state changed for folder " << alias << ": " << result.statusString();
// Promote sync result to settings-dialog for sync protocol?
// if( _progressDialog ) {
// _progressDialog->setSyncResult(result);
// }
if (result.status() == SyncResult::Success || result.status() == SyncResult::Error) {
Logger::instance()->enterNextLogFile();
}
}
void ownCloudGui::slotFoldersChanged()
{
slotComputeOverallSyncStatus();
setupContextMenu();
}
void ownCloudGui::slotOpenPath(const QString &path)
{
Utility::showInFileManager(path);
}
void ownCloudGui::slotAccountStateChanged()
{
setupContextMenu();
}
void ownCloudGui::startupConnected( bool connected, const QStringList& fails )
{
FolderMan *folderMan = FolderMan::instance();
if( connected ) {
qDebug() << "######## connected to ownCloud Server!";
folderMan->setSyncEnabled(true);
_tray->setIcon( Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true ) );
_tray->show();
}
_startupFails = fails; // store that for the settings dialog once it appears.
}
void ownCloudGui::slotComputeOverallSyncStatus()
{
if (Account *a = AccountManager::instance()->account()) {
if (a->state() == Account::SignedOut) {
_tray->setIcon(Theme::instance()->syncStateIcon( SyncResult::Unavailable, true));
_tray->setToolTip(tr("Please sign in"));
return;
}
}
// display the info of the least successful sync (eg. not just display the result of the latest sync
QString trayMessage;
FolderMan *folderMan = FolderMan::instance();
Folder::Map map = folderMan->map();
SyncResult overallResult = FolderMan::accountStatus(map.values());
// if there have been startup problems, show an error message.
if( !_settingsDialog.isNull() )
_settingsDialog->setGeneralErrors( _startupFails );
if( !_startupFails.isEmpty() ) {
trayMessage = _startupFails.join(QLatin1String("\n"));
QIcon statusIcon;
if (_app->_startupNetworkError) {
statusIcon = Theme::instance()->syncStateIcon( SyncResult::NotYetStarted, true );
} else {
statusIcon = Theme::instance()->syncStateIcon( SyncResult::Error, true );
}
_tray->setIcon( statusIcon );
_tray->setToolTip(trayMessage);
} else {
// create the tray blob message, check if we have an defined state
if( overallResult.status() != SyncResult::Undefined ) {
QStringList allStatusStrings;
foreach(Folder* folder, map.values()) {
qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncEnabled());
allStatusStrings += tr("Folder %1: %2").arg(folder->alias(), folderMessage);
}
if( ! allStatusStrings.isEmpty() )
trayMessage = allStatusStrings.join(QLatin1String("\n"));
else
trayMessage = tr("No sync folders configured.");
QIcon statusIcon = Theme::instance()->syncStateIcon( overallResult.status(), true);
_tray->setIcon( statusIcon );
_tray->setToolTip(trayMessage);
}
}
}
void ownCloudGui::setupContextMenu()
{
FolderMan *folderMan = FolderMan::instance();
Account *a = AccountManager::instance()->account();
bool isConfigured = (a != 0);
_actionOpenoC->setEnabled(isConfigured);
bool isConnected = false;
if (isConfigured) {
isConnected = (a->state() == Account::Connected);
}
if ( _contextMenu ) {
_contextMenu->clear();
_recentActionsMenu->clear();
_recentActionsMenu->addAction(tr("None."));
_recentActionsMenu->addAction(_actionRecent);
} else {
_contextMenu = new QMenu(_contextMenu);
_recentActionsMenu = new QMenu(tr("Recent Changes"));
// this must be called only once after creating the context menu, or
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
_tray->setContextMenu(_contextMenu);
}
_contextMenu->setTitle(Theme::instance()->appNameGUI() );
_contextMenu->addAction(_actionOpenoC);
int folderCnt = folderMan->map().size();
// add open actions for all sync folders to the tray menu
if( Theme::instance()->singleSyncFolder() ) {
// there should be exactly one folder. No sync-folder add action will be shown.
QStringList li = folderMan->map().keys();
if( li.size() == 1 ) {
Folder *folder = folderMan->map().value(li.first());
if( folder ) {
// if there is singleFolder mode, a generic open action is displayed.
QAction *action = new QAction( tr("Open %1 folder").arg(Theme::instance()->appNameGUI()), this);
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
} else {
// show a grouping with more than one folder.
if ( folderCnt > 1) {
_contextMenu->addAction(tr("Managed Folders:"))->setDisabled(true);
}
foreach (Folder *folder, folderMan->map() ) {
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->alias()), this );
connect( action, SIGNAL(triggered()),_folderOpenActionMapper,SLOT(map()));
_folderOpenActionMapper->setMapping( action, folder->alias() );
_contextMenu->addAction(action);
}
}
_contextMenu->addSeparator();
if (isConfigured && isConnected) {
_contextMenu->addAction(_actionQuota);
_contextMenu->addSeparator();
_contextMenu->addAction(_actionStatus);
_contextMenu->addMenu(_recentActionsMenu);
_contextMenu->addSeparator();
}
_contextMenu->addAction(_actionSettings);
if (!Theme::instance()->helpUrl().isEmpty()) {
_contextMenu->addAction(_actionHelp);
}
_contextMenu->addSeparator();
if (isConfigured && isConnected) {
_contextMenu->addAction(_actionLogout);
} else {
_contextMenu->addAction(_actionLogin);
}
_contextMenu->addAction(_actionQuit);
// Populate once at start
slotRebuildRecentMenus();
}
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
{
if( _tray )
_tray->showMessage(title, msg);
else
qDebug() << "Tray not ready: " << msg;
}
void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QString &msg)
{
MirallConfigFile cfg;
if (cfg.optionalDesktopNotifications()) {
slotShowTrayMessage(title, msg);
}
}
/*
* open the folder with the given Alais
*/
void ownCloudGui::slotFolderOpenAction( const QString& alias )
{
Folder *f = FolderMan::instance()->folder(alias);
qDebug() << "opening local url " << f->path();
if( f ) {
QUrl url(f->path(), QUrl::TolerantMode);
url.setScheme( QLatin1String("file") );
#ifdef Q_OS_WIN32
// work around a bug in QDesktopServices on Win32, see i-net
QString filePath = f->path();
if (filePath.startsWith(QLatin1String("\\\\")) || filePath.startsWith(QLatin1String("//")))
url.setUrl(QDir::toNativeSeparators(filePath));
else
url = QUrl::fromLocalFile(filePath);
#endif
QDesktopServices::openUrl(url);
}
}
void ownCloudGui::setupActions()
{
_actionOpenoC = new QAction(tr("Open %1 in browser").arg(Theme::instance()->appNameGUI()), this);
QObject::connect(_actionOpenoC, SIGNAL(triggered(bool)), SLOT(slotOpenOwnCloud()));
_actionQuota = new QAction(tr("Calculating quota..."), this);
_actionQuota->setEnabled( false );
_actionStatus = new QAction(tr("Unknown status"), this);
_actionStatus->setEnabled( false );
_actionSettings = new QAction(tr("Settings..."), this);
_actionRecent = new QAction(tr("Details..."), this);
_actionRecent->setEnabled( true );
QObject::connect(_actionRecent, SIGNAL(triggered(bool)), SLOT(slotShowSyncProtocol()));
QObject::connect(_actionSettings, SIGNAL(triggered(bool)), SLOT(slotShowSettings()));
_actionHelp = new QAction(tr("Help"), this);
QObject::connect(_actionHelp, SIGNAL(triggered(bool)), SLOT(slotHelp()));
_actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this);
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit()));
_actionLogin = new QAction(tr("Sign in..."), this);
connect(_actionLogin, SIGNAL(triggered()), _app, SLOT(slotLogin()));
_actionLogout = new QAction(tr("Sign out"), this);
connect(_actionLogout, SIGNAL(triggered()), _app, SLOT(slotLogout()));
_quotaInfo = new QuotaInfo(this);
connect(_quotaInfo, SIGNAL(quotaUpdated(qint64,qint64)), SLOT(slotRefreshQuotaDisplay(qint64,qint64)));
}
void ownCloudGui::slotRefreshQuotaDisplay( qint64 total, qint64 used )
{
if (total == 0) {
_actionQuota->setText(tr("Quota n/a"));
return;
}
double percent = used/(double)total*100;
QString percentFormatted = Utility::compactFormatDouble(percent, 1);
QString totalFormatted = Utility::octetsToString(total);
_actionQuota->setText(tr("%1% of %2 in use").arg(percentFormatted).arg(totalFormatted));
}
void ownCloudGui::slotProgressSyncProblem(const QString& folder, const Progress::SyncProblem& problem)
{
Q_UNUSED(folder);
Q_UNUSED(problem);
// display a warn icon if warnings happend.
QIcon warnIcon(":/mirall/resources/warning-16");
_actionRecent->setIcon(warnIcon);
slotRebuildRecentMenus();
}
void ownCloudGui::slotRebuildRecentMenus()
{
_recentActionsMenu->clear();
const QList<Progress::Info>& progressInfoList = ProgressDispatcher::instance()->recentChangedItems(5);
if( progressInfoList.size() == 0 ) {
_recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false);
} else {
QListIterator<Progress::Info> i(progressInfoList);
while(i.hasNext()) {
Progress::Info info = i.next();
QString kindStr = Progress::asResultString(info.kind);
QString timeStr = info.timestamp.toString("hh:mm");
QString actionText = tr("%1 (%2, %3)").arg(info.current_file).arg(kindStr).arg(timeStr);
QAction *action = _recentActionsMenu->addAction( actionText );
Folder *folder = FolderMan::instance()->folder(info.folder);
if (folder) {
QString fullPath = folder->path() + '/' + info.current_file;
if (QFile(fullPath).exists()) {
_recentItemsMapper->setMapping(action, fullPath);
connect(action, SIGNAL(triggered()), _recentItemsMapper, SLOT(map()));
} else {
action->setEnabled(false);
}
}
}
}
// add a more... entry.
_recentActionsMenu->addSeparator();
_recentActionsMenu->addAction(_actionRecent);
}
void ownCloudGui::slotUpdateProgress(const QString &folder, const Progress::Info& progress)
{
Q_UNUSED(folder);
// shows an entry in the context menu.
QString curAmount = Utility::octetsToString(progress.overall_current_bytes);
QString totalAmount = Utility::octetsToString(progress.overall_transmission_size);
_actionStatus->setText(tr("Syncing %1 of %2 (%3 of %4) ").arg(progress.current_file_no)
.arg(progress.overall_file_count).arg(curAmount, totalAmount));
// wipe the problem list at start of sync.
if( progress.kind == Progress::StartSync ) {
_actionRecent->setIcon( QIcon() ); // Fixme: Set a "in-progress"-item eventually.
}
// If there was a change in the file list, redo the progress menu.
if( progress.kind == Progress::EndDownload || progress.kind == Progress::EndUpload ||
progress.kind == Progress::EndDelete || progress.kind == Progress::EndRename ) {
slotRebuildRecentMenus();
}
if (progress.kind == Progress::EndSync) {
slotRebuildRecentMenus(); // show errors.
QTimer::singleShot(2000, this, SLOT(slotDisplayIdle()));
}
}
void ownCloudGui::slotDisplayIdle()
{
_actionStatus->setText(tr("Up to date"));
}
void ownCloudGui::slotShowGuiMessage(const QString &title, const QString &message)
{
QMessageBox *msgBox = new QMessageBox;
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setText(message);
msgBox->setWindowTitle(title);
msgBox->setIcon(QMessageBox::Information);
msgBox->open();
}
void ownCloudGui::slotShowSettings()
{
if (_settingsDialog.isNull()) {
_settingsDialog = new SettingsDialog(this);
_settingsDialog->setAttribute( Qt::WA_DeleteOnClose, true );
_settingsDialog->show();
}
_settingsDialog->setGeneralErrors( _startupFails );
Utility::raiseDialog(_settingsDialog.data());
_settingsDialog->slotRefreshResultList();
}
void ownCloudGui::slotShowSyncProtocol()
{
slotShowSettings();
_settingsDialog->showActivityPage();
}
void ownCloudGui::slotShutdown()
{
// those do delete on close
if (!_settingsDialog.isNull()) _settingsDialog->close();
if (!_logBrowser.isNull()) _logBrowser->deleteLater();
}
void ownCloudGui::slotToggleLogBrowser()
{
if (_logBrowser.isNull()) {
// init the log browser.
_logBrowser = new LogBrowser;
// ## TODO: allow new log name maybe?
}
if (_logBrowser->isVisible() ) {
_logBrowser->hide();
} else {
Utility::raiseDialog(_logBrowser);
}
}
void ownCloudGui::slotOpenOwnCloud()
{
if (Account *account = AccountManager::instance()->account()) {
QDesktopServices::openUrl(account->url());
}
}
void ownCloudGui::slotHelp()
{
QDesktopServices::openUrl(QUrl(Theme::instance()->helpUrl()));
}
} // end namespace

109
src/mirall/owncloudgui.h Normal file
View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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.
*/
#ifndef OWNCLOUDGUI_H
#define OWNCLOUDGUI_H
#include "mirall/systray.h"
#include "mirall/connectionvalidator.h"
#include "mirall/progressdispatcher.h"
#include "mirall/quotainfo.h"
#include <QObject>
#include <QPointer>
#include <QAction>
#include <QMenu>
#include <QSignalMapper>
namespace Mirall {
class SettingsDialog;
class Application;
class LogBrowser;
class ownCloudGui : public QObject
{
Q_OBJECT
public:
explicit ownCloudGui(Application *parent = 0);
void setupContextMenu();
void startupConnected(bool connected , const QStringList &fails);
bool checkAccountExists(bool openSettings);
QuotaInfo *quotaInfo() const;
signals:
void setupProxy();
public slots:
void slotComputeOverallSyncStatus();
void slotShowTrayMessage(const QString &title, const QString &msg);
void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
void slotFolderOpenAction( const QString& alias );
void slotRefreshQuotaDisplay( qint64 total, qint64 used );
void slotRebuildRecentMenus();
void slotProgressSyncProblem(const QString& folder, const Progress::SyncProblem& problem);
void slotUpdateProgress(const QString &folder, const Progress::Info& progress);
void slotShowGuiMessage(const QString &title, const QString &message);
void slotFoldersChanged();
void slotShowSettings();
void slotShowSyncProtocol();
void slotShutdown();
void slotSyncStateChange( const QString& alias );
void slotTrayClicked( QSystemTrayIcon::ActivationReason reason );
void slotToggleLogBrowser();
void slotOpenOwnCloud();
void slotOpenSettingsDialog( bool openSettings );
void slotHelp();
void slotOpenPath(const QString& path);
void slotAccountStateChanged();
private slots:
void slotDisplayIdle();
private:
void setupActions();
QPointer<Systray> _tray;
QPointer<SettingsDialog> _settingsDialog;
QPointer<LogBrowser>_logBrowser;
// tray's menu
QMenu *_contextMenu;
QMenu *_recentActionsMenu;
QAction *_actionLogin;
QAction *_actionLogout;
QAction *_actionOpenoC;
QAction *_actionSettings;
QAction *_actionQuota;
QAction *_actionStatus;
QAction *_actionRecent;
QAction *_actionHelp;
QAction *_actionQuit;
QuotaInfo *_quotaInfo;
QSignalMapper *_folderOpenActionMapper;
QSignalMapper *_recentItemsMapper;
Application *_app;
QStringList _startupFails;
};
} // namespace Mirall
#endif // OWNCLOUDGUI_H

View File

@@ -1,628 +0,0 @@
/*
* Copyright (C) by Klaas Freitag <freitag@kde.org>
*
* 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 "mirall/owncloudinfo.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/theme.h"
#include "mirall/logger.h"
#include "creds/abstractcredentials.h"
#include <QtCore>
#include <QtGui>
#include <QAuthenticator>
#define DEFAULT_CONNECTION QLatin1String("default");
static const char WEBDAV_PATH[] = "remote.php/webdav/";
namespace Mirall
{
ownCloudInfo *ownCloudInfo::_instance = 0;
ownCloudInfo* ownCloudInfo::instance()
{
static QMutex mutex;
if (!_instance)
{
mutex.lock();
if (!_instance) {
_instance = new ownCloudInfo;
}
mutex.unlock();
}
return _instance;
}
ownCloudInfo::ownCloudInfo() :
QObject(0),
_manager(0),
_authAttempts(0),
_lastQuotaUsedBytes(0),
_lastQuotaTotalBytes(0)
{
_connection = Theme::instance()->appName();
connect(this, SIGNAL(guiLog(QString,QString)),
Logger::instance(), SIGNAL(guiLog(QString,QString)));
// this will set credentials specific qnam
setCustomConfigHandle(QString());
}
void ownCloudInfo::setNetworkAccessManager( QNetworkAccessManager* qnam )
{
delete _manager;
qnam->setParent( this );
_manager = qnam;
MirallConfigFile cfg( _configHandle );
QSslSocket::addDefaultCaCertificates(QSslCertificate::fromData(cfg.caCerts()));
connect( _manager, SIGNAL( sslErrors(QNetworkReply*, QList<QSslError>)),
this, SIGNAL(sslFailed(QNetworkReply*, QList<QSslError>)) );
_certsUntrusted = false;
}
ownCloudInfo::~ownCloudInfo()
{
}
void ownCloudInfo::setCustomConfigHandle( const QString& handle )
{
_configHandle = handle;
_authAttempts = 0; // allow a couple of tries again.
resetSSLUntrust();
MirallConfigFile cfg(_configHandle);
setNetworkAccessManager (cfg.getCredentials()->getQNAM());
}
bool ownCloudInfo::isConfigured()
{
MirallConfigFile cfgFile( _configHandle );
return cfgFile.connectionExists( _connection );
}
QNetworkReply *ownCloudInfo::checkInstallation()
{
_redirectCount = 0;
MirallConfigFile cfgFile( _configHandle );
QUrl url ( cfgFile.ownCloudUrl( _connection ) + QLatin1String("status.php") );
/* No authentication required for this. */
return getRequest(url);
}
QNetworkReply* ownCloudInfo::getWebDAVPath( const QString& path )
{
_redirectCount = 0;
QUrl url ( webdavUrl( _connection ) + path );
QNetworkReply *reply = getRequest(url);
_directories[reply] = path;
return reply;
}
QNetworkReply* ownCloudInfo::getRequest( const QUrl& url )
{
qDebug() << "Get Request to " << url;
QNetworkRequest request;
request.setUrl( url );
setupHeaders( request, 0 );
QNetworkReply *reply = _manager->get( request );
connect( reply, SIGNAL(finished()), SLOT(slotReplyFinished()));
if( !_configHandle.isEmpty() ) {
qDebug() << "Setting config handle " << _configHandle;
_configHandleMap[reply] = _configHandle;
}
connect( reply, SIGNAL( error(QNetworkReply::NetworkError )),
this, SLOT(slotError( QNetworkReply::NetworkError )));
return reply;
}
QNetworkReply* ownCloudInfo::mkdirRequest( const QString& dir )
{
qDebug() << "OCInfo Making dir " << dir;
_authAttempts = 0;
QNetworkRequest req;
QUrl url = QUrl(webdavUrl(_connection));
url.setEncodedPath(url.encodedPath()+QUrl::toPercentEncoding(dir, "/"));
req.setUrl( url );
QNetworkReply *reply = davRequest("MKCOL", req, 0);
// remember the confighandle used for this request
if( ! _configHandle.isEmpty() )
qDebug() << "Setting config handle " << _configHandle;
_configHandleMap[reply] = _configHandle;
if( reply->error() != QNetworkReply::NoError ) {
qDebug() << "mkdir request network error: " << reply->errorString();
}
connect( reply, SIGNAL(finished()), SLOT(slotMkdirFinished()) );
connect( reply, SIGNAL( error(QNetworkReply::NetworkError )),
this, SLOT(slotError(QNetworkReply::NetworkError )));
return reply;
}
QNetworkReply* ownCloudInfo::getQuotaRequest( const QString& dir )
{
QNetworkRequest req;
req.setUrl( QUrl( webdavUrl(_connection) + dir ) );
req.setRawHeader("Depth", "0");
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:quota-available-bytes/>\n"
" <d:quota-used-bytes/>\n"
" <d:getetag/>"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer;
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = davRequest("PROPFIND", req, buf);
buf->setParent(reply);
if( reply->error() != QNetworkReply::NoError ) {
qDebug() << "getting quota: request network error: " << reply->errorString();
}
connect( reply, SIGNAL( finished()), SLOT(slotGetQuotaFinished()) );
connect( reply, SIGNAL( error(QNetworkReply::NetworkError)),
this, SLOT( slotError(QNetworkReply::NetworkError)));
return reply;
}
QNetworkReply* ownCloudInfo::getDirectoryListing( const QString& dir )
{
QNetworkRequest req;
req.setUrl( QUrl( webdavUrl(_connection) + dir ) );
req.setRawHeader("Depth", "1");
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:resourcetype/>\n"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer;
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = davRequest("PROPFIND", req, buf);
buf->setParent(reply);
if( reply->error() != QNetworkReply::NoError ) {
qDebug() << "getting quota: request network error: " << reply->errorString();
}
connect( reply, SIGNAL( finished()), SLOT(slotGetDirectoryListingFinished()) );
connect( reply, SIGNAL( error(QNetworkReply::NetworkError)),
this, SLOT( slotError(QNetworkReply::NetworkError)));
return reply;
}
void ownCloudInfo::slotMkdirFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if( ! reply ) {
qDebug() << "ownCloudInfo: Reply empty!";
return;
}
emit webdavColCreated( reply->error() );
qDebug() << "mkdir slot hit with status: " << reply->error();
if( _configHandleMap.contains( reply ) ) {
_configHandleMap.remove( reply );
}
reply->deleteLater();
}
void ownCloudInfo::slotGetQuotaFinished()
{
bool ok = false;
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
int http_result_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (http_result_code == 207) {
// Parse DAV response
QXmlStreamReader reader(reply);
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
qint64 quotaUsedBytes = 0;
qint64 quotaAvailableBytes = 0;
QString etag;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("quota-used-bytes")) {
quotaUsedBytes = reader.readElementText().toLongLong(&ok);
if (!ok) quotaUsedBytes = 0;
} else if (name == QLatin1String("quota-available-bytes")) {
quotaAvailableBytes = reader.readElementText().toLongLong(&ok);
if (!ok) quotaAvailableBytes = 0;
} else if (name == QLatin1String("getetag")) {
etag = reader.readElementText();
}
}
}
qint64 total = quotaUsedBytes + quotaAvailableBytes;
_lastQuotaTotalBytes = total;
_lastQuotaUsedBytes = quotaUsedBytes;
emit quotaUpdated(total, quotaUsedBytes);
_lastEtag = etag;
} else {
qDebug() << "Quota request *not* successful, http result code is " << http_result_code;
_lastQuotaTotalBytes = 0;
_lastQuotaUsedBytes = 0;
}
reply->deleteLater();
}
void ownCloudInfo::slotGetDirectoryListingFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
// Parse DAV response
QXmlStreamReader reader(reply);
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QStringList folders;
QString currentItem;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("href")) {
currentItem = reader.readElementText();
} else if (name == QLatin1String("collection") &&
!currentItem.isEmpty()) {
folders.append(QUrl::fromEncoded(currentItem.toLatin1()).path());
currentItem.clear();
}
}
}
emit directoryListingUpdated(folders);
}
reply->deleteLater();
}
QList<QNetworkCookie> ownCloudInfo::getLastAuthCookies()
{
QUrl url = QUrl( webdavUrl(_connection));
QList<QNetworkCookie> cookies = _manager->cookieJar()->cookiesForUrl(url);
return cookies;
}
QString ownCloudInfo::configHandle(QNetworkReply *reply)
{
QString configHandle;
if( _configHandleMap.contains(reply) ) {
configHandle = _configHandleMap[reply];
}
return configHandle;
}
QList<QSslCertificate> ownCloudInfo::certificateChain() const
{
QMutexLocker lock(const_cast<QMutex*>(&_certChainMutex));
return _certificateChain;
}
//
// There have been problems with the finish-signal coming from the networkmanager.
// To avoid that, the reply-signals were connected and the data is taken from the
// sender() method.
//
void ownCloudInfo::slotReplyFinished()
{
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
QSslConfiguration sslConfig = reply->sslConfiguration();
if (!sslConfig.isNull()) {
QMutexLocker lock(&_certChainMutex);
_certificateChain = sslConfig.peerCertificateChain();
}
if( ! reply ) {
qDebug() << "ownCloudInfo: Reply empty!";
return;
}
// Detect redirect url
QUrl possibleRedirUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
/* We'll deduct if the redirection is valid in the redirectUrl function */
if (!possibleRedirUrl.isEmpty() && _redirectCount++ > 10) {
// Are we in a redirect loop
qDebug() << "Redirect loop while redirecting to" << possibleRedirUrl;
possibleRedirUrl.clear();
}
if(!possibleRedirUrl.isEmpty()) {
QString configHandle;
qDebug() << "Redirected to " << possibleRedirUrl;
// We'll do another request to the redirection url.
// an empty config handle is ok for the default config.
if( _configHandleMap.contains(reply) ) {
configHandle = _configHandleMap[reply];
qDebug() << "Redirect: Have a custom config handle: " << configHandle;
}
QString path = _directories[reply];
if (path.isEmpty()) {
path = QLatin1String("status.php");
} else {
path.prepend( QLatin1String(WEBDAV_PATH) );
}
qDebug() << "This path was redirected: " << path;
QString newUrl = possibleRedirUrl.toString();
if( !path.isEmpty() && newUrl.endsWith( path )) {
// cut off the trailing path
newUrl.chop( path.length() );
_urlRedirectedTo = newUrl;
qDebug() << "Updated url to" << newUrl;
getRequest( possibleRedirUrl );
} else {
qDebug() << "WRN: Path is not part of the redirect URL. NO redirect.";
}
reply->deleteLater();
_directories.remove(reply);
_configHandleMap.remove(reply);
return;
}
// TODO: check if this is always the correct encoding
const QString version = QString::fromUtf8( reply->readAll() );
const QString url = reply->url().toString();
QString plainUrl(url);
plainUrl.remove( QLatin1String("/status.php"));
QString info( version );
if( url.endsWith( QLatin1String("status.php")) ) {
// it was a call to status.php
if( reply->error() == QNetworkReply::NoError && info.isEmpty() ) {
// This seems to be a bit strange behaviour of QNetworkAccessManager.
// It calls the finised slot multiple times but only the first read wins.
// That happend when the code connected the finished signal of the manager.
// It did not happen when the code connected to the reply finish signal.
qDebug() << "WRN: NetworkReply with not content but also no error! " << reply;
reply->deleteLater();
return;
}
qDebug() << "status.php returns: " << info << " " << reply->error() << " Reply: " << reply;
if( info.contains(QLatin1String("installed"))
&& info.contains(QLatin1String("version"))
&& info.contains(QLatin1String("versionstring")) ) {
info.remove(0,1); // remove first char which is a "{"
info.remove(-1,1); // remove the last char which is a "}"
QStringList li = info.split( QLatin1Char(',') );
QString versionStr;
QString version;
QString edition;
foreach ( const QString& infoString, li ) {
QStringList touple = infoString.split( QLatin1Char(':'));
QString key = touple[0];
key.remove(QLatin1Char('"'));
QString val = touple[1];
val.remove(QLatin1Char('"'));
if( key == QLatin1String("versionstring") ) {
// get the versionstring out.
versionStr = val;
} else if( key == QLatin1String( "version") ) {
// get version out
version = val;
} else if( key == QLatin1String( "edition") ) {
// get version out
edition = val;
} else if(key == QLatin1String("installed")) {
// Silently ignoring "installed = true" information
} else {
qDebug() << "Unknown info from ownCloud status.php: "<< key << "=" << val;
}
}
emit ownCloudInfoFound( plainUrl, versionStr, version, edition );
} else {
qDebug() << "No proper answer on " << url;
emit noOwncloudFound( reply );
}
} else {
// it was a general GET request.
QString dir(QLatin1String("unknown"));
if( _directories.contains(reply) ) {
dir = _directories[reply];
}
emit ownCloudDirExists( dir, reply );
}
reply->deleteLater();
_directories.remove(reply);
_configHandleMap.remove(reply);
}
void ownCloudInfo::resetSSLUntrust()
{
_certsUntrusted = false;
}
void ownCloudInfo::setCertsUntrusted(bool donttrust)
{
_certsUntrusted = donttrust;
}
bool ownCloudInfo::certsUntrusted()
{
return _certsUntrusted;
}
void ownCloudInfo::slotError( QNetworkReply::NetworkError err)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
qDebug() << "ownCloudInfo Network Error"
<< err << ":" << reply->errorString();
switch (err) {
case QNetworkReply::ProxyConnectionRefusedError:
emit guiLog(tr("Proxy Refused Connection "),
tr("The configured proxy has refused the connection. "
"Please check the proxy settings."));
break;
case QNetworkReply::ProxyConnectionClosedError:
emit guiLog(tr("Proxy Closed Connection"),
tr("The configured proxy has closed the connection. "
"Please check the proxy settings."));
break;
case QNetworkReply::ProxyNotFoundError:
emit guiLog(tr("Proxy Not Found"),
tr("The configured proxy could not be found. "
"Please check the proxy settings."));
break;
case QNetworkReply::ProxyAuthenticationRequiredError:
emit guiLog(tr("Proxy Authentication Error"),
tr("The configured proxy requires login but the proxy credentials "
"are invalid. Please check the proxy settings."));
break;
case QNetworkReply::ProxyTimeoutError:
emit guiLog(tr("Proxy Connection Timed Out"),
tr("The connection to the configured proxy has timed out."));
break;
default:
break;
}
}
// ============================================================================
void ownCloudInfo::setupHeaders( QNetworkRequest & req, quint64 size )
{
QUrl url( req.url() );
qDebug() << "Setting up host header: " << url.host();
if (size) {
req.setHeader( QNetworkRequest::ContentLengthHeader, size);
req.setHeader( QNetworkRequest::ContentTypeHeader, QLatin1String("text/xml; charset=utf-8"));
}
}
QNetworkReply* ownCloudInfo::davRequest(const QByteArray& reqVerb, QNetworkRequest& req, QIODevice *data)
{
setupHeaders(req, quint64(data ? data->size() : 0));
return _manager->sendCustomRequest(req, reqVerb, data );
}
QString ownCloudInfo::webdavUrl(const QString &connection)
{
QString url;
if (!_urlRedirectedTo.isEmpty()) {
url = _urlRedirectedTo.toString();
} else {
MirallConfigFile cfgFile(_configHandle );
url = cfgFile.ownCloudUrl( connection );
}
url.append( QLatin1String( WEBDAV_PATH ) );
if (!url.endsWith('/')) url.append('/');
return url;
}
RequestEtagJob::RequestEtagJob(const QString& dir, QObject* parent)
: QObject(parent)
{
QNetworkRequest req;
req.setUrl( QUrl( ownCloudInfo::instance()->webdavUrl(ownCloudInfo::instance()->_connection) + dir ) );
if (dir.isEmpty() || dir == "/") {
/* For the root directory, we need to query the etags of all the sub directories
* because, at the time I am writing this comment (Owncloud 5.0.9), the etag of the
* root directory is not updated when the sub directories changes */
req.setRawHeader("Depth", "1");
} else {
req.setRawHeader("Depth", "0");
}
QByteArray xml("<?xml version=\"1.0\" ?>\n"
"<d:propfind xmlns:d=\"DAV:\">\n"
" <d:prop>\n"
" <d:getetag/>"
" </d:prop>\n"
"</d:propfind>\n");
QBuffer *buf = new QBuffer;
buf->setData(xml);
buf->open(QIODevice::ReadOnly);
_reply = ownCloudInfo::instance()->davRequest("PROPFIND", req, buf);
buf->setParent(_reply);
if( _reply->error() != QNetworkReply::NoError ) {
qDebug() << "getting etag: request network error: " << _reply->errorString();
}
connect( _reply, SIGNAL( finished()), SLOT(slotFinished()) );
connect( _reply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotError()));
connect( _reply, SIGNAL(error(QNetworkReply::NetworkError)),
ownCloudInfo::instance(), SLOT(slotError(QNetworkReply::NetworkError)));
}
void RequestEtagJob::slotFinished()
{
if (_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 207) {
// Parse DAV response
QXmlStreamReader reader(_reply);
reader.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("d", "DAV:"));
QString etag;
while (!reader.atEnd()) {
QXmlStreamReader::TokenType type = reader.readNext();
if (type == QXmlStreamReader::StartElement &&
reader.namespaceUri() == QLatin1String("DAV:")) {
QString name = reader.name().toString();
if (name == QLatin1String("getetag")) {
etag += reader.readElementText();
}
}
}
emit etagRetreived(etag);
}
_reply->deleteLater();
deleteLater();
}
void RequestEtagJob::slotError()
{
qDebug() << "RequestEtagJob Error: " << _reply->errorString();
_reply->deleteLater();
deleteLater();
emit networkError();
}
} // ns Mirall

View File

@@ -1,186 +0,0 @@
/*
* Copyright (C) by Klaas Freitag <freitag@kde.org>
*
* 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.
*/
#ifndef OWNCLOUDINFO_H
#define OWNCLOUDINFO_H
#include <QObject>
#include <QtNetwork>
namespace Mirall
{
class ownCloudInfo : public QObject
{
Q_OBJECT
public:
static ownCloudInfo *instance();
bool isConfigured();
/**
* call status.php
*/
QNetworkReply* checkInstallation();
/**
* convenience: GET request to the WebDAV server.
*/
QNetworkReply* getWebDAVPath( const QString& );
/**
* There is a global flag here if the user once decided against trusting the
* SSL connection. This method resets it so that the ssl dialog is shown again.
*/
void resetSSLUntrust();
/**
* Set wether or not to trust errorneus SSL certificates
*/
void setCertsUntrusted(bool donttrust);
/**
* Do we trust the certificate?.
*/
bool certsUntrusted();
/**
* Set a NetworkAccessManager to be used
*
* This method will take ownership of the NetworkAccessManager, so you can just
* set it initially and forget about its memory management.
*/
void setNetworkAccessManager( QNetworkAccessManager *qnam );
/**
* Create a collection via owncloud. Provide a relative path.
*/
QNetworkReply* mkdirRequest( const QString& );
/**
* Retrieve quota for a path. Provide a relative path.
*/
QNetworkReply* getQuotaRequest( const QString& );
/**
* provide collections in a directory via owncloud. Provide a relative path.
*/
QNetworkReply* getDirectoryListing( const QString& dir );
/**
* Use a custom ownCloud configuration file identified by handle
*/
void setCustomConfigHandle( const QString& );
/**
* Accessor to the config handle.
*/
QString configHandle(QNetworkReply *reply = 0);
/**
* Certificate chain of the connection est. with ownCloud.
* Empty if the connection is HTTP-based
*/
QList<QSslCertificate> certificateChain() const;
/**
* returns the owncloud webdav url.
* It may be different from the one in the config if there was a HTTP redirection
* The returned URL is guaranteed to end in a forward slash ('/')
*/
QString webdavUrl(const QString& connection = QString());
qint64 lastQuotaUsedBytes() const { return _lastQuotaUsedBytes; }
qint64 lastQuotaTotalBytes() const { return _lastQuotaTotalBytes; }
QString lastEtag() const { return _lastEtag; }
QList<QNetworkCookie> getLastAuthCookies();
signals:
// result signal with url- and version string.
void ownCloudInfoFound( const QString&, const QString&, const QString&, const QString& );
void noOwncloudFound( QNetworkReply* );
void ownCloudDirExists( const QString&, QNetworkReply* );
void webdavColCreated( QNetworkReply::NetworkError );
void sslFailed( QNetworkReply *reply, QList<QSslError> errors );
void guiLog( const QString& title, const QString& content );
void quotaUpdated( qint64 total, qint64 quotaUsedBytes );
void directoryListingUpdated(const QStringList &directories);
protected slots:
void slotReplyFinished( );
void slotError( QNetworkReply::NetworkError );
void slotMkdirFinished();
void slotGetQuotaFinished();
void slotGetDirectoryListingFinished();
private:
explicit ownCloudInfo();
/**
* a general GET request to the ownCloud WebDAV.
*/
QNetworkReply* getRequest( const QUrl &url);
QNetworkReply* davRequest(const QByteArray&, QNetworkRequest&, QIODevice* );
~ownCloudInfo();
void setupHeaders(QNetworkRequest &req, quint64 size );
static ownCloudInfo *_instance;
QNetworkAccessManager *_manager;
QString _connection;
QString _configHandle;
QUrl _urlRedirectedTo;
QHash<QNetworkReply*, QString> _directories;
QHash<QNetworkReply*, QString> _configHandleMap;
QList<QSslCertificate> _certificateChain;
bool _certsUntrusted;
int _authAttempts;
QMutex _certChainMutex;
int _redirectCount;
qint64 _lastQuotaUsedBytes;
qint64 _lastQuotaTotalBytes;
QString _lastEtag;
friend class RequestEtagJob;
};
class RequestEtagJob : public QObject {
Q_OBJECT
QNetworkReply *_reply;
public:
explicit RequestEtagJob(const QString &dir , QObject* parent = 0);
private slots:
void slotFinished();
void slotError();
signals:
void etagRetreived(const QString &etag);
void networkError();
};
} // ns Mirall
#endif // OWNCLOUDINFO_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
/*
* 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.
*/
#ifndef OWNCLOUDPROPAGATOR_H
#define OWNCLOUDPROPAGATOR_H
#include <neon/ne_request.h>
#include <QHash>
#include <QObject>
#include <qelapsedtimer.h>
#include "syncfileitem.h"
#include "progressdispatcher.h"
struct hbf_transfer_s;
struct ne_session_s;
struct ne_decompress_s;
namespace Mirall {
class SyncJournalDb;
class OwncloudPropagator;
class PropagatorJob : public QObject {
Q_OBJECT
protected:
OwncloudPropagator *_propagator;
public:
explicit PropagatorJob(OwncloudPropagator* propagator) : _propagator(propagator) {}
public slots:
virtual void start() = 0;
signals:
void finished(SyncFileItem::Status);
void completed(const SyncFileItem &);
void progress(Progress::Kind, const SyncFileItem& item, quint64 bytes, quint64 total);
};
/*
* Propagate a directory, and all its sub entries.
*/
class PropagateDirectory : public PropagatorJob {
Q_OBJECT
public:
// e.g: create the directory
QScopedPointer<PropagatorJob>_firstJob;
// all the sub files or sub directories.
//TODO: in the future, all sub job can be run in parallel
QVector<PropagatorJob *> _subJobs;
SyncFileItem _item;
int _current; // index of the current running job
SyncFileItem::Status _hasError; // NoStatus, or NormalError / SoftError if there was an error
explicit PropagateDirectory(OwncloudPropagator *propagator, const SyncFileItem &item = SyncFileItem())
: PropagatorJob(propagator)
, _firstJob(0), _item(item), _current(-1), _hasError(SyncFileItem::NoStatus) { }
virtual ~PropagateDirectory() {
qDeleteAll(_subJobs);
}
void append(PropagatorJob *subJob) {
_subJobs.append(subJob);
}
virtual void start();
private slots:
void startJob(PropagatorJob *next) {
connect(next, SIGNAL(finished(SyncFileItem::Status)), this, SLOT(proceedNext(SyncFileItem::Status)), Qt::QueuedConnection);
connect(next, SIGNAL(completed(SyncFileItem)), this, SIGNAL(completed(SyncFileItem)));
connect(next, SIGNAL(progress(Progress::Kind,SyncFileItem,quint64,quint64)), this, SIGNAL(progress(Progress::Kind,SyncFileItem,quint64,quint64)));
next->start();
}
void proceedNext(SyncFileItem::Status status);
};
/*
* Abstract class to propagate a single item
*/
class PropagateItemJob : public PropagatorJob {
Q_OBJECT
protected:
void done(SyncFileItem::Status status, const QString &errorString = QString());
void updateMTimeAndETag(const char *uri, time_t);
/* fetch the error code and string from the session
in case of error, calls done with the error and returns true.
If the HTTP error code is ignoreHTTPError, the error is ignored
*/
bool updateErrorFromSession(int neon_code = 0, ne_request *req = 0, int ignoreHTTPError = 0);
/*
* to be called by the progress callback and will wait the amount of time needed.
*/
void limitBandwidth(qint64 progress, qint64 limit);
QElapsedTimer _lastTime;
qint64 _lastProgress;
int _httpStatusCode;
SyncFileItem _item;
public:
PropagateItemJob(OwncloudPropagator* propagator, const SyncFileItem &item)
: PropagatorJob(propagator), _lastProgress(0), _httpStatusCode(0), _item(item) {}
};
// Dummy job that just mark it as completed and ignored.
class PropagateIgnoreJob : public PropagateItemJob {
Q_OBJECT
public:
PropagateIgnoreJob(OwncloudPropagator* propagator,const SyncFileItem& item)
: PropagateItemJob(propagator, item) {}
void start() {
done(SyncFileItem::FileIgnored);
}
};
class OwncloudPropagator : public QObject {
Q_OBJECT
PropagateItemJob *createJob(const SyncFileItem& item);
QScopedPointer<PropagateDirectory> _rootJob;
public:
ne_session_s *_session;
QString _localDir; // absolute path to the local directory. ends with '/'
QString _remoteDir; // path to the root of the remote. ends with '/'
SyncJournalDb *_journal;
public:
OwncloudPropagator(ne_session_s *session, const QString &localDir, const QString &remoteDir,
SyncJournalDb *progressDb, QAtomicInt *abortRequested)
: _session(session)
, _localDir(localDir)
, _remoteDir(remoteDir)
, _journal(progressDb)
, _abortRequested(abortRequested)
{
if (!localDir.endsWith(QChar('/'))) _localDir+='/';
if (!remoteDir.endsWith(QChar('/'))) _remoteDir+='/';
}
void start(const SyncFileItemVector &_syncedItems);
int _downloadLimit;
int _uploadLimit;
QAtomicInt *_abortRequested; // boolean set by the main thread to abort.
signals:
void completed(const SyncFileItem &);
void progress(Progress::Kind kind, const SyncFileItem&, quint64 bytes, quint64 total);
void finished();
};
}
#endif

View File

@@ -1,478 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OwncloudSetupPage</class>
<widget class="QWidget" name="OwncloudSetupPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>583</width>
<height>448</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item row="0" column="0" colspan="3">
<widget class="QLabel" name="topLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>48</width>
<height>68</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0" colspan="3">
<layout class="QHBoxLayout" name="resultLayout">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
<item row="5" column="0" colspan="3">
<widget class="QWidget" name="advancedBox" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Local Folder</string>
</property>
<property name="buddy">
<cstring>pbSelectLocalFolder</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pbSelectLocalFolder">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>pbSelectLocalFolder</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="resolutionWidget" native="true">
<layout class="QFormLayout" name="formLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<item row="0" column="0" colspan="2">
<widget class="QRadioButton" name="radioButton">
<property name="text">
<string>&amp;Keep local data</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;small&gt;Syncs your existing data to new location.&lt;/small&gt;</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QRadioButton" name="cbSyncFromScratch">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If this box is checked, existing content in the local directory will be erased to start a clean sync from the server.&lt;/p&gt;&lt;p&gt;Do not check this if the local content should be uploaded to the servers directory.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>&amp;Start a clean sync</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;small&gt;Erases the contents of the local folder before syncing using the new settings.&lt;/small&gt;</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="indent">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Server &amp;Address</string>
</property>
<property name="buddy">
<cstring>leUrl</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Utils::FancyLineEdit" name="leUrl">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Enter the url of the ownCloud you want to connect to (without http or https).</string>
</property>
<property name="placeholderText">
<string>https://...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&amp;Username</string>
</property>
<property name="buddy">
<cstring>leUsername</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Utils::FancyLineEdit" name="leUsername">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Enter the ownCloud username.</string>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Password</string>
</property>
<property name="buddy">
<cstring>lePassword</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Utils::FancyLineEdit" name="lePassword">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Enter the ownCloud password.</string>
</property>
<property name="text">
<string/>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="errorLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Error Label</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="cbAdvanced">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Advanced &amp;Settings</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="8" column="0" colspan="3">
<widget class="QLabel" name="syncModeLabel">
<property name="text">
<string>Status message</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
</spacer>
</item>
<item row="9" column="0">
<widget class="QLabel" name="bottomLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="6" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Utils::FancyLineEdit</class>
<extends>QLineEdit</extends>
<header location="global">fancylineedit.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>lePassword</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -23,10 +23,14 @@
#include "wizard/owncloudwizard.h"
#include "mirall/owncloudsetupwizard.h"
#include "mirall/mirallconfigfile.h"
#include "mirall/owncloudinfo.h"
#include "mirall/folderman.h"
#include "mirall/utility.h"
#include "mirall/mirallaccessmanager.h"
#include "mirall/account.h"
#include "mirall/networkjobs.h"
#include "mirall/sslerrordialog.h"
#include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h"
#include "creds/dummycredentials.h"
@@ -34,11 +38,8 @@ namespace Mirall {
OwncloudSetupWizard::OwncloudSetupWizard(QObject* parent) :
QObject( parent ),
_account(0),
_ocWizard(new OwncloudWizard),
_mkdirRequestReply(),
_checkInstallationRequest(),
_checkRemoteFolderRequest(),
_configHandle(),
_remoteFolder()
{
connect( _ocWizard, SIGNAL(determineAuthType(const QString&)),
@@ -52,8 +53,6 @@ OwncloudSetupWizard::OwncloudSetupWizard(QObject* parent) :
Therefore Qt::QueuedConnection is required */
connect( _ocWizard, SIGNAL(basicSetupFinished(int)),
this, SLOT(slotAssistantFinished(int)), Qt::QueuedConnection);
connect( _ocWizard, SIGNAL(clearPendingRequests()),
this, SLOT(slotClearPendingRequests()));
}
OwncloudSetupWizard::~OwncloudSetupWizard()
@@ -63,35 +62,55 @@ OwncloudSetupWizard::~OwncloudSetupWizard()
void OwncloudSetupWizard::runWizard(QObject* obj, const char* amember, QWidget *parent)
{
OwncloudSetupWizard *wiz = new OwncloudSetupWizard(parent);
static QPointer<OwncloudSetupWizard> wiz;
if (!wiz.isNull()) {
return;
}
wiz = new OwncloudSetupWizard(parent);
connect( wiz, SIGNAL(ownCloudWizardDone(int)), obj, amember);
connect( wiz, SIGNAL(ownCloudWizardDone(int)), wiz, SLOT(deleteLater()));
FolderMan::instance()->setSyncEnabled(false);
wiz->startWizard();
}
void OwncloudSetupWizard::startWizard()
{
// Set useful default values.
MirallConfigFile cfgFile;
// Fill the entry fields with existing values.
QString url = cfgFile.ownCloudUrl();
//QString user = cfgFile.ownCloudUser();
bool configExists = !( url.isEmpty()/* || user.isEmpty()*/ );
_ocWizard->setConfigExists( configExists );
if( !url.isEmpty() ) {
_ocWizard->setOCUrl( url );
FolderMan *folderMan = FolderMan::instance();
bool multiFolderSetup = folderMan->map().count() > 1;
// ###
Account *account = Account::restore();
if (!account) {
_ocWizard->setConfigExists(false);
account = new Account;
account->setCredentials(CredentialsFactory::create("dummy"));
} else {
account->credentials()->fetch(account);
_ocWizard->setConfigExists(true);
}
account->setSslErrorHandler(new SslDialogErrorHandler);
_ocWizard->setAccount(account);
_ocWizard->setOCUrl(account->url().toString());
_remoteFolder = Theme::instance()->defaultServerFolder();
// remoteFolder may be empty, which means /
QString localFolder = Theme::instance()->defaultClientFolder();
// if its a relative path, prepend with users home dir, otherwise use as absolute path
if( !QDir(localFolder).isAbsolute() ) {
localFolder = QDir::homePath() + QDir::separator() + localFolder;
}
if (!multiFolderSetup) {
QList<Folder*> folders = folderMan->map().values();
if (!folders.isEmpty()) {
Folder* folder = folders.first();
localFolder = QDir(folder->path()).absolutePath();
}
}
_ocWizard->setProperty("localFolder", localFolder);
_ocWizard->setRemoteFolder(_remoteFolder);
@@ -100,190 +119,92 @@ void OwncloudSetupWizard::startWizard()
_ocWizard->restart();
// settings re-initialized in initPage must be set here after restart
_ocWizard->setMultipleFoldersExist(FolderMan::instance()->map().count() > 1);
_ocWizard->setMultipleFoldersExist( multiFolderSetup );
_ocWizard->open();
_ocWizard->raise();
}
void OwncloudSetupWizard::slotDetermineAuthType(const QString& serverUrl)
// also checks if an installation is valid and determines auth type in a second step
void OwncloudSetupWizard::slotDetermineAuthType(const QString &urlString)
{
QString url(serverUrl);
qDebug() << "Connect to url: " << url;
_ocWizard->setField(QLatin1String("OCUrl"), url );
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2 to determine authentication type...")
.arg( Theme::instance()->appNameGUI() ).arg(url) );
// write a temporary config.
QDateTime now = QDateTime::currentDateTime();
// remove a possibly existing custom config.
if( ! _configHandle.isEmpty() ) {
// remove the old config file.
MirallConfigFile oldConfig( _configHandle );
oldConfig.cleanupCustomConfig();
}
_configHandle = now.toString(QLatin1String("MMddyyhhmmss"));
MirallConfigFile cfgFile( _configHandle, true );
if( url.isEmpty() ) return;
if( !( url.startsWith(QLatin1String("https://")) || url.startsWith(QLatin1String("http://"))) ) {
qDebug() << "url does not start with a valid protocol, assuming https.";
url.prepend(QLatin1String("https://"));
// FIXME: give a hint about the auto completion
_ocWizard->setOCUrl(url);
}
cfgFile.writeOwncloudConfig( Theme::instance()->appName(),
url,
new DummyCredentials);
ownCloudInfo* info = ownCloudInfo::instance();
info->setCustomConfigHandle( _configHandle );
if( info->isConfigured() ) {
// reset the SSL Untrust flag to let the SSL dialog appear again.
info->resetSSLUntrust();
connect(info, SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
SLOT(slotOwnCloudFoundAuth(QString,QString,QString,QString)));
connect(info, SIGNAL(noOwncloudFound(QNetworkReply*)),
SLOT(slotNoOwnCloudFoundAuth(QNetworkReply*)));
_checkInstallationRequest = info->checkInstallation();
} else {
qDebug() << " ownCloud seems not to be configured, can not start test connect.";
QString fixedUrl = urlString;
QUrl url = QUrl::fromUserInput(fixedUrl);
// fromUserInput defaults to http, not http if no scheme is specified
if (!fixedUrl.startsWith("http://") && !fixedUrl.startsWith("https://")) {
url.setScheme("https");
}
Account *account = _ocWizard->account();
account->setUrl(url);
CheckServerJob *job = new CheckServerJob(_ocWizard->account(), false, this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(instanceFound(QUrl,QVariantMap)), SLOT(slotOwnCloudFoundAuth(QUrl,QVariantMap)));
connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotNoOwnCloudFoundAuth(QNetworkReply*)));
connect(job, SIGNAL(timeout(const QUrl&)), SLOT(slotNoOwnCloudFoundAuthTimeout(const QUrl&)));
job->setTimeout(10*1000);
job->start();
}
void OwncloudSetupWizard::slotOwnCloudFoundAuth( const QString& url, const QString& infoString, const QString& version, const QString& )
void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl& url, const QVariantMap &info)
{
disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
this, SLOT(slotOwnCloudFoundAuth(QString,QString,QString,QString)));
disconnect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotNoOwnCloudFoundAuth(QNetworkReply*)));
_ocWizard->appendToConfigurationLog(tr("<font color=\"green\">Successfully connected to %1: %2 version %3 (%4)</font><br/><br/>")
.arg( url ).arg(Theme::instance()->appNameGUI()).arg(infoString).arg(version));
.arg(url.toString())
.arg(Theme::instance()->appNameGUI())
.arg(CheckServerJob::versionString(info))
.arg(CheckServerJob::version(info)));
MirallAccessManager* nm = new MirallAccessManager(this);
// TODO: We should get this path from owncloud info.
QNetworkReply* reply = nm->get (QNetworkRequest (url + "/remote.php/webdav/"));
connect (reply, SIGNAL(finished()),
this, SLOT(slotAuthCheckReplyFinished()));
nm->setProperty ("mirallRedirs", QVariant (0));
}
void OwncloudSetupWizard::slotAuthCheckReplyFinished()
{
QNetworkReply* reply = qobject_cast< QNetworkReply* > (sender ());
QUrl redirection = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
QNetworkAccessManager* nm = reply->manager ();
const int redirCount = nm->property ("mirallRedirs").toInt();
if (redirCount > 10) {
redirection.clear ();
if (url.path().endsWith("/status.php")) {
// We might be redirected, update the account
QUrl redirectedUrl = url;
redirectedUrl.setPath(url.path().left(url.path().length() - 11));
_ocWizard->account()->setUrl(redirectedUrl);
qDebug() << Q_FUNC_INFO << " was redirected to" << redirectedUrl.toString();
}
disconnect (reply, SIGNAL(finished()),
this, SLOT(slotAuthCheckReplyFinished()));
if ((reply->error () == QNetworkReply::AuthenticationRequiredError) || redirection.isEmpty()) {
reply->deleteLater();
nm->deleteLater();
_ocWizard->setAuthType (WizardCommon::HttpCreds);
} else if (redirection.toString().endsWith ("/remote.php/webdav/")) {
QNetworkReply* newReply = nm->get (QNetworkRequest(redirection));
connect (newReply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(slotAuthCheckReplyError(QNetworkReply::NetworkError)));
connect (newReply, SIGNAL(finished()),
this, SLOT(slotAuthCheckReplyFinished(QNetworkReply::NetworkError)));
reply->deleteLater();
nm->setProperty ("mirallRedirs", QVariant(redirCount + 1));
} else {
QRegExp shibbolethyWords ("SAML|wayf");
shibbolethyWords.setCaseSensitivity (Qt::CaseInsensitive);
if (redirection.toString ().contains (shibbolethyWords)) {
_ocWizard->setAuthType(WizardCommon::Shibboleth);
} else {
// TODO: Send an error.
// eh?
_ocWizard->setAuthType (WizardCommon::HttpCreds);
}
reply->deleteLater();
nm->deleteLater();
}
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authType(WizardCommon::AuthType)),
_ocWizard, SLOT(setAuthType(WizardCommon::AuthType)));
job->start();
}
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth( QNetworkReply *err )
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
{
disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudInfoFound(QString,QString,QString,QString)),
this, SLOT(slotOwnCloudFound(QString,QString,QString,QString)));
disconnect(ownCloudInfo::instance(), SIGNAL(noOwncloudFound(QNetworkReply*)),
this, SLOT(slotNoOwnCloudFound(QNetworkReply*)));
_ocWizard->displayError(tr("Failed to connect to %1 at %2:<br/>%3")
.arg(Theme::instance()->appNameGUI())
.arg(reply->url().toString())
.arg(reply->errorString()));
}
_ocWizard->displayError(tr("Failed to connect to %1:<br/>%2").
arg(Theme::instance()->appNameGUI()).arg(err->errorString()));
// remove the config file again
MirallConfigFile cfgFile( _configHandle );
cfgFile.cleanupCustomConfig();
void OwncloudSetupWizard::slotNoOwnCloudFoundAuthTimeout(const QUrl&url)
{
_ocWizard->displayError(tr("Failed to connect to %1 at %2:<br/>%3")
.arg(Theme::instance()->appNameGUI())
.arg(url.toString())
.arg("Timeout"));
}
void OwncloudSetupWizard::slotConnectToOCUrl( const QString& url )
{
qDebug() << "Connect to url: " << url;
_ocWizard->setField(QLatin1String("OCUrl"), url );
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2...")
.arg( Theme::instance()->appNameGUI() ).arg(url) );
testOwnCloudConnect();
qDebug() << "Connect to url: " << url;
_ocWizard->account()->setCredentials(_ocWizard->getCredentials());
_ocWizard->setField(QLatin1String("OCUrl"), url );
_ocWizard->appendToConfigurationLog(tr("Trying to connect to %1 at %2...")
.arg( Theme::instance()->appNameGUI() ).arg(url) );
testOwnCloudConnect();
}
void OwncloudSetupWizard::testOwnCloudConnect()
{
// write a temporary config.
QDateTime now = QDateTime::currentDateTime();
if( _configHandle.isEmpty() ) {
_configHandle = now.toString(QLatin1String("MMddyyhhmmss"));
}
MirallConfigFile cfgFile( _configHandle, true );
QString url = _ocWizard->field(QLatin1String("OCUrl")).toString();
if( url.isEmpty() ) return;
if( !( url.startsWith(QLatin1String("https://")) || url.startsWith(QLatin1String("http://"))) ) {
qDebug() << "url does not start with a valid protocol, assuming https.";
url.prepend(QLatin1String("https://"));
// FIXME: give a hint about the auto completion
_ocWizard->setOCUrl(url);
}
cfgFile.writeOwncloudConfig( Theme::instance()->appName(),
url,
_ocWizard->getCredentials());
ownCloudInfo* info(ownCloudInfo::instance());
info->setCustomConfigHandle( _configHandle );
// If there is already a config, take its proxy config.
if( info->isConfigured() ) {
MirallConfigFile prevCfg;
cfgFile.setProxyType( prevCfg.proxyType(), prevCfg.proxyHostName(), prevCfg.proxyPort(),
prevCfg.proxyNeedsAuth(), prevCfg.proxyUser(), prevCfg.proxyPassword() );
}
connect( info,SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this,SLOT(slotConnectionCheck(QString,QNetworkReply*)));
qDebug() << "# checking for authentication settings.";
_checkRemoteFolderRequest = info->getWebDAVPath(_remoteFolder ); // this call needs to be authenticated.
// continue in slotConnectionCheck
ValidateDavAuthJob *job = new ValidateDavAuthJob(_ocWizard->account(), this);
job->setIgnoreCredentialFailure(true);
connect(job, SIGNAL(authResult(QNetworkReply*)), SLOT(slotConnectionCheck(QNetworkReply*)));
job->start();
}
void OwncloudSetupWizard::slotConnectionCheck(const QString&, QNetworkReply* reply)
void OwncloudSetupWizard::slotConnectionCheck(QNetworkReply* reply)
{
// disconnect from ownCloud Info signals
disconnect(ownCloudInfo::instance(), SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this, SLOT(slotConnectionCheck(QString,QNetworkReply*)));
switch (reply->error()) {
case QNetworkReply::NoError:
case QNetworkReply::ContentNotFoundError:
@@ -300,9 +221,8 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString& localFo
{
qDebug() << "Setup local sync folder for new oC connection " << localFolder;
const QDir fi( localFolder );
// FIXME: Show problems with local folder properly.
bool localFolderOk = true;
bool nextStep = true;
if( fi.exists() ) {
// there is an existing local folder. If its non empty, it can only be synced if the
// ownCloud is newly created.
@@ -316,35 +236,23 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString& localFo
} else {
res += tr("failed.");
qDebug() << "Failed to create " << fi.path();
localFolderOk = false;
_ocWizard->displayError(tr("Could not create local folder %1").arg(localFolder));
nextStep = false;
}
_ocWizard->appendToConfigurationLog( res );
}
if( localFolderOk ) {
checkRemoteFolder(remoteFolder);
if (nextStep) {
EntityExistsJob *job = new EntityExistsJob(_ocWizard->account(), remoteFolder, this);
connect(job, SIGNAL(exists(QNetworkReply*)), SLOT(slotAuthCheckReply(QNetworkReply*)));
job->start();
} else {
finalizeSetup( false );
}
}
void OwncloudSetupWizard::checkRemoteFolder(const QString& remoteFolder)
// ### TODO move into EntityExistsJob once we decide if/how to return gui strings from jobs
void OwncloudSetupWizard::slotAuthCheckReply(QNetworkReply *reply)
{
ownCloudInfo* info(ownCloudInfo::instance());
connect( info,SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this,SLOT(slotAuthCheckReply(QString,QNetworkReply*)));
qDebug() << "# checking for existence of remote folder.";
info->setCustomConfigHandle(_configHandle);
_checkRemoteFolderRequest = info->getWebDAVPath(remoteFolder); // this call needs to be authenticated.
// continue in slotAuthCheckReply
}
void OwncloudSetupWizard::slotAuthCheckReply( const QString&, QNetworkReply *reply )
{
// disconnect from ownCloud Info signals
disconnect( ownCloudInfo::instance(),SIGNAL(ownCloudDirExists(QString,QNetworkReply*)),
this,SLOT(slotAuthCheckReply(QString,QNetworkReply*)));
bool ok = true;
QString error;
QNetworkReply::NetworkError errId = reply->error();
@@ -352,11 +260,11 @@ void OwncloudSetupWizard::slotAuthCheckReply( const QString&, QNetworkReply *rep
if( errId == QNetworkReply::NoError ) {
qDebug() << "******** Remote folder found, all cool!";
} else if( errId == QNetworkReply::ContentNotFoundError ) {
if( createRemoteFolder() ) {
return; // Finish here, the mkdir request will go on.
} else {
error = tr("The remote folder could not be accessed!");
if( _remoteFolder.isEmpty() ) {
error = tr("No remote folder specified!");
ok = false;
} else {
createRemoteFolder();
}
} else {
error = tr("Error: %1").arg(reply->errorString());
@@ -370,25 +278,20 @@ void OwncloudSetupWizard::slotAuthCheckReply( const QString&, QNetworkReply *rep
finalizeSetup( ok );
}
bool OwncloudSetupWizard::createRemoteFolder()
void OwncloudSetupWizard::createRemoteFolder()
{
if( _remoteFolder.isEmpty() ) return false;
_ocWizard->appendToConfigurationLog( tr("creating folder on ownCloud: %1" ).arg( _remoteFolder ));
ownCloudInfo* info(ownCloudInfo::instance());
connect(info, SIGNAL(webdavColCreated(QNetworkReply::NetworkError)),
this, SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
_mkdirRequestReply = info->mkdirRequest( _remoteFolder );
return (_mkdirRequestReply != NULL);
MkColJob *job = new MkColJob(_ocWizard->account(), _remoteFolder, this);
connect(job, SIGNAL(finished(QNetworkReply::NetworkError)), SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
job->start();
}
void OwncloudSetupWizard::slotCreateRemoteFolderFinished( QNetworkReply::NetworkError error )
{
qDebug() << "** webdav mkdir request finished " << error;
disconnect(ownCloudInfo::instance(), SIGNAL(webdavColCreated(QNetworkReply::NetworkError)),
this, SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
// disconnect(ownCloudInfo::instance(), SIGNAL(webdavColCreated(QNetworkReply::NetworkError)),
// this, SLOT(slotCreateRemoteFolderFinished(QNetworkReply::NetworkError)));
bool success = true;
@@ -405,7 +308,7 @@ void OwncloudSetupWizard::slotCreateRemoteFolderFinished( QNetworkReply::Network
"are wrong!"
"<br/>Please go back and check your credentials.</p>"));
_ocWizard->appendToConfigurationLog( tr("<p><font color=\"red\">Remote folder creation failed probably because the provided credentials are wrong.</font>"
"<br/>Please go back and check your credentials.</p>"));
"<br/>Please go back and check your credentials.</p>"));
_remoteFolder.clear();
success = false;
} else {
@@ -427,125 +330,168 @@ void OwncloudSetupWizard::finalizeSetup( bool success )
if( success ) {
if( !(localFolder.isEmpty() || _remoteFolder.isEmpty() )) {
_ocWizard->appendToConfigurationLog( tr("A sync connection from %1 to remote directory %2 was set up.")
.arg(localFolder).arg(_remoteFolder));
.arg(localFolder).arg(_remoteFolder));
}
_ocWizard->appendToConfigurationLog( QLatin1String(" "));
_ocWizard->appendToConfigurationLog( QLatin1String("<p><font color=\"green\"><b>")
+ tr("Successfully connected to %1!")
.arg(Theme::instance()->appNameGUI())
+ QLatin1String("</b></font></p>"));
+ tr("Successfully connected to %1!")
.arg(Theme::instance()->appNameGUI())
+ QLatin1String("</b></font></p>"));
_ocWizard->successfulStep();
} else {
// ### this is not quite true, pass in the real problem as optional parameter
_ocWizard->appendToConfigurationLog(QLatin1String("<p><font color=\"red\">")
+ tr("Connection to %1 could not be established. Please check again.")
.arg(Theme::instance()->appNameGUI())
+ QLatin1String("</font></p>"));
+ tr("Connection to %1 could not be established. Please check again.")
.arg(Theme::instance()->appNameGUI())
+ QLatin1String("</font></p>"));
}
}
bool OwncloudSetupWizard::ensureStartFromScratch(const QString &localFolder) {
// first try to rename (backup) the current local dir.
bool renameOk = false;
while( !renameOk ) {
renameOk = FolderMan::instance()->startFromScratch(localFolder);
if( ! renameOk ) {
QMessageBox::StandardButton but;
but = QMessageBox::question( 0, tr("Folder rename failed"),
tr("Can't remove and back up the folder because the folder or a file in it is open in another program."
"Please close the folder or file and hit retry or cancel the setup."), QMessageBox::Retry | QMessageBox::Abort, QMessageBox::Retry);
if( but == QMessageBox::Abort ) {
break;
}
}
}
return renameOk;
}
void OwncloudSetupWizard::replaceDefaultAccountWith(Account *newAccount)
{
// new Account
AccountManager *mgr = AccountManager::instance();
if (mgr->account()) {
mgr->account()->deleteLater();
}
mgr->setAccount(newAccount);
newAccount->save();
}
// Method executed when the user ends the wizard, either with 'accept' or 'reject'.
// accept the custom config to be the main one if Accepted.
void OwncloudSetupWizard::slotAssistantFinished( int result )
{
MirallConfigFile cfg( _configHandle );
FolderMan *folderMan = FolderMan::instance();
if( result == QDialog::Rejected ) {
// the old config remains valid. Remove the temporary one.
cfg.cleanupCustomConfig();
_ocWizard->account()->deleteLater();
qDebug() << "Rejected the new config, use the old!";
} else if( result == QDialog::Accepted ) {
AbstractCredentials* credentials(_ocWizard->getCredentials());
qDebug() << "Config Changes were accepted!";
// go through all folders and remove the journals if the server changed.
MirallConfigFile prevCfg;
QUrl prevUrl( prevCfg.ownCloudUrl() );
QUrl newUrl( cfg.ownCloudUrl() );
AbstractCredentials* oldCredentials(prevCfg.getCredentials());
bool urlHasChanged = (prevUrl.host() != newUrl.host() ||
prevUrl.port() != newUrl.port() ||
prevUrl.path() != newUrl.path());
// if the user changed, its also a changed url.
if(credentials->changed(oldCredentials)) {
urlHasChanged = true;
qDebug() << "The User has changed, same as url change.";
}
Account *newAccount = _ocWizard->account();
Account *origAccount = AccountManager::instance()->account();
const QString localFolder = _ocWizard->localFolder();
bool acceptCfg = true;
if( urlHasChanged ) {
// first terminate sync jobs.
folderMan->terminateSyncProcess();
bool isInitialSetup = (origAccount == 0);
bool reinitRequired = newAccount->changed(origAccount, true /* ignoreProtocol, allows http->https */);
bool startFromScratch = _ocWizard->field("OCSyncFromScratch").toBool();
bool startFromScratch = _ocWizard->field( "OCSyncFromScratch" ).toBool();
if( startFromScratch ) {
// first try to rename (backup) the current local dir.
bool renameOk = false;
while( !renameOk ) {
renameOk = folderMan->startFromScratch(localFolder);
if( ! renameOk ) {
QMessageBox::StandardButton but;
but = QMessageBox::question( 0, tr("Folder rename failed"),
tr("Can't remove and back up the folder because the folder or a file in it is open in another program."
"Please close the folder or file and hit retry or cancel the setup."), QMessageBox::Retry | QMessageBox::Abort, QMessageBox::Retry);
if( but == QMessageBox::Abort ) {
renameOk = true;
acceptCfg = false;
}
}
// This distinguishes three possibilities:
// 1. Initial setup, no prior account exists
if (isInitialSetup) {
folderMan->addFolderDefinition(Theme::instance()->appName(),
localFolder, _remoteFolder );
replaceDefaultAccountWith(newAccount);
}
// 2. Server URL or user changed, requires reinit of folders
else if (reinitRequired) {
// 2.1: startFromScratch: (Re)move local data, clean slate sync
if (startFromScratch) {
if (ensureStartFromScratch(localFolder)) {
folderMan->addFolderDefinition(Theme::instance()->appName(),
localFolder, _remoteFolder );
_ocWizard->appendToConfigurationLog(tr("<font color=\"green\"><b>Local sync folder %1 successfully created!</b></font>").arg(localFolder));
replaceDefaultAccountWith(newAccount);
}
}
}
// Now write the resulting folder definition if folder names are set.
if( acceptCfg && urlHasChanged ) {
folderMan->removeAllFolderDefinitions();
folderMan->addFolderDefinition(Theme::instance()->appName(),
localFolder, _remoteFolder );
_ocWizard->appendToConfigurationLog(tr("<font color=\"green\"><b>Local sync folder %1 successfully created!</b></font>").arg(localFolder));
} else {
// url is unchanged. Only the password was changed.
if( acceptCfg ) {
qDebug() << "Only password was changed, no changes to folder configuration.";
} else {
qDebug() << "User interrupted change of configuration.";
// 2.2: Reinit: Remove journal and start a sync
else {
folderMan->removeAllFolderDefinitions();
folderMan->addFolderDefinition(Theme::instance()->appName(),
localFolder, _remoteFolder );
_ocWizard->appendToConfigurationLog(tr("<font color=\"green\"><b>Local sync folder %1 successfully created!</b></font>").arg(localFolder));
replaceDefaultAccountWith(newAccount);
}
}
// save the user credentials and afterwards clear the cred store.
if( acceptCfg ) {
cfg.acceptCustomConfig();
// 3. Existing setup, http -> https or password changed
else {
replaceDefaultAccountWith(newAccount);
qDebug() << "Only password was changed, no changes to folder configuration.";
}
}
// clear the custom config handle
_configHandle.clear();
ownCloudInfo::instance()->setCustomConfigHandle( QString::null );
// notify others.
emit ownCloudWizardDone( result );
}
void OwncloudSetupWizard::slotClearPendingRequests()
DetermineAuthTypeJob::DetermineAuthTypeJob(Account *account, QObject *parent)
: AbstractNetworkJob(account, QString(), parent)
, _redirects(0)
{
qDebug() << "Pending request: " << _mkdirRequestReply;
if( _mkdirRequestReply && _mkdirRequestReply->isRunning() ) {
qDebug() << "ABORTing pending mkdir request.";
_mkdirRequestReply->abort();
}
void DetermineAuthTypeJob::start()
{
QNetworkReply *reply = getRequest(Account::davPath());
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
}
void DetermineAuthTypeJob::finished()
{
QUrl redirection = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
qDebug() << redirection.toString();
if (_redirects >= maxRedirects()) {
redirection.clear();
}
if( _checkInstallationRequest && _checkInstallationRequest->isRunning() ) {
qDebug() << "ABORTing pending check installation request.";
_checkInstallationRequest->abort();
}
if( _checkRemoteFolderRequest && _checkRemoteFolderRequest->isRunning() ) {
qDebug() << "ABORTing pending remote folder check request.";
_checkRemoteFolderRequest->abort();
if ((reply()->error() == QNetworkReply::AuthenticationRequiredError) || redirection.isEmpty()) {
emit authType(WizardCommon::HttpCreds);
} else if (redirection.toString().endsWith(Account::davPath())) {
// do a new run
_redirects++;
setReply(getRequest(redirection));
setupConnections(reply());
} else {
QRegExp shibbolethyWords("SAML|wayf");
shibbolethyWords.setCaseSensitivity(Qt::CaseInsensitive);
if (redirection.toString().contains(shibbolethyWords)) {
emit authType(WizardCommon::Shibboleth);
} else {
// TODO: Send an error.
// eh?
emit authType(WizardCommon::HttpCreds);
}
}
}
ValidateDavAuthJob::ValidateDavAuthJob(Account *account, QObject *parent)
: AbstractNetworkJob(account, QString(), parent)
{
}
void ValidateDavAuthJob::start()
{
QNetworkReply *reply = getRequest(Account::davPath());
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start();
}
void ValidateDavAuthJob::finished()
{
emit authResult(reply());
}
} // ns Mirall

View File

@@ -22,10 +22,39 @@
#include <QPointer>
#include "mirall/theme.h"
#include "mirall/networkjobs.h"
#include "wizard/owncloudwizardcommon.h"
namespace Mirall {
class OwncloudWizard;
class Account;
class ValidateDavAuthJob : public AbstractNetworkJob {
Q_OBJECT
public:
ValidateDavAuthJob(Account* account, QObject *parent = 0);
void start();
signals:
void authResult(QNetworkReply*);
private slots:
void finished();
};
class DetermineAuthTypeJob : public AbstractNetworkJob {
Q_OBJECT
public:
explicit DetermineAuthTypeJob(Account *account, QObject *parent = 0);
void start();
signals:
void authType(WizardCommon::AuthType);
private slots:
void finished();
private:
int _redirects;
};
class OwncloudSetupWizard : public QObject
{
@@ -40,18 +69,17 @@ signals:
private slots:
void slotDetermineAuthType(const QString&);
void slotOwnCloudFoundAuth(const QString&, const QString&, const QString&, const QString&);
void slotAuthCheckReplyFinished();
void slotNoOwnCloudFoundAuth(QNetworkReply*);
void slotOwnCloudFoundAuth(const QUrl&, const QVariantMap&);
void slotNoOwnCloudFoundAuth(QNetworkReply *reply);
void slotNoOwnCloudFoundAuthTimeout(const QUrl&url);
void slotConnectToOCUrl(const QString&);
void slotConnectionCheck(const QString&, QNetworkReply*);
void slotConnectionCheck(QNetworkReply*);
void slotCreateLocalAndRemoteFolders(const QString&, const QString&);
void slotAuthCheckReply(const QString&, QNetworkReply*);
void slotAuthCheckReply(QNetworkReply*);
void slotCreateRemoteFolderFinished(QNetworkReply::NetworkError);
void slotAssistantFinished( int );
void slotClearPendingRequests();
private:
explicit OwncloudSetupWizard(QObject *parent = 0 );
@@ -59,16 +87,15 @@ private:
void startWizard();
void testOwnCloudConnect();
void checkRemoteFolder(const QString& remoteFolder);
bool createRemoteFolder();
void createRemoteFolder();
void finalizeSetup( bool );
bool ensureStartFromScratch(const QString &localFolder);
void replaceDefaultAccountWith(Account *newAccount);
Account* _account;
OwncloudWizard* _ocWizard;
QPointer<QNetworkReply> _mkdirRequestReply;
QPointer<QNetworkReply> _checkInstallationRequest;
QPointer<QNetworkReply> _checkRemoteFolderRequest;
QString _configHandle;
QString _remoteFolder;
};
}

View File

@@ -58,6 +58,12 @@ QString Progress::asResultString( Kind kind )
case EndDelete:
re = QCoreApplication::translate( "progress", "deleted");
break;
case StartRename:
re = QCoreApplication::translate( "progress", "Move");
break;
case EndRename:
re = QCoreApplication::translate( "progress", "Move");
break;
default:
Q_ASSERT(false);
}
@@ -106,12 +112,27 @@ QString Progress::asActionString( Kind kind )
case EndDelete:
re = QCoreApplication::translate( "progress", "deleted");
break;
case StartRename:
re = QCoreApplication::translate( "progress", "move");
break;
case EndRename:
re = QCoreApplication::translate( "progress", "moved");
break;
default:
Q_ASSERT(false);
}
return re;
}
bool Progress::isErrorKind( Kind kind )
{
bool re = false;
if( kind == SoftError || kind == NormalError || kind == FatalError ) {
re = true;
}
return re;
}
ProgressDispatcher* ProgressDispatcher::instance() {
if (!_instance) {
_instance = new ProgressDispatcher();
@@ -147,52 +168,57 @@ QList<Progress::SyncProblem> ProgressDispatcher::recentProblems(int count)
return _recentProblems;
}
void ProgressDispatcher::setProgressProblem(const QString& folder, const Progress::SyncProblem &problem)
{
Q_ASSERT( Progress::isErrorKind(problem.kind));
_recentProblems.prepend( problem );
if( _recentProblems.size() > _QueueSize ) {
_recentProblems.removeLast();
}
emit progressSyncProblem( folder, problem );
}
void ProgressDispatcher::setProgressInfo(const QString& folder, const Progress::Info& progress)
{
if( folder.isEmpty() ) {
return;
}
Progress::Info newProgress = progress;
Progress::Info newProgress(progress);
if( newProgress.kind == Progress::Error ) {
Progress::SyncProblem err;
err.folder = folder;
err.current_file = newProgress.current_file;
// its really
err.error_message = QString::fromLocal8Bit( (const char*)newProgress.file_size );
err.error_code = newProgress.current_file_bytes;
err.timestamp = QDateTime::currentDateTime();
Q_ASSERT( !Progress::isErrorKind(progress.kind));
_recentProblems.prepend( err );
if( _recentProblems.size() > _QueueSize ) {
_recentProblems.removeLast();
}
emit progressSyncProblem( folder, err );
} else {
if( newProgress.kind == Progress::StartSync ) {
_recentProblems.clear();
}
if( newProgress.kind == Progress::EndSync ) {
newProgress.overall_current_bytes = newProgress.overall_transmission_size;
newProgress.current_file_no = newProgress.overall_file_count;
_currentAction.remove(newProgress.folder);
}
if( newProgress.kind == Progress::EndDownload ||
newProgress.kind == Progress::EndUpload ||
newProgress.kind == Progress::EndDelete ) {
_recentChanges.prepend(newProgress);
if( _recentChanges.size() > _QueueSize ) {
_recentChanges.removeLast();
}
}
// store the last real action to help clients that start during
// the Context-phase of an upload or download.
if( newProgress.kind != Progress::Context ) {
_currentAction[folder] = newProgress.kind;
}
emit progressInfo( folder, newProgress );
if( newProgress.kind == Progress::StartSync ) {
_recentProblems.clear();
_timer.start();
}
if( newProgress.kind == Progress::EndSync ) {
newProgress.overall_current_bytes = newProgress.overall_transmission_size;
newProgress.current_file_no = newProgress.overall_file_count;
_currentAction.remove(newProgress.folder);
qint64 msecs = _timer.elapsed();
qDebug()<< "[PROGRESS] progressed " << newProgress.overall_transmission_size
<< " bytes in " << newProgress.overall_file_count << " files"
<< " in msec " << msecs;
}
if( newProgress.kind == Progress::EndDownload ||
newProgress.kind == Progress::EndUpload ||
newProgress.kind == Progress::EndDelete ||
newProgress.kind == Progress::EndRename ) {
_recentChanges.prepend(newProgress);
if( _recentChanges.size() > _QueueSize ) {
_recentChanges.removeLast();
}
}
// store the last real action to help clients that start during
// the Context-phase of an upload or download.
if( newProgress.kind != Progress::Context ) {
_currentAction[folder] = newProgress.kind;
}
emit progressInfo( folder, newProgress );
}
Progress::Kind ProgressDispatcher::currentFolderContext( const QString& folder )

View File

@@ -18,6 +18,7 @@
#include <QHash>
#include <QTime>
#include <QQueue>
#include <QElapsedTimer>
namespace Mirall {
@@ -27,7 +28,7 @@ namespace Mirall {
*/
namespace Progress
{
typedef enum {
enum Kind {
Invalid,
StartSync,
Download,
@@ -41,10 +42,14 @@ namespace Progress
EndSync,
StartDelete,
EndDelete,
Error
} Kind;
StartRename,
EndRename,
SoftError,
NormalError,
FatalError
};
typedef struct {
struct Info {
Kind kind;
QString folder;
QString current_file;
@@ -58,18 +63,26 @@ namespace Progress
QDateTime timestamp;
} Info;
Info() : kind(Invalid), file_size(0), current_file_bytes(0),
overall_file_count(0), current_file_no(0),
overall_transmission_size(0), overall_current_bytes(0) { }
};
typedef struct {
struct SyncProblem {
Kind kind;
QString folder;
QString current_file;
QString error_message;
int error_code;
QDateTime timestamp;
} SyncProblem;
SyncProblem() : kind(Invalid), error_code(0) {}
};
QString asActionString( Kind );
QString asResultString( Kind );
bool isErrorKind( Kind );
}
/**
@@ -94,6 +107,7 @@ public:
QList<Progress::SyncProblem> recentProblems(int count);
Progress::Kind currentFolderContext( const QString& folder );
signals:
/**
@brief Signals the progress of data transmission.
@@ -107,7 +121,8 @@ signals:
void progressSyncProblem( const QString& folder, const Progress::SyncProblem& problem );
protected:
void setProgressInfo(const QString &folder, const Progress::Info& progress);
void setProgressInfo(const QString& folder, const Progress::Info& progress);
void setProgressProblem( const QString& folder, const Progress::SyncProblem& problem);
private:
ProgressDispatcher(QObject* parent = 0);
@@ -116,6 +131,8 @@ private:
QList<Progress::SyncProblem> _recentProblems;
QHash<QString, Progress::Kind> _currentAction;
QElapsedTimer _timer;
static ProgressDispatcher* _instance;
};

View File

@@ -0,0 +1,302 @@
/*
* Copyright (C) by Klaas Freitag <freitag@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; 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 <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
#include "mirall/protocolwidget.h"
#include "mirall/syncresult.h"
#include "mirall/logger.h"
#include "mirall/utility.h"
#include "mirall/theme.h"
#include "mirall/folderman.h"
#include "mirall/syncfileitem.h"
#include "mirall/folder.h"
#include "ui_protocolwidget.h"
namespace Mirall {
ProtocolWidget::ProtocolWidget(QWidget *parent) :
QWidget(parent),
ErrorIndicatorRole( Qt::UserRole +1 ),
_ui(new Ui::ProtocolWidget)
{
_ui->setupUi(this);
connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString,Progress::Info)),
this, SLOT(slotProgressInfo(QString,Progress::Info)));
connect(ProgressDispatcher::instance(), SIGNAL(progressSyncProblem(const QString&,const Progress::SyncProblem&)),
this, SLOT(slotProgressProblem(const QString&, const Progress::SyncProblem&)));
connect(_ui->_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem*,int)), SLOT(slotOpenFile(QTreeWidgetItem*,int)));
// Adjust copyToClipboard() when making changes here!
QStringList header;
header << tr("Time");
header << tr("File");
header << tr("Folder");
header << tr("Action");
header << tr("Size");
_ui->_treeWidget->setHeaderLabels( header );
_ui->_treeWidget->setColumnWidth(1, 180);
_ui->_treeWidget->setColumnCount(5);
_ui->_treeWidget->setRootIsDecorated(false);
connect(this, SIGNAL(guiLog(QString,QString)), Logger::instance(), SIGNAL(guiLog(QString,QString)));
_clearBlacklistBtn = _ui->_dialogButtonBox->addButton(tr("Retry Sync"), QDialogButtonBox::ActionRole);
_clearBlacklistBtn->setToolTip( tr("Some files are ignored because of previous errors.\n Try to sync these again.") );
_clearBlacklistBtn->setEnabled(false);
connect(_clearBlacklistBtn, SIGNAL(clicked()), SLOT(slotClearBlacklist()));
QPushButton *copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
copyBtn->setToolTip( tr("Copy the activity list to the clipboard."));
connect(copyBtn, SIGNAL(clicked()), SLOT(copyToClipboard()));
}
void ProtocolWidget::setupList()
{
QList<Progress::Info> progressList = ProgressDispatcher::instance()->recentChangedItems(0); // All.
QList<QTreeWidgetItem*> items;
QTreeWidgetItem *item;
_ui->_treeWidget->clear();
QList<Progress::SyncProblem> problemList = ProgressDispatcher::instance()->recentProblems(0);
items.clear();
foreach( Progress::SyncProblem prob, problemList ) {
item = createProblemTreewidgetItem(prob);
if (item) {
items.append(item);
}
}
_ui->_treeWidget->addTopLevelItems(items);
foreach( Progress::Info info, progressList ) {
item = createProgressTreewidgetItem(info);
if(item) {
items.append(item);
}
}
_ui->_treeWidget->addTopLevelItems(items);
}
ProtocolWidget::~ProtocolWidget()
{
delete _ui;
}
void ProtocolWidget::copyToClipboard()
{
QString text;
QTextStream ts(&text);
int topLevelItems = _ui->_treeWidget->topLevelItemCount();
for (int i = 0; i < topLevelItems; i++) {
QTreeWidgetItem *child = _ui->_treeWidget->topLevelItem(i);
ts << left
// time stamp
<< qSetFieldWidth(10)
<< child->data(0,Qt::DisplayRole).toString()
// file name
<< qSetFieldWidth(64)
<< child->data(1,Qt::DisplayRole).toString()
// folder
<< qSetFieldWidth(15)
<< child->data(2, Qt::DisplayRole).toString()
// action
<< qSetFieldWidth(15)
<< child->data(3, Qt::DisplayRole).toString()
// size
<< qSetFieldWidth(10)
<< child->data(4, Qt::DisplayRole).toString()
<< qSetFieldWidth(0)
<< endl;
}
QApplication::clipboard()->setText(text);
emit guiLog(tr("Copied to clipboard"), tr("The sync status has been copied to the clipboard."));
}
void ProtocolWidget::slotClearBlacklist()
{
FolderMan *folderMan = FolderMan::instance();
Folder::Map folders = folderMan->map();
foreach( Folder *f, folders ) {
int num = f->slotWipeBlacklist();
qDebug() << num << "entries were removed from"<< f->alias() << "blacklist";
}
folderMan->slotScheduleAllFolders();
}
QList<QTreeWidgetItem*> ProtocolWidget::errorItems( const QString& folder )
{
QList<QTreeWidgetItem*> list;
int itemCnt = _ui->_treeWidget->topLevelItemCount();
for( int cnt = 0; cnt < itemCnt; cnt++ ) {
QTreeWidgetItem *item = _ui->_treeWidget->topLevelItem(cnt);
bool isErrorItem = item->data(0, ErrorIndicatorRole).toBool();
QString itemFolder = item->data(2, Qt::DisplayRole).toString();
if( isErrorItem && itemFolder == folder ) {
list.append(item);
}
}
return list;
}
void ProtocolWidget::cleanErrorItems( const QString& folder ) // FIXME: Use the folder to detect which errors can be deleted.
{
QList<QTreeWidgetItem*> wipeList = errorItems(folder);
if( wipeList.count() > 0 ) {
qDeleteAll(wipeList.begin(), wipeList.end());
}
}
QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format) const
{
QLocale loc = QLocale::system();
QString timeStr;
QDate today = QDate::currentDate();
if( format == QLocale::NarrowFormat ) {
if( dt.date().day() == today.day() ) {
timeStr = loc.toString(dt.time(), QLocale::NarrowFormat);
} else {
timeStr = loc.toString(dt, QLocale::NarrowFormat);
}
} else {
timeStr = loc.toString(dt, format);
}
return timeStr;
}
QTreeWidgetItem *ProtocolWidget::createProblemTreewidgetItem( const Progress::SyncProblem& problem)
{
QStringList columns;
QString timeStr = timeString(problem.timestamp);
QString longTimeStr = timeString(problem.timestamp, QLocale::LongFormat);
columns << timeStr;
columns << problem.current_file;
columns << problem.folder;
QString errMsg = problem.error_message;
#if 0
if( problem.error_code == 507 ) {
errMsg = tr("No more storage space available on server.");
}
#endif
columns << errMsg;
QTreeWidgetItem *item = new QTreeWidgetItem(columns);
item->setData(0, ErrorIndicatorRole, QVariant(true) );
// Maybe we should not set the error icon for all problems but distinguish
// by error_code. A quota problem is considered an error, others might not??
if( problem.kind == Progress::SoftError ) {
item->setIcon(0, Theme::instance()->syncStateIcon(SyncResult::Problem, true));
} else {
item->setIcon(0, Theme::instance()->syncStateIcon(SyncResult::Error, true));
}
item->setToolTip(0, longTimeStr);
item->setToolTip(3, errMsg );
return item;
}
void ProtocolWidget::slotProgressProblem( const QString& folder, const Progress::SyncProblem& problem)
{
Q_UNUSED(folder);
QTreeWidgetItem *item = createProblemTreewidgetItem(problem);
_ui->_treeWidget->insertTopLevelItem(0, item);
}
void ProtocolWidget::slotOpenFile( QTreeWidgetItem *item, int )
{
QString folderName = item->text(2);
QString fileName = item->text(1);
Folder *folder = FolderMan::instance()->folder(folderName);
if (folder) {
QString fullPath = folder->path() + '/' + fileName;
if (QFile(fullPath).exists()) {
Utility::showInFileManager(fullPath);
}
}
}
QTreeWidgetItem* ProtocolWidget::createProgressTreewidgetItem( const Progress::Info& progress )
{
QStringList columns;
QString timeStr = timeString(progress.timestamp);
QString longTimeStr = timeString(progress.timestamp, QLocale::LongFormat);
columns << timeStr;
columns << progress.current_file;
columns << progress.folder;
columns << Progress::asResultString(progress.kind);
columns << Utility::octetsToString( progress.file_size );
QTreeWidgetItem *item = new QTreeWidgetItem(columns);
item->setToolTip(0, longTimeStr);
return item;
}
void ProtocolWidget::computeResyncButtonEnabled()
{
FolderMan *folderMan = FolderMan::instance();
Folder::Map folders = folderMan->map();
int cnt = 0;
foreach( Folder *f, folders ) {
cnt += f->blackListEntryCount();
}
_clearBlacklistBtn->setEnabled(cnt > 0);
}
void ProtocolWidget::slotProgressInfo( const QString& folder, const Progress::Info& progress )
{
if( progress.kind == Progress::StartSync ) {
cleanErrorItems( folder );
_clearBlacklistBtn->setEnabled(false);
}
if( progress.kind == Progress::EndSync ) {
computeResyncButtonEnabled();
}
// Ingore other events than finishing an individual up- or download.
if( !(progress.kind == Progress::EndDownload || progress.kind == Progress::EndUpload
|| progress.kind == Progress::EndDelete || progress.kind == Progress::EndRename)) {
return;
}
QTreeWidgetItem *item = createProgressTreewidgetItem(progress);
if(item) {
_ui->_treeWidget->insertTopLevelItem(0, item);
}
}
}

View File

@@ -11,8 +11,8 @@
* for more details.
*/
#ifndef FILEITEMDIALOG_H
#define FILEITEMDIALOG_H
#ifndef PROTOCOLWIDGET_H
#define PROTOCOLWIDGET_H
#include <QDialog>
#include <QDateTime>
@@ -20,48 +20,56 @@
#include "mirall/progressdispatcher.h"
#include "ui_itemprogressdialog.h"
#include "ui_protocolwidget.h"
class QPushButton;
namespace Mirall {
class SyncResult;
namespace Ui {
class ItemProgressDialog;
class ProtocolWidget;
}
class Application;
class ItemProgressDialog : public QDialog
class ProtocolWidget : public QWidget
{
Q_OBJECT
public:
explicit ItemProgressDialog(Application *app, QWidget *parent = 0);
~ItemProgressDialog();
explicit ProtocolWidget(QWidget *parent = 0);
~ProtocolWidget();
void setupList();
void setSyncResult( const SyncResult& result );
signals:
public slots:
void accept();
void slotProgressInfo( const QString& folder, const Progress::Info& progress );
void slotProgressErrors( const QString& folder, const Progress::SyncProblem& problem );
void slotProgressProblem(const QString& folder, const Progress::SyncProblem& problem );
void slotOpenFile( QTreeWidgetItem* item, int );
protected slots:
void copyToClipboard();
void slotClearBlacklist();
signals:
void guiLog(const QString&, const QString&);
private:
void setSyncResultStatus(const SyncResult& result );
void cleanErrors( const QString& folder );
void cleanErrorItems( const QString& folder );
void computeResyncButtonEnabled();
QTreeWidgetItem* createProgressTreewidgetItem(const Progress::Info& progress );
QTreeWidgetItem* createProblemTreewidgetItem( const Progress::SyncProblem& problem);
QList<QTreeWidgetItem*> errorItems(const QString &folder);
QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat) const;
const int ErrorIndicatorRole;
Ui::ItemProgressDialog *_ui;
int _problemCounter;
Ui::ProtocolWidget *_ui;
QPushButton *_clearBlacklistBtn;
};
}
#endif // FILEITEMDIALOG_H
#endif // PROTOCOLWIDGET_H

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Mirall::ProtocolWidget</class>
<widget class="QWidget" name="Mirall::ProtocolWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>612</width>
<height>515</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Sync Activity</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeWidget" name="_treeWidget">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>true</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="columnCount">
<number>4</number>
</property>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
<column>
<property name="text">
<string notr="true">2</string>
</property>
</column>
<column>
<property name="text">
<string>3</string>
</property>
</column>
<column>
<property name="text">
<string>4</string>
</property>
</column>
</widget>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="_dialogButtonBox">
<property name="standardButtons">
<set>QDialogButtonBox::NoButton</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

81
src/mirall/quotainfo.cpp Normal file
View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) by Daniel Molkentin <danimo@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; 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 "mirall/quotainfo.h"
#include "mirall/account.h"
#include "mirall/networkjobs.h"
#include "creds/abstractcredentials.h"
#include <QTimer>
#include <QDebug>
namespace Mirall {
namespace {
static const int defaultIntervalT = 30*1000;
static const int failIntervalT = 5*1000;
static const int initialTimeT = 1*1000;
}
QuotaInfo::QuotaInfo(QObject *parent)
: QObject(parent)
, _account(AccountManager::instance()->account())
, _lastQuotaTotalBytes(0)
, _lastQuotaUsedBytes(0)
, _refreshTimer(new QTimer(this))
{
connect(AccountManager::instance(), SIGNAL(accountChanged(Account*,Account*)),
SLOT(slotAccountChanged(Account*,Account*)));
connect(_refreshTimer, SIGNAL(timeout()), SLOT(slotCheckQuota()));
_refreshTimer->setSingleShot(true);
_refreshTimer->start(initialTimeT);
}
void QuotaInfo::slotAccountChanged(Account *newAccount, Account *oldAccount)
{
_account = newAccount;
disconnect(oldAccount, SIGNAL(stateChanged(int)), this, SLOT(slotAccountStateChanged(int)));
connect(newAccount, SIGNAL(stateChanged(int)), this, SLOT(slotAccountStateChanged(int)));
}
void QuotaInfo::slotAccountStateChanged(int state)
{
if (state == Account::Connected) {
slotCheckQuota();
} else {
_refreshTimer->stop();
}
}
void QuotaInfo::slotCheckQuota()
{
if (!_account.isNull() && _account->credentials() && _account->credentials()->ready()) {
CheckQuotaJob *job = new CheckQuotaJob(_account, "/", this);
connect(job, SIGNAL(quotaRetrieved(qint64,qint64)), SLOT(slotUpdateLastQuota(qint64,qint64)));
job->start();
_refreshTimer->start(defaultIntervalT);
} else {
_lastQuotaTotalBytes = 0;
_lastQuotaUsedBytes = 0;
_refreshTimer->start(failIntervalT);
}
}
void QuotaInfo::slotUpdateLastQuota(qint64 total, qint64 used)
{
_lastQuotaTotalBytes = total;
_lastQuotaUsedBytes = used;
emit quotaUpdated(total, used);
}
}

Some files were not shown because too many files have changed in this diff Show More