Comparar commits
92 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 953ee55128 | |||
| 1a3c9a9c1a | |||
| a038c99232 | |||
| 30c8fa1c93 | |||
| 010649f997 | |||
| 597f2a4dfc | |||
| cf1fe690a3 | |||
| 31b27bea38 | |||
| 04f99f3bd7 | |||
| fbf92ac239 | |||
| 669a72f0e1 | |||
| 2f0e65d37c | |||
| edb3759684 | |||
| 0f9c32452c | |||
| 45dd1d0acf | |||
| 552af7b94d | |||
| 8934979ba1 | |||
| b3e16e0eb0 | |||
| 7773380deb | |||
| e22050a434 | |||
| 7b0231bfce | |||
| edc58c045f | |||
| 035058934f | |||
| 9b1f46e560 | |||
| 9e7a8e619b | |||
| 9c0cd2b13e | |||
| 58ad781bd4 | |||
| 9d6701ecbe | |||
| d659c54798 | |||
| 0e9170cb36 | |||
| 8820bc1c17 | |||
| 74f67c97a9 | |||
| fd96b482c5 | |||
| 727e73d640 | |||
| d7804d8df3 | |||
| 3d9d106bb1 | |||
| 15dc3408ef | |||
| 928643f597 | |||
| 7110091fdd | |||
| 064c2b678a | |||
| e58739de00 | |||
| 7c2fdee78b | |||
| 7bfe46962f | |||
| 32b3023a8e | |||
| a5df44c757 | |||
| 641125eac1 | |||
| d340017a0a | |||
| a67173610d | |||
| e833d01288 | |||
| 3047682223 | |||
| c91dd94728 | |||
| 52f9f44b51 | |||
| 49748191a9 | |||
| e6b937f508 | |||
| 31c13f74fb | |||
| be466b47b7 | |||
| 11b144957b | |||
| 817e97c148 | |||
| 29932004ae | |||
| 6d83e841a9 | |||
| b43a9421d2 | |||
| e70b78d14b | |||
| 6814342d1f | |||
| 68126dcff6 | |||
| e7f00339e6 | |||
| a36b4ec863 | |||
| 074f8eadb1 | |||
| 6a51ae5b1b | |||
| af5f2d3860 | |||
| 38bad564a0 | |||
| c2fa3fb4c8 | |||
| f7082ee3df | |||
| f7c6efb391 | |||
| 051a348afd | |||
| d0af3ede05 | |||
| 0da2adcbe0 | |||
| 07bdd519e9 | |||
| 0829a94c92 | |||
| faf2514be2 | |||
| 4ea2edcf4a | |||
| cd29875b76 | |||
| 85bc3b276f | |||
| ce5ca8a42e | |||
| ce6a365328 | |||
| 09eea7f5f2 | |||
| 043350f49d | |||
| f657bd0cd9 | |||
| b4cf17d99d | |||
| 12bf6e39b7 | |||
| 4e7c09de83 | |||
| 31f2c3e76a | |||
| 64b6486327 |
@@ -1,5 +1,28 @@
|
||||
ChangeLog
|
||||
=========
|
||||
version 2.2.0 (release 2016-05-12)
|
||||
* Overlay icons: Refactoring - mainly for performance improvements
|
||||
* Improved error handling with Sync Journal on USB storages (#4632)
|
||||
* Sharing Completion: Improved UI of completion in sharing from desktop. (#3737)
|
||||
* Show server notifications on the client (#3733)
|
||||
* Improved Speed with small files by dynamic parallel request count (#4529)
|
||||
* LockWatcher: Make sure to sync files after apps released exclusive locks on Windows.
|
||||
* Improved handling of Win32 file locks and network files
|
||||
* Workaround Ubuntu 16.04 tray icon bug (#4693)
|
||||
* Removed the Alias field from the folder definition (#4695)
|
||||
* Improved netrc parser (#4691)
|
||||
* Improved user notifications about ignored files and conflicts (#4761, #3222)
|
||||
* Add warnings for old server versions (#4523)
|
||||
* Enable tranportation checksums if the server supports based on server capabilities (#3735)
|
||||
|
||||
* Default Chunk-size changed to 10MB (#4354)
|
||||
* Documentation Improvements, ie. about overlay icons
|
||||
* Translation fixes
|
||||
* Countless other bugfixes
|
||||
* Sqlite Update to recent version
|
||||
* Update of QtKeyChain to support Windows credential store
|
||||
* Packaging of dolphin overlay icon module for bleeding edge distros
|
||||
|
||||
version 2.1.1 (release 2016-02-10)
|
||||
* UI improvements for HiDPI screens, error messages, RTL languages
|
||||
* Fix occurences of "Connection Closed" when a new unauthenticated TCP socket is used
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
set( MIRALL_VERSION_MAJOR 2 )
|
||||
set( MIRALL_VERSION_MINOR 2 )
|
||||
set( MIRALL_VERSION_PATCH 0 )
|
||||
set( MIRALL_VERSION_PATCH 1 )
|
||||
set( MIRALL_VERSION_YEAR 2016 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||
set( MIRALL_VERSION_SUFFIX "beta1") #e.g. beta1, beta2, rc1
|
||||
set( MIRALL_VERSION_SUFFIX "git") #e.g. beta1, beta2, rc1
|
||||
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||
|
||||
if( NOT DEFINED MIRALL_VERSION_BUILD )
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## Patches used
|
||||
|
||||
There are our patches on top of Qt 5.4.0, which we are currently
|
||||
using for our binary packages on Windows and Mac OS. Most of them
|
||||
using for our binary packages on Windows and Mac OS X. Most of them
|
||||
have been sent upstream and are part of newer Qt releases.
|
||||
|
||||
All changes are designed to up upstream, and all those that are
|
||||
@@ -28,18 +28,26 @@ purpose is outlined in each patches' front matter.
|
||||
* 0007-X-Network-Fix-up-previous-corruption-patch.patch
|
||||
* 0008-QNAM-Fix-reply-deadlocks-on-server-closing-connectio.patch
|
||||
* 0014-Fix-SNI-for-TlsV1_0OrLater-TlsV1_1OrLater-and-TlsV1_.patch
|
||||
* 0016-Fix-possible-crash-when-passing-an-invalid-PAC-URL.patch
|
||||
* 0011-Make-sure-to-report-correct-NetworkAccessibility.patch
|
||||
|
||||
### Upstreamed but not in any release yet (as of 2015-11-16)
|
||||
### Part of Qt v5.5.2 (UNRELEASED!)
|
||||
* 0009-QNAM-Assign-proper-channel-before-sslErrors-emission.patch
|
||||
* 0010-Don-t-let-closed-http-sockets-pass-as-valid-connecti.patch
|
||||
* 0012-Make-sure-networkAccessibilityChanged-is-emitted.patch
|
||||
|
||||
### Part of Qt v5.6 and later
|
||||
* 0009-QNAM-Assign-proper-channel-before-sslErrors-emission.patch
|
||||
* 0010-Don-t-let-closed-http-sockets-pass-as-valid-connecti.patch
|
||||
* 0011-Make-sure-to-report-correct-NetworkAccessibility.patch
|
||||
* 0012-Make-sure-networkAccessibilityChanged-is-emitted.patch
|
||||
* 0013-Make-UnknownAccessibility-not-block-requests.patch
|
||||
* 0015-Remove-legacy-platform-code-in-QSslSocket-for-OS-X-1.patch
|
||||
* 0016-Fix-possible-crash-when-passing-an-invalid-PAC-URL.patch
|
||||
* 0019-Ensure-system-tray-icon-is-prepared-even-when-menu-bar.patch
|
||||
|
||||
### Not submitted to be part of any release:
|
||||
### Part of Qt 5.7 and later
|
||||
* 0015-Remove-legacy-platform-code-in-QSslSocket-for-OS-X-1.patch
|
||||
|
||||
### Not submitted upstream to be part of any release:
|
||||
* 0006-Fix-force-debug-info-with-macx-clang_NOUPSTREAM.patch
|
||||
This is only needed if you intent to harvest debugging symbols
|
||||
for breakpad.
|
||||
|
||||
@@ -620,7 +620,6 @@ Section Uninstall
|
||||
!insertmacro UnInstallLib REGDLL NOTSHARED REBOOT_PROTECTED "$INSTDIR\shellext\OCOverlays_x64.dll"
|
||||
!insertmacro UnInstallLib DLL NOTSHARED REBOOT_PROTECTED "$INSTDIR\shellext\OCUtil_x64.dll"
|
||||
!undef LIBRARY_X64
|
||||
${Else}
|
||||
DetailPrint "Uninstalling x86 overlay DLLs"
|
||||
!insertmacro UnInstallLib REGDLL NOTSHARED REBOOT_PROTECTED "$INSTDIR\shellext\OCContextMenu_x86.dll"
|
||||
!insertmacro UnInstallLib REGDLL NOTSHARED REBOOT_PROTECTED "$INSTDIR\shellext\OCOverlays_x86.dll"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
* than fmmatch anyway, which does not care for flags.
|
||||
**/
|
||||
#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */
|
||||
#define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */
|
||||
#endif
|
||||
|
||||
int csync_fnmatch(__const char *__pattern, __const char *__name, int __flags);
|
||||
|
||||
@@ -297,7 +297,10 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
|
||||
// zero size in statedb can happen during migration
|
||||
|| (tmp->size != 0 && fs->size != tmp->size))) {
|
||||
|
||||
if (fs->size == tmp->size && tmp->checksumTypeId) {
|
||||
// Checksum comparison at this stage is only enabled for .eml files,
|
||||
// check #4754 #4755
|
||||
bool isEmlFile = csync_fnmatch("*.eml", file, FNM_CASEFOLD) == 0;
|
||||
if (isEmlFile && fs->size == tmp->size && tmp->checksumTypeId) {
|
||||
if (ctx->callbacks.checksum_hook) {
|
||||
st->checksum = ctx->callbacks.checksum_hook(
|
||||
file, tmp->checksumTypeId,
|
||||
|
||||
@@ -230,8 +230,10 @@ int csync_vio_local_stat(const char *uri, csync_vio_file_stat_t *buf) {
|
||||
ULARGE_INTEGER FileIndex;
|
||||
mbchar_t *wuri = c_utf8_path_to_locale( uri );
|
||||
|
||||
h = CreateFileW( wuri, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL+FILE_FLAG_BACKUP_SEMANTICS+FILE_FLAG_OPEN_REPARSE_POINT, NULL );
|
||||
h = CreateFileW( wuri, 0, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
NULL );
|
||||
if( h == INVALID_HANDLE_VALUE ) {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_CRIT, "CreateFileW failed on %s", uri );
|
||||
errno = GetLastError();
|
||||
|
||||
@@ -85,6 +85,16 @@ sub fromFileName($)
|
||||
}
|
||||
}
|
||||
|
||||
sub setCredentials
|
||||
{
|
||||
my ($dav, $user, $passwd) = @_;
|
||||
|
||||
$dav->credentials(-url=> $owncloud, -realm=>"sabre/dav",
|
||||
-user=> $user, -pass=> $passwd);
|
||||
$dav->credentials(-url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $user, -pass=> $passwd);
|
||||
}
|
||||
|
||||
|
||||
sub initTesting(;$)
|
||||
{
|
||||
@@ -127,9 +137,7 @@ sub initTesting(;$)
|
||||
my $ua = HTTP::DAV::UserAgent->new(keep_alive => 1 );
|
||||
$d = HTTP::DAV->new(-useragent => $ua);
|
||||
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $user,
|
||||
-pass=> $passwd );
|
||||
setCredentials($d, $user, $passwd);
|
||||
# $d->DebugLevel(3);
|
||||
$prefix = "t1" unless( defined $prefix );
|
||||
|
||||
@@ -193,9 +201,7 @@ sub removeRemoteDir($;$)
|
||||
|
||||
my $url = testDirUrl() . $dir;
|
||||
if( $optionsRef && $optionsRef->{user} && $optionsRef->{passwd} ) {
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $optionsRef->{user},
|
||||
-pass=> $optionsRef->{passwd} );
|
||||
setCredentials($d, $optionsRef->{user}, $optionsRef->{passwd});
|
||||
if( $optionsRef->{url} ) {
|
||||
$url = $optionsRef->{url} . $dir;
|
||||
}
|
||||
@@ -219,9 +225,7 @@ sub createRemoteDir(;$$)
|
||||
my $url = testDirUrl() . $dir;
|
||||
|
||||
if( $optionsRef && $optionsRef->{user} && $optionsRef->{passwd} ) {
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $optionsRef->{user},
|
||||
-pass=> $optionsRef->{passwd} );
|
||||
setCredentials($d, $optionsRef->{user}, $optionsRef->{passwd});
|
||||
if( $optionsRef->{url} ) {
|
||||
$url = $optionsRef->{url} . $dir;
|
||||
}
|
||||
@@ -396,9 +400,7 @@ sub traverse( $$;$ )
|
||||
my %seen;
|
||||
|
||||
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $user,
|
||||
-pass=> $passwd );
|
||||
setCredentials($d, $user, $passwd);
|
||||
$d->open( $owncloud );
|
||||
|
||||
if( my $r = $d->propfind( -url => $url, -depth => 1 ) ) {
|
||||
@@ -513,9 +515,7 @@ sub put_to_dir( $$;$ )
|
||||
my $targetUrl = testDirUrl();
|
||||
|
||||
if( $optionsRef && $optionsRef->{user} && $optionsRef->{passwd} ) {
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $optionsRef->{user},
|
||||
-pass=> $optionsRef->{passwd} );
|
||||
setCredentials($d, $optionsRef->{user}, $optionsRef->{passwd});
|
||||
if( $optionsRef->{url} ) {
|
||||
$targetUrl = $optionsRef->{url};
|
||||
}
|
||||
@@ -649,9 +649,7 @@ sub moveRemoteFile($$;$)
|
||||
{
|
||||
my ($from, $to, $no_testdir) = @_;
|
||||
|
||||
$d->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $user,
|
||||
-pass=> $passwd );
|
||||
setCredentials($d, $user, $passwd);
|
||||
|
||||
my $fromUrl = testDirUrl(). $from;
|
||||
my $toUrl = testDirUrl() . $to;
|
||||
@@ -725,9 +723,7 @@ sub createShare($$)
|
||||
|
||||
my $dd = HTTP::DAV->new();
|
||||
|
||||
$dd->credentials( -url=> $owncloud, -realm=>"ownCloud",
|
||||
-user=> $share_user,
|
||||
-pass=> $share_passwd );
|
||||
setCredentials($dd, $share_user, $share_passwd);
|
||||
$dd->open( $owncloud);
|
||||
|
||||
# create a remote dir
|
||||
@@ -769,9 +765,7 @@ sub removeShare($$)
|
||||
|
||||
my $dd = HTTP::DAV->new();
|
||||
|
||||
$dd->credentials( -url => $owncloud, -realm=>"ownCloud",
|
||||
-user => $share_user,
|
||||
-pass => $share_passwd );
|
||||
setCredentials($dd, $share_user, $share_passwd);
|
||||
$dd->open( $owncloud);
|
||||
|
||||
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 });
|
||||
|
||||
@@ -146,9 +146,9 @@ assertLocalAndRemoteDir( '', 0);
|
||||
|
||||
# create a true conflict.
|
||||
printInfo( "Create a conflict." );
|
||||
system( "echo \"This is more stuff\" >> /tmp/kernelcrash.txt" );
|
||||
system( "echo \"This is more server stuff\" >> /tmp/kernelcrash.txt" );
|
||||
put_to_dir( '/tmp/kernelcrash.txt', 'remoteToLocal1' );
|
||||
system( "sleep 2 && touch " . localDir() . "remoteToLocal1/kernelcrash.txt" );
|
||||
system( "sleep 2 && echo \"This is more client stuff\" >> " . localDir() . "remoteToLocal1/kernelcrash.txt" );
|
||||
csync();
|
||||
assertLocalAndRemoteDir( '', 1);
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ assert($emlpropafter->get_property( "getlastmodified" ) eq
|
||||
$emlpropbefore->get_property( "getlastmodified" ));
|
||||
|
||||
printInfo( "Change content of eml file (but not size)");
|
||||
system( "sed -i -e 's/in/IN/' $locDir/test.eml" );
|
||||
system( "sleep 1 && sed -i -e 's/in/IN/' $locDir/test.eml" );
|
||||
|
||||
csync( );
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'ownCloud Client Manual'
|
||||
copyright = u'2013, The ownCloud developers'
|
||||
copyright = u'2013-2016, The ownCloud developers'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -250,7 +250,7 @@ texinfo_documents = [
|
||||
epub_title = u'ownCloud Client Manual'
|
||||
epub_author = u'The ownCloud developers'
|
||||
epub_publisher = u'The ownCloud developers'
|
||||
epub_copyright = u'2013, The ownCloud developers'
|
||||
epub_copyright = u'2013-2016, The ownCloud developers'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 86 KiB Depois Largura: | Altura: | Tamanho: 52 KiB |
|
Depois Largura: | Altura: | Tamanho: 82 KiB |
|
Antes Largura: | Altura: | Tamanho: 49 KiB Depois Largura: | Altura: | Tamanho: 58 KiB |
|
Antes Largura: | Altura: | Tamanho: 58 KiB Depois Largura: | Altura: | Tamanho: 224 KiB |
|
Antes Largura: | Altura: | Tamanho: 23 KiB Depois Largura: | Altura: | Tamanho: 20 KiB |
|
Antes Largura: | Altura: | Tamanho: 24 KiB Depois Largura: | Altura: | Tamanho: 34 KiB |
@@ -4,16 +4,16 @@ ownCloud Desktop Client Manual
|
||||
==============================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
:maxdepth: 2
|
||||
|
||||
introduction
|
||||
installing
|
||||
navigating
|
||||
advancedusage
|
||||
autoupdate
|
||||
|
||||
building
|
||||
architecture
|
||||
troubleshooting
|
||||
faq
|
||||
glossary
|
||||
glossary
|
||||
|
||||
@@ -12,26 +12,22 @@ Desktop Sync client enables you to:
|
||||
Your files are always automatically synchronized between your ownCloud server
|
||||
and local PC.
|
||||
|
||||
.. note:: Because of various technical issues, desktop sync clients older than
|
||||
1.7 will not allowed to connect and sync with the ownCloud 8.1+ server. It
|
||||
is highly recommended to keep your client updated.
|
||||
|
||||
Because of various technical issues, desktop sync clients older than 1.7 will
|
||||
not allowed to connect and sync with the ownCloud 8.1+ server. It is highly
|
||||
recommended to keep your client updated.
|
||||
|
||||
Improvements and New Features
|
||||
-----------------------------
|
||||
|
||||
The 2.1 release of the ownCloud desktop sync client has many new features and
|
||||
The 2.2 release of the ownCloud desktop sync client has many new features and
|
||||
improvements. (See the `complete changelog
|
||||
<https://owncloud.org/changelog/desktop/>`_.)
|
||||
|
||||
* Improved appearance on HiDPI screens
|
||||
* Improved error messages
|
||||
* Several fixes/improvements to the sharing dialog
|
||||
* Several fixes/improvements to the server activity tab
|
||||
* Allow changeable upload chunk size in owncloud.cfg
|
||||
* Forget password on explicit sign-out
|
||||
* Windows: Fix deleting and replacing of read-only files
|
||||
* Share with internal ownCloud users from your desktop
|
||||
* Separate views for server activity, sync activity, and errors
|
||||
* Don't re-upload *eml-files if size and checksum are unchanged
|
||||
* Improved upload/download progress indicator
|
||||
* Show server notifications on the client
|
||||
* Improved sync speed
|
||||
* Improved handling of Win32 file locks and network files
|
||||
* Improved user notifications about ignored files and conflicts
|
||||
* Add warnings for old server versions
|
||||
* Update of QtKeyChain to support Windows credential store
|
||||
* Packaging of dolphin overlay icon module for bleeding edge distros
|
||||
|
||||
@@ -62,6 +62,7 @@ This menu provides the following options:
|
||||
* Recent Changes, showing latest activities
|
||||
* Settings
|
||||
* Help menu
|
||||
* Pause synchronizations
|
||||
* An option to log in or log out of all of your accounts at once
|
||||
* Quit ownCloud, logging out and closing the client
|
||||
|
||||
@@ -83,7 +84,7 @@ have the following features:
|
||||
* Connection status, showing which ownCloud server you are connected to, and
|
||||
your ownCloud username.
|
||||
* An **Account** button, which contains a dropdown menu with **Add New**,
|
||||
**Log In/Log Out**, and **Remove**.
|
||||
**Log Out**, and **Remove**.
|
||||
* Used and available space on the server.
|
||||
* Current synchronization status.
|
||||
* **Add Folder Sync Connection** button, which is active only when you have
|
||||
@@ -93,26 +94,17 @@ The little button with three dots (the overflow menu) that sits to the right of
|
||||
the sync status bar offers four additional options:
|
||||
|
||||
* Open Folder
|
||||
* Choose What to Sync
|
||||
* Choose What to Sync (This appears only when your file tree is collapsed, and
|
||||
expands the file tree)
|
||||
* Pause Sync / Resume Sync
|
||||
* Remove folder sync connection
|
||||
|
||||
**Open Folder** opens a file explorer window displaying the client-side folder
|
||||
that is being synced.
|
||||
|
||||
**Choose What to Sync** opens the folder sync tree view. Use this to sync all
|
||||
or only some of the folders in the folder tree.
|
||||
**Open Folder** opens your local ownCloud sync folder.
|
||||
|
||||
**Pause Sync** pauses sync operations without making any changes to your
|
||||
account. It will continue to update file and folder lists, without
|
||||
downloading or updating files. To stop all sync activity use **Remove Sync**.
|
||||
|
||||
**Resume Sync** resumes sync operations.
|
||||
|
||||
**Remove Sync** removes the sync connection without removing the account. This
|
||||
stops all sync activity, including file and folder list updates. If you want to
|
||||
synchronize the folder tree again then click the **Add Folder Sync Connection**
|
||||
button, and re-select the folder tree that you want to sync.
|
||||
downloading or updating files. To stop all sync activity use **Remove
|
||||
Folder Sync Connection**.
|
||||
|
||||
.. figure:: images/client-7.png
|
||||
:alt: Extra options for sync operations
|
||||
@@ -125,12 +117,10 @@ button, and re-select the folder tree that you want to sync.
|
||||
Adding New Accounts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You may configure multiple ownCloud accounts in your desktop sync client.
|
||||
Simply
|
||||
You may configure multiple ownCloud accounts in your desktop sync client. Simply
|
||||
click the **Account** > **Add New** button on any account tab to add a new
|
||||
account, and then follow the account creation wizard. The new account will
|
||||
appear as a new tab in the settings dialog, where you can adjust its settings
|
||||
at
|
||||
appear as a new tab in the settings dialog, where you can adjust its settings at
|
||||
any time. Use **Account** > **Remove** to delete accounts.
|
||||
|
||||
File Manager Overlay Icons
|
||||
@@ -175,9 +165,7 @@ the ``owncloud-client-nautilus`` plugin.) You can create share links, and share
|
||||
with internal ownCloud users the same way as in your ownCloud Web interface.
|
||||
|
||||
.. figure:: images/mac-share.png
|
||||
:alt: Sync client integration in Finder on Mac OS X.
|
||||
|
||||
*Shared ownCloud files in Finder on Mac OS X*
|
||||
:alt: Sync client integration in Windows Explorer.
|
||||
|
||||
Right-click your systray icon, hover over the account you want to use, and
|
||||
left-click "Open folder [folder name] to quickly enter your local ownCloud
|
||||
@@ -206,6 +194,25 @@ such as files not synced.
|
||||
|
||||
.. figure:: images/client-8.png
|
||||
:alt: Activity windows logs all server and client activities.
|
||||
|
||||
Server Notifications
|
||||
--------------------
|
||||
|
||||
Starting with version 2.2.0, the client will display notifications from your
|
||||
ownCloud server that require manual interaction by you. For example, when a
|
||||
user on a remote ownCloud creates a new Federated share for you, you can accept
|
||||
it from your desktop client.
|
||||
|
||||
The desktop client automatically checks for available notifications
|
||||
automatically on a regular basis. Notifications are displayed in the Server
|
||||
Activity tab, and if you have **Show Desktop Notifications** enabled (General
|
||||
tab) you'll also see a systray notification.
|
||||
|
||||
.. figure:: images/client12.png
|
||||
:alt: Activity window with notification.
|
||||
|
||||
This also displays notifications sent to users by the ownCloud admin via the
|
||||
Announcements app.
|
||||
|
||||
General Window
|
||||
--------------
|
||||
|
||||
@@ -36,7 +36,7 @@ endif()
|
||||
set(OWNCLOUDDOLPHINHELPER ${APPLICATION_EXECUTABLE}dolphinpluginhelper)
|
||||
add_library(${OWNCLOUDDOLPHINHELPER} SHARED ownclouddolphinpluginhelper.cpp)
|
||||
target_link_libraries(${OWNCLOUDDOLPHINHELPER} Qt5::Network)
|
||||
generate_export_header(${OWNCLOUDDOLPHINHELPER})
|
||||
generate_export_header(${OWNCLOUDDOLPHINHELPER} BASE_NAME ownclouddolphinpluginhelper)
|
||||
install(TARGETS ${OWNCLOUDDOLPHINHELPER} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
#---OVERLAY PLUGIN---
|
||||
|
||||
@@ -21,11 +21,15 @@ import socket
|
||||
|
||||
from gi.repository import GObject, Nautilus
|
||||
|
||||
print("Initializing owncloud-client-nautilus extension")
|
||||
|
||||
# Do not touch the following line.
|
||||
# Please do not touch the following line.
|
||||
# The reason is that we use a script to adopt this file for branding
|
||||
# by replacing this line with the branding app name. If the following
|
||||
# line is changed, the script can not match the pattern and fails.
|
||||
appname = 'ownCloud'
|
||||
|
||||
print("Initializing "+appname+"-client-nautilus extension")
|
||||
|
||||
|
||||
def get_local_path(url):
|
||||
if url[0:7] == 'file://':
|
||||
url = url[7:]
|
||||
@@ -66,6 +70,7 @@ class SocketConnect(GObject.GObject):
|
||||
GObject.timeout_add(5000, self._connectToSocketServer)
|
||||
|
||||
def sendCommand(self, cmd):
|
||||
# print("Server command: " + cmd)
|
||||
if self.connected:
|
||||
try:
|
||||
self._sock.send(cmd)
|
||||
@@ -180,10 +185,10 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||
if not syncedFile:
|
||||
return items
|
||||
|
||||
# Create an menu item
|
||||
# Create a menu item
|
||||
labelStr = "Share with " + appname + "..."
|
||||
item = Nautilus.MenuItem(name='NautilusPython::ShareItem', label=labelStr,
|
||||
tip='Share file %s through ownCloud' % file.get_name())
|
||||
tip='Share file {} through {}'.format(file.get_name(), appname) )
|
||||
item.connect("activate", self.menu_share, file)
|
||||
items.append(item)
|
||||
|
||||
@@ -232,6 +237,48 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
|
||||
|
||||
# Handles a single line of server response and sets the emblem
|
||||
def handle_commands(self, action, args):
|
||||
# file = args[0] # For debug only
|
||||
# print("Action for " + file + ": " + args[0]) # For debug only
|
||||
if action == 'STATUS':
|
||||
newState = args[0]
|
||||
filename = ':'.join(args[1:])
|
||||
|
||||
itemStore = self.find_item_for_file(filename)
|
||||
if itemStore:
|
||||
if( not itemStore['state'] or newState != itemStore['state'] ):
|
||||
item = itemStore['item']
|
||||
|
||||
# print("Setting emblem on " + filename + "<>" + emblem + "<>") # For debug only
|
||||
|
||||
# If an emblem is already set for this item, we need to
|
||||
# clear the existing extension info before setting a new one.
|
||||
#
|
||||
# That will also trigger a new call to
|
||||
# update_file_info for this item! That's why we set
|
||||
# skipNextUpdate to True: we don't want to pull the
|
||||
# current data from the client after getting a push
|
||||
# notification.
|
||||
invalidate = itemStore['state'] != None
|
||||
if invalidate:
|
||||
item.invalidate_extension_info()
|
||||
self.set_emblem(item, newState)
|
||||
|
||||
socketConnect.nautilusVFSFile_table[filename] = {
|
||||
'item': item,
|
||||
'state': newState,
|
||||
'skipNextUpdate': invalidate }
|
||||
|
||||
elif action == 'UPDATE_VIEW':
|
||||
# Search all items underneath this path and invalidate them
|
||||
if args[0] in socketConnect.registered_paths:
|
||||
self.invalidate_items_underneath(args[0])
|
||||
|
||||
elif action == 'REGISTER_PATH':
|
||||
self.invalidate_items_underneath(args[0])
|
||||
elif action == 'UNREGISTER_PATH':
|
||||
self.invalidate_items_underneath(args[0])
|
||||
|
||||
def set_emblem(self, item, state):
|
||||
Emblems = { 'OK' : appname +'_ok',
|
||||
'SYNC' : appname +'_sync',
|
||||
'NEW' : appname +'_sync',
|
||||
@@ -245,33 +292,10 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
|
||||
'NOP' : ''
|
||||
}
|
||||
|
||||
# file = args[0] # For debug only
|
||||
# print("Action for " + file + ": " + args[0]) # For debug only
|
||||
if action == 'STATUS':
|
||||
newState = args[0]
|
||||
emblem = 'NOP' # Show nothing if no emblem si defined.
|
||||
if newState in Emblems:
|
||||
emblem = Emblems[newState]
|
||||
filename = ':'.join(args[1:])
|
||||
|
||||
if emblem:
|
||||
itemStore = self.find_item_for_file(filename)
|
||||
if itemStore:
|
||||
if( not itemStore['state'] or newState != itemStore['state'] ):
|
||||
item = itemStore['item']
|
||||
item.add_emblem(emblem)
|
||||
# print("Setting emblem on " + filename + "<>" + emblem + "<>") # For debug only
|
||||
socketConnect.nautilusVFSFile_table[filename] = {'item': item, 'state':newState}
|
||||
|
||||
elif action == 'UPDATE_VIEW':
|
||||
# Search all items underneath this path and invalidate them
|
||||
if args[0] in socketConnect.registered_paths:
|
||||
self.invalidate_items_underneath(args[0])
|
||||
|
||||
elif action == 'REGISTER_PATH':
|
||||
self.invalidate_items_underneath(args[0])
|
||||
elif action == 'UNREGISTER_PATH':
|
||||
self.invalidate_items_underneath(args[0])
|
||||
emblem = 'NOP' # Show nothing if no emblem is defined.
|
||||
if state in Emblems:
|
||||
emblem = Emblems[state]
|
||||
item.add_emblem(emblem)
|
||||
|
||||
def update_file_info(self, item):
|
||||
if item.get_uri_scheme() != 'file':
|
||||
@@ -281,13 +305,28 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
|
||||
if item.is_directory():
|
||||
filename += '/'
|
||||
|
||||
inScope = False
|
||||
for reg_path in socketConnect.registered_paths:
|
||||
if filename.startswith(reg_path):
|
||||
socketConnect.nautilusVFSFile_table[filename] = {'item': item, 'state':''}
|
||||
|
||||
# item.add_string_attribute('share_state', "share state") # ?
|
||||
self.askForOverlay(filename)
|
||||
inScope = True
|
||||
break
|
||||
else:
|
||||
# print("Not in scope:" + filename) # For debug only
|
||||
pass
|
||||
|
||||
if not inScope:
|
||||
return
|
||||
|
||||
# Ask for the current state from the client -- unless this update was
|
||||
# triggered by receiving a STATUS message from the client in the first
|
||||
# place.
|
||||
itemStore = self.find_item_for_file(filename)
|
||||
if itemStore and itemStore['skipNextUpdate'] and itemStore['state']:
|
||||
itemStore['skipNextUpdate'] = False
|
||||
itemStore['item'] = item
|
||||
self.set_emblem(item, itemStore['state'])
|
||||
else:
|
||||
socketConnect.nautilusVFSFile_table[filename] = {
|
||||
'item': item,
|
||||
'state': None,
|
||||
'skipNextUpdate': False }
|
||||
|
||||
# item.add_string_attribute('share_state', "share state") # ?
|
||||
self.askForOverlay(filename)
|
||||
|
||||
@@ -36,8 +36,7 @@ using namespace std;
|
||||
|
||||
OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
|
||||
{
|
||||
auto pipename = std::wstring(L"\\\\.\\pipe\\");
|
||||
pipename += L"ownCloud";
|
||||
auto pipename = CommunicationSocket::DefaultPipePath();
|
||||
|
||||
CommunicationSocket socket;
|
||||
if (!WaitNamedPipe(pipename.data(), PIPE_TIMEOUT)) {
|
||||
@@ -72,8 +71,7 @@ OCClientInterface::ContextMenuInfo OCClientInterface::FetchInfo()
|
||||
|
||||
void OCClientInterface::ShareObject(const std::wstring &path)
|
||||
{
|
||||
auto pipename = std::wstring(L"\\\\.\\pipe\\");
|
||||
pipename += L"ownCloud";
|
||||
auto pipename = CommunicationSocket::DefaultPipePath();
|
||||
|
||||
CommunicationSocket socket;
|
||||
if (!WaitNamedPipe(pipename.data(), PIPE_TIMEOUT)) {
|
||||
|
||||
@@ -24,11 +24,31 @@
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#define BUFSIZE 1024
|
||||
#define DEFAULT_BUFLEN 4096
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DEFAULT_BUFLEN 4096
|
||||
namespace {
|
||||
|
||||
std::wstring getUserName() {
|
||||
DWORD len = DEFAULT_BUFLEN;
|
||||
TCHAR buf[DEFAULT_BUFLEN];
|
||||
if (GetUserName(buf, &len)) {
|
||||
return std::wstring(&buf[0], len);
|
||||
} else {
|
||||
return std::wstring();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::wstring CommunicationSocket::DefaultPipePath()
|
||||
{
|
||||
auto pipename = std::wstring(L"\\\\.\\pipe\\");
|
||||
pipename += L"ownCloud\\";
|
||||
pipename += getUserName();
|
||||
return pipename;
|
||||
}
|
||||
|
||||
CommunicationSocket::CommunicationSocket()
|
||||
: _pipe(INVALID_HANDLE_VALUE)
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
class __declspec(dllexport) CommunicationSocket
|
||||
{
|
||||
public:
|
||||
static std::wstring DefaultPipePath();
|
||||
|
||||
CommunicationSocket();
|
||||
~CommunicationSocket();
|
||||
|
||||
@@ -43,4 +45,4 @@ private:
|
||||
bool _connected;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
// This code is run in a thread
|
||||
void RemotePathChecker::workerThreadLoop()
|
||||
{
|
||||
auto pipename = std::wstring(L"\\\\.\\pipe\\");
|
||||
pipename += L"ownCloud";
|
||||
|
||||
auto pipename = CommunicationSocket::DefaultPipePath();
|
||||
bool connected = false;
|
||||
CommunicationSocket socket;
|
||||
std::unordered_set<std::wstring> asked;
|
||||
@@ -241,4 +238,4 @@ RemotePathChecker::FileState RemotePathChecker::_StrToFileState(const std::wstri
|
||||
}
|
||||
|
||||
return StateNone;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,9 @@ public:
|
||||
|
||||
\sa QStringTokenizer, QByteArrayTokenizer, StringTokenizer, WStringTokenizer
|
||||
*/
|
||||
QTokenizer(const T& string, const T& delimiters) {
|
||||
d.reset(new QTokenizerPrivate<T, const_iterator>(string, delimiters));
|
||||
}
|
||||
QTokenizer(const T& string, const T& delimiters)
|
||||
: d(new QTokenizerPrivate<T, const_iterator>(string, delimiters))
|
||||
{ }
|
||||
|
||||
/*!
|
||||
Whether or not to return delimiters as tokens
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/******************************************************************************
|
||||
** This file is an amalgamation of many separate C source files from SQLite
|
||||
** version 3.12.1. By combining all the individual C code files into this
|
||||
** version 3.12.2. By combining all the individual C code files into this
|
||||
** single large file, the entire code can be compiled as a single translation
|
||||
** unit. This allows many compilers to do optimizations that would not be
|
||||
** possible if the files were compiled separately. Performance improvements
|
||||
@@ -336,9 +336,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.12.1"
|
||||
#define SQLITE_VERSION_NUMBER 3012001
|
||||
#define SQLITE_SOURCE_ID "2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d"
|
||||
#define SQLITE_VERSION "3.12.2"
|
||||
#define SQLITE_VERSION_NUMBER 3012002
|
||||
#define SQLITE_SOURCE_ID "2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
@@ -63941,6 +63941,28 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
|
||||
pPage = pCur->apPage[iCellDepth];
|
||||
pCell = findCell(pPage, iCellIdx);
|
||||
|
||||
/* If the bPreserve flag is set to true, then the cursor position must
|
||||
** be preserved following this delete operation. If the current delete
|
||||
** will cause a b-tree rebalance, then this is done by saving the cursor
|
||||
** key and leaving the cursor in CURSOR_REQUIRESEEK state before
|
||||
** returning.
|
||||
**
|
||||
** Or, if the current delete will not cause a rebalance, then the cursor
|
||||
** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately
|
||||
** before or after the deleted entry. In this case set bSkipnext to true. */
|
||||
if( bPreserve ){
|
||||
if( !pPage->leaf
|
||||
|| (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
|
||||
){
|
||||
/* A b-tree rebalance will be required after deleting this entry.
|
||||
** Save the cursor key. */
|
||||
rc = saveCursorKey(pCur);
|
||||
if( rc ) return rc;
|
||||
}else{
|
||||
bSkipnext = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the page containing the entry to delete is not a leaf page, move
|
||||
** the cursor to the largest entry in the tree that is smaller than
|
||||
** the entry being deleted. This cell will replace the cell being deleted
|
||||
@@ -63967,28 +63989,6 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){
|
||||
invalidateIncrblobCursors(p, pCur->info.nKey, 0);
|
||||
}
|
||||
|
||||
/* If the bPreserve flag is set to true, then the cursor position must
|
||||
** be preserved following this delete operation. If the current delete
|
||||
** will cause a b-tree rebalance, then this is done by saving the cursor
|
||||
** key and leaving the cursor in CURSOR_REQUIRESEEK state before
|
||||
** returning.
|
||||
**
|
||||
** Or, if the current delete will not cause a rebalance, then the cursor
|
||||
** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately
|
||||
** before or after the deleted entry. In this case set bSkipnext to true. */
|
||||
if( bPreserve ){
|
||||
if( !pPage->leaf
|
||||
|| (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
|
||||
){
|
||||
/* A b-tree rebalance will be required after deleting this entry.
|
||||
** Save the cursor key. */
|
||||
rc = saveCursorKey(pCur);
|
||||
if( rc ) return rc;
|
||||
}else{
|
||||
bSkipnext = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make the page containing the entry to be deleted writable. Then free any
|
||||
** overflow pages associated with the entry and finally remove the cell
|
||||
** itself from within the page. */
|
||||
@@ -82769,7 +82769,6 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
|
||||
){
|
||||
int pgsz; /* Page size of main database */
|
||||
int i; /* Used to iterate through aTask[] */
|
||||
int mxCache; /* Cache size */
|
||||
VdbeSorter *pSorter; /* The new sorter */
|
||||
KeyInfo *pKeyInfo; /* Copy of pCsr->pKeyInfo with db==0 */
|
||||
int szKeyInfo; /* Size of pCsr->pKeyInfo in bytes */
|
||||
@@ -82826,11 +82825,20 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit(
|
||||
}
|
||||
|
||||
if( !sqlite3TempInMemory(db) ){
|
||||
i64 mxCache; /* Cache size in bytes*/
|
||||
u32 szPma = sqlite3GlobalConfig.szPma;
|
||||
pSorter->mnPmaSize = szPma * pgsz;
|
||||
|
||||
mxCache = db->aDb[0].pSchema->cache_size;
|
||||
if( mxCache<(int)szPma ) mxCache = (int)szPma;
|
||||
pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ);
|
||||
if( mxCache<0 ){
|
||||
/* A negative cache-size value C indicates that the cache is abs(C)
|
||||
** KiB in size. */
|
||||
mxCache = mxCache * -1024;
|
||||
}else{
|
||||
mxCache = mxCache * pgsz;
|
||||
}
|
||||
mxCache = MIN(mxCache, SQLITE_MAX_PMASZ);
|
||||
pSorter->mxPmaSize = MAX(pSorter->mnPmaSize, (int)mxCache);
|
||||
|
||||
/* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
|
||||
** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary
|
||||
@@ -95563,6 +95571,7 @@ SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){
|
||||
zType = z + sqlite3Strlen30(z) + 1;
|
||||
memcpy(zType, pType->z, pType->n);
|
||||
zType[pType->n] = 0;
|
||||
sqlite3Dequote(zType);
|
||||
pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst);
|
||||
pCol->colFlags |= COLFLAG_HASTYPE;
|
||||
}
|
||||
@@ -121360,7 +121369,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart(
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
|
||||
/* These registers need to be preserved in case there is an IN operator
|
||||
** loop. So we could deallocate the registers here (and potentially
|
||||
** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems
|
||||
** simpler and safer to simply not reuse the registers.
|
||||
**
|
||||
** sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
|
||||
*/
|
||||
sqlite3ExprCachePop(pParse);
|
||||
}else
|
||||
#endif /* SQLITE_OMIT_VIRTUALTABLE */
|
||||
@@ -185458,7 +185473,7 @@ static void fts5SourceIdFunc(
|
||||
){
|
||||
assert( nArg==0 );
|
||||
UNUSED_PARAM2(nArg, apUnused);
|
||||
sqlite3_result_text(pCtx, "fts5: 2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d", -1, SQLITE_TRANSIENT);
|
||||
sqlite3_result_text(pCtx, "fts5: 2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e", -1, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
static int fts5Init(sqlite3 *db){
|
||||
|
||||
@@ -111,9 +111,9 @@ extern "C" {
|
||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.12.1"
|
||||
#define SQLITE_VERSION_NUMBER 3012001
|
||||
#define SQLITE_SOURCE_ID "2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d"
|
||||
#define SQLITE_VERSION "3.12.2"
|
||||
#define SQLITE_VERSION_NUMBER 3012002
|
||||
#define SQLITE_SOURCE_ID "2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
||||
@@ -54,6 +54,7 @@ set(client_SRCS
|
||||
folderwizard.cpp
|
||||
generalsettings.cpp
|
||||
ignorelisteditor.cpp
|
||||
lockwatcher.cpp
|
||||
logbrowser.cpp
|
||||
networksettings.cpp
|
||||
ocsjob.cpp
|
||||
|
||||
@@ -197,8 +197,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
return;
|
||||
}
|
||||
|
||||
QString alias = _model->data( index, FolderStatusDelegate::FolderAliasRole ).toString();
|
||||
if (alias.isEmpty()) {
|
||||
if (_model->classify(index) != FolderStatusModel::RootFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -277,7 +276,6 @@ void AccountSettings::slotFolderWizardAccepted()
|
||||
qDebug() << "* Folder wizard completed";
|
||||
|
||||
FolderDefinition definition;
|
||||
definition.alias = folderWizard->field(QLatin1String("alias")).toString();
|
||||
definition.localPath = FolderDefinition::prepareLocalPath(
|
||||
folderWizard->field(QLatin1String("sourceFolder")).toString());
|
||||
definition.targetPath = folderWizard->property("targetPath").toString();
|
||||
@@ -335,12 +333,12 @@ void AccountSettings::slotRemoveCurrentFolder()
|
||||
qDebug() << "Remove Folder alias " << alias;
|
||||
if( !alias.isEmpty() ) {
|
||||
FolderMan *folderMan = FolderMan::instance();
|
||||
QString aliasGui = folderMan->folder(alias)->aliasGui();
|
||||
QString shortGuiLocalPath = folderMan->folder(alias)->shortGuiLocalPath();
|
||||
|
||||
QMessageBox messageBox(QMessageBox::Question,
|
||||
tr("Confirm Folder Sync Connection Removal"),
|
||||
tr("<p>Do you really want to stop syncing the folder <i>%1</i>?</p>"
|
||||
"<p><b>Note:</b> This will <b>not</b> delete any files.</p>").arg(aliasGui),
|
||||
"<p><b>Note:</b> This will <b>not</b> delete any files.</p>").arg(shortGuiLocalPath),
|
||||
QMessageBox::NoButton,
|
||||
this);
|
||||
QPushButton* yesButton =
|
||||
|
||||
@@ -95,6 +95,9 @@ void AccountState::setState(State state)
|
||||
} else if (oldState == SignedOut && _state == Disconnected) {
|
||||
checkConnectivity();
|
||||
}
|
||||
if (oldState == Connected || _state == Connected) {
|
||||
emit isConnectedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// might not have changed but the underlying _connectionErrors might have
|
||||
@@ -149,11 +152,6 @@ bool AccountState::isConnectedOrTemporarilyUnavailable() const
|
||||
return isConnected() || _state == ServiceUnavailable;
|
||||
}
|
||||
|
||||
bool AccountState::canSync() const
|
||||
{
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
void AccountState::tagLastSuccessfullETagRequest()
|
||||
{
|
||||
_timeSinceLastETagCheck.restart();
|
||||
|
||||
@@ -102,9 +102,6 @@ public:
|
||||
bool isConnected() const;
|
||||
bool isConnectedOrTemporarilyUnavailable() const;
|
||||
|
||||
/// Returns whether sync actions are allowed to run.
|
||||
bool canSync() const;
|
||||
|
||||
/// Triggers a ping to the server to update state and
|
||||
/// connection status and errors.
|
||||
void checkConnectivity();
|
||||
@@ -130,6 +127,7 @@ private:
|
||||
|
||||
signals:
|
||||
void stateChanged(int state);
|
||||
void isConnectedChanged();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList& errors);
|
||||
|
||||
@@ -38,7 +38,12 @@ namespace OCC
|
||||
|
||||
class UserAgentWebPage : public QWebPage {
|
||||
public:
|
||||
UserAgentWebPage(QObject *parent) : QWebPage(parent) {}
|
||||
UserAgentWebPage(QObject *parent) : QWebPage(parent)
|
||||
{
|
||||
if (!qgetenv("OWNCLOUD_SHIBBOLETH_DEBUG").isEmpty()) {
|
||||
settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
||||
}
|
||||
}
|
||||
QString userAgentForUrl(const QUrl &url ) const {
|
||||
return QWebPage::userAgentForUrl(url) + " " + Utility::userAgentString();
|
||||
}
|
||||
|
||||
@@ -67,8 +67,13 @@ void ShibbolethCredentials::setAccount(Account* account)
|
||||
{
|
||||
AbstractCredentials::setAccount(account);
|
||||
|
||||
// This is for existing saved accounts.
|
||||
if (_user.isEmpty()) {
|
||||
_user = _account->credentialSetting(QLatin1String(userC)).toString();
|
||||
}
|
||||
|
||||
// When constructed with a cookie (by the wizard), we usually don't know the
|
||||
// user name yet. Request it now.
|
||||
// user name yet. Request it now from the server.
|
||||
if (_ready && _user.isEmpty()) {
|
||||
QTimer::singleShot(1234, this, SLOT(slotFetchUser()));
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ Folder::Folder(const FolderDefinition& definition,
|
||||
if (!setIgnoredFiles())
|
||||
qWarning("Could not read system exclude file");
|
||||
|
||||
connect(_accountState.data(), SIGNAL(isConnectedChanged()), this, SIGNAL(canSyncChanged()));
|
||||
connect(_engine.data(), SIGNAL(rootEtag(QString)), this, SLOT(etagRetreivedFromSyncEngine(QString)));
|
||||
connect(_engine.data(), SIGNAL(treeWalkResult(const SyncFileItemVector&)),
|
||||
this, SLOT(slotThreadTreeWalkResult(const SyncFileItemVector&)), Qt::QueuedConnection);
|
||||
@@ -115,6 +116,7 @@ Folder::Folder(const FolderDefinition& definition,
|
||||
connect(_engine.data(), SIGNAL(itemCompleted(const SyncFileItem &, const PropagatorJob &)),
|
||||
this, SLOT(slotItemCompleted(const SyncFileItem &, const PropagatorJob &)));
|
||||
connect(_engine.data(), SIGNAL(newBigFolder(QString)), this, SLOT(slotNewBigFolderDiscovered(QString)));
|
||||
connect(_engine.data(), SIGNAL(seenLockedFile(QString)), FolderMan::instance(), SLOT(slotSyncOnceFileUnlocks(QString)));
|
||||
}
|
||||
|
||||
Folder::~Folder()
|
||||
@@ -142,7 +144,7 @@ void Folder::checkLocalPath()
|
||||
}
|
||||
}
|
||||
|
||||
QString Folder::aliasGui() const
|
||||
QString Folder::shortGuiRemotePathOrAppName() const
|
||||
{
|
||||
if (remotePath().length() > 0 && remotePath() != QLatin1String("/")) {
|
||||
QString a = QFile(remotePath()).fileName();
|
||||
@@ -169,7 +171,7 @@ QString Folder::path() const
|
||||
return p;
|
||||
}
|
||||
|
||||
QString Folder::shortGuiPath() const
|
||||
QString Folder::shortGuiLocalPath() const
|
||||
{
|
||||
QString p = _definition.localPath;
|
||||
QString home = QDir::homePath();
|
||||
@@ -229,7 +231,7 @@ bool Folder::syncPaused() const
|
||||
|
||||
bool Folder::canSync() const
|
||||
{
|
||||
return !syncPaused() && accountState()->canSync();
|
||||
return !syncPaused() && accountState()->isConnected();
|
||||
}
|
||||
|
||||
void Folder::setSyncPaused( bool paused )
|
||||
@@ -248,6 +250,7 @@ void Folder::setSyncPaused( bool paused )
|
||||
}
|
||||
emit syncPausedChanged(this, paused);
|
||||
emit syncStateChange();
|
||||
emit canSyncChanged();
|
||||
}
|
||||
|
||||
void Folder::setSyncState(SyncResult::Status state)
|
||||
@@ -578,6 +581,7 @@ void Folder::slotWatchedPathChanged(const QString& path)
|
||||
// When no sync is running or it's in the prepare phase, we can
|
||||
// always schedule a new sync.
|
||||
if (! _engine->isSyncRunning() || _syncResult.status() == SyncResult::SyncPrepare) {
|
||||
emit watchedFileChangedExternally(path);
|
||||
emit scheduleToSync(this);
|
||||
return;
|
||||
}
|
||||
@@ -601,6 +605,7 @@ void Folder::slotWatchedPathChanged(const QString& path)
|
||||
#endif
|
||||
|
||||
if (! ownChange) {
|
||||
emit watchedFileChangedExternally(path);
|
||||
emit scheduleToSync(this);
|
||||
}
|
||||
}
|
||||
@@ -683,7 +688,8 @@ void Folder::wipe()
|
||||
QFile::remove( stateDbFile + "-wal" );
|
||||
QFile::remove( stateDbFile + "-journal" );
|
||||
|
||||
FolderMan::instance()->socketApi()->slotRegisterPath(alias());
|
||||
if (canSync())
|
||||
FolderMan::instance()->socketApi()->slotRegisterPath(alias());
|
||||
}
|
||||
|
||||
bool Folder::setIgnoredFiles()
|
||||
@@ -975,7 +981,7 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
|
||||
"the files were manually removed.\n"
|
||||
"Are you sure you want to perform this operation?");
|
||||
QMessageBox msgBox(QMessageBox::Warning, tr("Remove All Files?"),
|
||||
msg.arg(aliasGui()));
|
||||
msg.arg(shortGuiLocalPath()));
|
||||
msgBox.addButton(tr("Remove all files"), QMessageBox::DestructiveRole);
|
||||
QPushButton* keepBtn = msgBox.addButton(tr("Keep files"), QMessageBox::AcceptRole);
|
||||
if (msgBox.exec() == -1) {
|
||||
@@ -1001,7 +1007,7 @@ void Folder::slotAboutToRestoreBackup(bool *restore)
|
||||
"file in an earlier state. "
|
||||
"Do you want to keep your local most recent files as conflict files?");
|
||||
QMessageBox msgBox(QMessageBox::Warning, tr("Backup detected"),
|
||||
msg.arg(aliasGui()));
|
||||
msg.arg(shortGuiLocalPath()));
|
||||
msgBox.addButton(tr("Normal Synchronisation"), QMessageBox::DestructiveRole);
|
||||
QPushButton* keepBtn = msgBox.addButton(tr("Keep Local Files as Conflict"), QMessageBox::AcceptRole);
|
||||
|
||||
|
||||
@@ -95,12 +95,12 @@ public:
|
||||
* alias or nickname
|
||||
*/
|
||||
QString alias() const;
|
||||
QString aliasGui() const; // since 2.0 we don't want to show aliases anymore, show the path instead
|
||||
QString shortGuiRemotePathOrAppName() const; // since 2.0 we don't want to show aliases anymore, show the path instead
|
||||
|
||||
/**
|
||||
* short path to display on the GUI (native separators)
|
||||
* short local path to display on the GUI (native separators)
|
||||
*/
|
||||
QString shortGuiPath() const;
|
||||
QString shortGuiLocalPath() const;
|
||||
|
||||
/**
|
||||
* local folder path
|
||||
@@ -131,8 +131,6 @@ public:
|
||||
|
||||
/**
|
||||
* Returns true when the folder may sync.
|
||||
*
|
||||
* !syncPaused() && accountState->canSync().
|
||||
*/
|
||||
bool canSync() const;
|
||||
|
||||
@@ -203,6 +201,13 @@ signals:
|
||||
void progressInfo(const ProgressInfo& progress);
|
||||
void newBigFolderDiscovered(const QString &); // A new folder bigger than the threshold was discovered
|
||||
void syncPausedChanged(Folder*, bool paused);
|
||||
void canSyncChanged();
|
||||
|
||||
/**
|
||||
* Fires for each change inside this folder that wasn't caused
|
||||
* by sync activity.
|
||||
*/
|
||||
void watchedFileChangedExternally(const QString& path);
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "accountstate.h"
|
||||
#include "accountmanager.h"
|
||||
#include "filesystem.h"
|
||||
#include "lockwatcher.h"
|
||||
#include <syncengine.h>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
@@ -44,6 +45,7 @@ FolderMan::FolderMan(QObject *parent) :
|
||||
QObject(parent),
|
||||
_currentSyncFolder(0),
|
||||
_syncEnabled( true ),
|
||||
_lockWatcher(new LockWatcher),
|
||||
_appRestartRequired(false)
|
||||
{
|
||||
Q_ASSERT(!_instance);
|
||||
@@ -64,6 +66,9 @@ FolderMan::FolderMan(QObject *parent) :
|
||||
|
||||
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState*)),
|
||||
SLOT(slotRemoveFoldersForAccount(AccountState*)));
|
||||
|
||||
connect(_lockWatcher.data(), SIGNAL(fileUnlocked(QString)),
|
||||
SLOT(slotScheduleFolderOwningFile(QString)));
|
||||
}
|
||||
|
||||
FolderMan *FolderMan::instance()
|
||||
@@ -107,6 +112,8 @@ void FolderMan::unloadFolder( Folder *f )
|
||||
this, SLOT(slotFolderSyncPaused(Folder*,bool)));
|
||||
disconnect(&f->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
|
||||
_socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus)));
|
||||
disconnect(f, SIGNAL(watchedFileChangedExternally(QString)),
|
||||
&f->syncEngine().syncFileStatusTracker(), SLOT(slotPathTouched(QString)));
|
||||
}
|
||||
|
||||
int FolderMan::unloadAndDeleteAllFolders()
|
||||
@@ -145,11 +152,13 @@ void FolderMan::registerFolderMonitor( Folder *folder )
|
||||
// to the signal mapper which maps to the folder alias. The changed path
|
||||
// is lost this way, but we do not need it for the current implementation.
|
||||
connect(fw, SIGNAL(pathChanged(QString)), folder, SLOT(slotWatchedPathChanged(QString)));
|
||||
|
||||
_folderWatchers.insert(folder->alias(), fw);
|
||||
}
|
||||
|
||||
// register the folder with the socket API
|
||||
_socketApi->slotRegisterPath(folder->alias());
|
||||
if (folder->canSync())
|
||||
_socketApi->slotRegisterPath(folder->alias());
|
||||
}
|
||||
|
||||
void FolderMan::addMonitorPath( const QString& alias, const QString& path )
|
||||
@@ -200,7 +209,7 @@ int FolderMan::setupFolders()
|
||||
foreach (const auto& folderAlias, settings->childGroups()) {
|
||||
FolderDefinition folderDefinition;
|
||||
if (FolderDefinition::load(*settings, folderAlias, &folderDefinition)) {
|
||||
Folder* f = addFolderInternal(folderDefinition, account.data());
|
||||
Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
|
||||
if (f) {
|
||||
slotScheduleSync(f);
|
||||
emit folderSyncStateChange(f);
|
||||
@@ -419,6 +428,17 @@ void FolderMan::slotFolderSyncPaused( Folder *f, bool paused )
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMan::slotFolderCanSyncChanged()
|
||||
{
|
||||
Folder *f = qobject_cast<Folder*>(sender());
|
||||
Q_ASSERT(f);
|
||||
if (f->canSync()) {
|
||||
_socketApi->slotRegisterPath(f->alias());
|
||||
} else {
|
||||
_socketApi->slotUnregisterPath(f->alias());
|
||||
}
|
||||
}
|
||||
|
||||
// this really terminates the current sync process
|
||||
// ie. no questions, no prisoners
|
||||
// csync still remains in a stable state, regardless of that.
|
||||
@@ -457,6 +477,11 @@ void FolderMan::slotScheduleAppRestart()
|
||||
qDebug() << "## Application restart requested!";
|
||||
}
|
||||
|
||||
void FolderMan::slotSyncOnceFileUnlocks(const QString& path)
|
||||
{
|
||||
_lockWatcher->addFile(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* if a folder wants to be synced, it calls this slot and is added
|
||||
* to the queue. The slot to actually start a sync is called afterwards.
|
||||
@@ -536,7 +561,7 @@ void FolderMan::slotAccountStateChanged()
|
||||
}
|
||||
QString accountName = accountState->account()->displayName();
|
||||
|
||||
if (accountState->canSync()) {
|
||||
if (accountState->isConnected()) {
|
||||
qDebug() << "Account" << accountName << "connected, scheduling its folders";
|
||||
|
||||
foreach (Folder *f, _folderMap.values()) {
|
||||
@@ -741,6 +766,13 @@ void FolderMan::slotServerVersionChanged(Account *account)
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMan::slotScheduleFolderOwningFile(const QString& path)
|
||||
{
|
||||
if (Folder* f = folderForPath(path)) {
|
||||
slotScheduleSync(f);
|
||||
}
|
||||
}
|
||||
|
||||
void FolderMan::slotFolderSyncStarted( )
|
||||
{
|
||||
qDebug() << ">===================================== sync started for " << _currentSyncFolder->remoteUrl().toString();
|
||||
@@ -777,11 +809,18 @@ Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition&
|
||||
return folder;
|
||||
}
|
||||
|
||||
Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState)
|
||||
Folder* FolderMan::addFolderInternal(FolderDefinition folderDefinition, AccountState* accountState)
|
||||
{
|
||||
auto alias = folderDefinition.alias;
|
||||
int count = 0;
|
||||
while (folderDefinition.alias.isEmpty() || _folderMap.contains(folderDefinition.alias)) {
|
||||
// There is already a folder configured with this name and folder names need to be unique
|
||||
folderDefinition.alias = alias + QString::number(++count);
|
||||
}
|
||||
|
||||
auto folder = new Folder(folderDefinition, accountState, this );
|
||||
|
||||
qDebug() << "Adding folder to Folder Map " << folder;
|
||||
qDebug() << "Adding folder to Folder Map " << folder << folder->alias();
|
||||
_folderMap[folder->alias()] = folder;
|
||||
if (folder->syncPaused()) {
|
||||
_disabledFolders.insert(folder);
|
||||
@@ -793,8 +832,11 @@ Folder* FolderMan::addFolderInternal(const FolderDefinition& folderDefinition, A
|
||||
connect(folder, SIGNAL(syncFinished(SyncResult)), SLOT(slotFolderSyncFinished(SyncResult)));
|
||||
connect(folder, SIGNAL(syncStateChange()), SLOT(slotForwardFolderSyncStateChange()));
|
||||
connect(folder, SIGNAL(syncPausedChanged(Folder*,bool)), SLOT(slotFolderSyncPaused(Folder*,bool)));
|
||||
connect(folder, SIGNAL(canSyncChanged()), SLOT(slotFolderCanSyncChanged()));
|
||||
connect(&folder->syncEngine().syncFileStatusTracker(), SIGNAL(fileStatusChanged(const QString &, SyncFileStatus)),
|
||||
_socketApi.data(), SLOT(slotFileStatusChanged(const QString &, SyncFileStatus)));
|
||||
connect(folder, SIGNAL(watchedFileChangedExternally(QString)),
|
||||
&folder->syncEngine().syncFileStatusTracker(), SLOT(slotPathTouched(QString)));
|
||||
|
||||
registerFolderMonitor(folder);
|
||||
return folder;
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace OCC {
|
||||
class Application;
|
||||
class SyncResult;
|
||||
class SocketApi;
|
||||
class LockWatcher;
|
||||
|
||||
/**
|
||||
* @brief The FolderMan class
|
||||
@@ -143,6 +144,7 @@ signals:
|
||||
public slots:
|
||||
void slotRemoveFolder( Folder* );
|
||||
void slotFolderSyncPaused(Folder *, bool paused);
|
||||
void slotFolderCanSyncChanged();
|
||||
|
||||
void slotFolderSyncStarted();
|
||||
void slotFolderSyncFinished( const SyncResult& );
|
||||
@@ -184,6 +186,13 @@ public slots:
|
||||
*/
|
||||
void slotScheduleAppRestart();
|
||||
|
||||
/**
|
||||
* Triggers a sync run once the lock on the given file is removed.
|
||||
*
|
||||
* Automatically detemines the folder that's responsible for the file.
|
||||
*/
|
||||
void slotSyncOnceFileUnlocks(const QString& path);
|
||||
|
||||
private slots:
|
||||
// slot to take the next folder from queue and start syncing.
|
||||
void slotStartScheduledFolderSync();
|
||||
@@ -197,11 +206,17 @@ private slots:
|
||||
|
||||
void slotServerVersionChanged(Account* account);
|
||||
|
||||
/**
|
||||
* Schedules the folder for synchronization that contains
|
||||
* the file with the given path.
|
||||
*/
|
||||
void slotScheduleFolderOwningFile(const QString& path);
|
||||
|
||||
private:
|
||||
/** Adds a new folder, does not add it to the account settings and
|
||||
* does not set an account on the new folder.
|
||||
*/
|
||||
Folder* addFolderInternal(const FolderDefinition& folderDefinition, AccountState* accountState);
|
||||
Folder* addFolderInternal(FolderDefinition folderDefinition, AccountState* accountState);
|
||||
|
||||
/* unloads a folder object, does not delete it */
|
||||
void unloadFolder( Folder * );
|
||||
@@ -227,6 +242,7 @@ private:
|
||||
QPointer<RequestEtagJob> _currentEtagJob; // alias of Folder running the current RequestEtagJob
|
||||
|
||||
QMap<QString, FolderWatcher*> _folderWatchers;
|
||||
QScopedPointer<LockWatcher> _lockWatcher;
|
||||
QScopedPointer<SocketApi> _socketApi;
|
||||
|
||||
/** The aliases of folders that shall be synced. */
|
||||
|
||||
@@ -185,11 +185,11 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
|
||||
const bool accountConnected = _accountState->isConnected();
|
||||
|
||||
switch (role) {
|
||||
case FolderStatusDelegate::FolderPathRole : return f->shortGuiPath();
|
||||
case FolderStatusDelegate::FolderPathRole : return f->shortGuiLocalPath();
|
||||
case FolderStatusDelegate::FolderSecondPathRole : return f->remotePath();
|
||||
case FolderStatusDelegate::FolderErrorMsg : return f->syncResult().errorStrings();
|
||||
case FolderStatusDelegate::SyncRunning : return f->syncResult().status() == SyncResult::SyncRunning;
|
||||
case FolderStatusDelegate::HeaderRole : return f->aliasGui();
|
||||
case FolderStatusDelegate::HeaderRole : return f->shortGuiRemotePathOrAppName();
|
||||
case FolderStatusDelegate::FolderAliasRole : return f->alias();
|
||||
case FolderStatusDelegate::FolderSyncPaused : return f->syncPaused();
|
||||
case FolderStatusDelegate::FolderAccountConnected : return accountConnected;
|
||||
@@ -574,9 +574,6 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
|
||||
return;
|
||||
}
|
||||
|
||||
QVarLengthArray<int, 10> undecidedIndexes;
|
||||
QVector<SubFolderInfo> newSubs;
|
||||
|
||||
std::set<QString> selectiveSyncUndecidedSet; // not QSet because it's not sorted
|
||||
foreach (const QString &str, selectiveSyncUndecidedList) {
|
||||
if (str.startsWith(parentInfo->_path) || parentInfo->_path == QLatin1String("/")) {
|
||||
@@ -584,10 +581,16 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
|
||||
}
|
||||
}
|
||||
|
||||
newSubs.reserve(list.size() - 1);
|
||||
for (int i = 1; // skip the parent item (first in the list)
|
||||
i < list.size(); ++i) {
|
||||
const QString &path = list.at(i);
|
||||
QStringList sortedSubfolders = list;
|
||||
// skip the parent item (first in the list)
|
||||
sortedSubfolders.erase(sortedSubfolders.begin());
|
||||
sortedSubfolders.sort();
|
||||
|
||||
QVarLengthArray<int, 10> undecidedIndexes;
|
||||
|
||||
QVector<SubFolderInfo> newSubs;
|
||||
newSubs.reserve(sortedSubfolders.size());
|
||||
foreach (const QString& path, sortedSubfolders) {
|
||||
auto relativePath = path.mid(pathToRemove.size());
|
||||
if (parentInfo->_folder->isFileExcludedRelative(relativePath)) {
|
||||
continue;
|
||||
|
||||
@@ -68,13 +68,6 @@ FolderWizardLocalPath::FolderWizardLocalPath()
|
||||
_ui.localFolderLineEdit->setText( QDir::toNativeSeparators( defaultPath ) );
|
||||
_ui.localFolderLineEdit->setToolTip(tr("Enter the path to the local folder."));
|
||||
|
||||
QString newAlias = Theme::instance()->appName();
|
||||
int count = 0;
|
||||
while (FolderMan::instance()->folder(newAlias)) {
|
||||
// There is already a folder configured with this name and folder names need to be unique
|
||||
newAlias = Theme::instance()->appName() + QString::number(++count);
|
||||
}
|
||||
|
||||
_ui.warnLabel->setTextFormat(Qt::RichText);
|
||||
_ui.warnLabel->hide();
|
||||
}
|
||||
@@ -135,14 +128,6 @@ void FolderWizardLocalPath::slotChooseLocalFolder()
|
||||
if (!dir.isEmpty()) {
|
||||
// set the last directory component name as alias
|
||||
_ui.localFolderLineEdit->setText(QDir::toNativeSeparators(dir));
|
||||
|
||||
QDir pickedDir(dir);
|
||||
QString newAlias = pickedDir.dirName();
|
||||
int count = 0;
|
||||
while (FolderMan::instance()->folder(newAlias)) {
|
||||
// There is already a folder configured with this name and folder names need to be unique
|
||||
newAlias = pickedDir.dirName() + QString::number(++count);
|
||||
}
|
||||
}
|
||||
emit completeChanged();
|
||||
}
|
||||
@@ -306,6 +291,7 @@ bool FolderWizardRemotePath::selectByPath(QString path)
|
||||
}
|
||||
|
||||
_ui.folderTreeWidget->setCurrentItem(it);
|
||||
_ui.folderTreeWidget->scrollToItem(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -321,7 +307,9 @@ void FolderWizardRemotePath::slotUpdateDirectories(const QStringList &list)
|
||||
root->setToolTip(0, tr("Choose this to sync the entire account"));
|
||||
root->setData(0, Qt::UserRole, "/");
|
||||
}
|
||||
foreach (QString path, list) {
|
||||
QStringList sortedList = list;
|
||||
sortedList.sort();
|
||||
foreach (QString path, sortedList) {
|
||||
path.remove(webdavFolder);
|
||||
QStringList paths = path.split('/');
|
||||
if (paths.last().isEmpty()) paths.removeLast();
|
||||
@@ -374,8 +362,11 @@ void FolderWizardRemotePath::slotLsColFolderEntry()
|
||||
path = path.mid(1);
|
||||
|
||||
LsColJob *job = runLsColJob(path);
|
||||
// no error handling, no updating, we do this manually
|
||||
// No error handling, no updating, we do this manually
|
||||
// because of extra logic in the typed-path case.
|
||||
disconnect(job, 0, this, 0);
|
||||
connect(job, SIGNAL(finishedWithError(QNetworkReply*)),
|
||||
SLOT(slotTypedPathError(QNetworkReply*)));
|
||||
connect(job, SIGNAL(directoryListingSubfolders(QStringList)),
|
||||
SLOT(slotTypedPathFound(QStringList)));
|
||||
}
|
||||
@@ -386,6 +377,21 @@ void FolderWizardRemotePath::slotTypedPathFound(const QStringList& subpaths)
|
||||
selectByPath(_ui.folderEntry->text());
|
||||
}
|
||||
|
||||
void FolderWizardRemotePath::slotTypedPathError(QNetworkReply* reply)
|
||||
{
|
||||
// Ignore 404s, otherwise users will get annoyed by error popups
|
||||
// when not typing fast enough. It's still clear that a given path
|
||||
// was not found, because the 'Next' button is disabled and no entry
|
||||
// is selected in the tree view.
|
||||
int httpCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (httpCode == 404) {
|
||||
showWarn(""); // hides the warning pane
|
||||
return;
|
||||
}
|
||||
|
||||
slotHandleLsColNetworkError(reply);
|
||||
}
|
||||
|
||||
LsColJob* FolderWizardRemotePath::runLsColJob(const QString& path)
|
||||
{
|
||||
LsColJob *job = new LsColJob(_account, path, this);
|
||||
@@ -482,11 +488,13 @@ FolderWizardSelectiveSync::~FolderWizardSelectiveSync()
|
||||
|
||||
void FolderWizardSelectiveSync::initializePage()
|
||||
{
|
||||
QString alias = wizard()->field(QLatin1String("alias")).toString();
|
||||
QString targetPath = wizard()->property("targetPath").toString();
|
||||
if (targetPath.startsWith('/')) {
|
||||
targetPath = targetPath.mid(1);
|
||||
}
|
||||
QString alias = QFileInfo(targetPath).fileName();
|
||||
if (alias.isEmpty())
|
||||
alias = Theme::instance()->appName();
|
||||
_treeView->setFolderInfo(targetPath, alias);
|
||||
QWizardPage::initializePage();
|
||||
}
|
||||
@@ -499,8 +507,10 @@ bool FolderWizardSelectiveSync::validatePage()
|
||||
|
||||
void FolderWizardSelectiveSync::cleanupPage()
|
||||
{
|
||||
QString alias = wizard()->field(QLatin1String("alias")).toString();
|
||||
QString targetPath = wizard()->property("targetPath").toString();
|
||||
QString alias = QFileInfo(targetPath).fileName();
|
||||
if (alias.isEmpty())
|
||||
alias = Theme::instance()->appName();
|
||||
_treeView->setFolderInfo(targetPath, alias);
|
||||
QWizardPage::cleanupPage();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ protected slots:
|
||||
void slotFolderEntryEdited(const QString& text);
|
||||
void slotLsColFolderEntry();
|
||||
void slotTypedPathFound(const QStringList& subpaths);
|
||||
void slotTypedPathError(QNetworkReply* reply);
|
||||
private:
|
||||
LsColJob* runLsColJob(const QString& path);
|
||||
void recursiveInsert(QTreeWidgetItem *parent, QStringList pathTrail, QString path);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) by Christian Kamm <mail@ckamm.de>
|
||||
*
|
||||
* 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 "lockwatcher.h"
|
||||
#include "filesystem.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
static const int check_frequency = 20 * 1000; // ms
|
||||
|
||||
LockWatcher::LockWatcher(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(&_timer, SIGNAL(timeout()),
|
||||
SLOT(checkFiles()));
|
||||
_timer.start(check_frequency);
|
||||
}
|
||||
|
||||
void LockWatcher::addFile(const QString& path)
|
||||
{
|
||||
_watchedPaths.insert(path);
|
||||
}
|
||||
|
||||
void LockWatcher::checkFiles()
|
||||
{
|
||||
QSet<QString> unlocked;
|
||||
|
||||
foreach (const QString& path, _watchedPaths) {
|
||||
if (!FileSystem::isFileLocked(path)) {
|
||||
emit fileUnlocked(path);
|
||||
unlocked.insert(path);
|
||||
}
|
||||
}
|
||||
|
||||
// Doing it this way instead of with a QMutableSetIterator
|
||||
// ensures that calling back into addFile from connected
|
||||
// slots isn't a problem.
|
||||
_watchedPaths.subtract(unlocked);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (C) by Christian Kamm <mail@ckamm.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* @brief Monitors files that are locked, signaling when they become unlocked
|
||||
*
|
||||
* Only relevant on Windows. Some high-profile applications like Microsoft
|
||||
* Word lock the document that is currently being edited. The synchronization
|
||||
* client will be unable to update them while they are locked.
|
||||
*
|
||||
* In this situation we do want to start a sync run as soon as the file
|
||||
* becomes available again. To do that, we need to regularly check whether
|
||||
* the file is still being locked.
|
||||
*
|
||||
* @ingroup gui
|
||||
*/
|
||||
|
||||
class LockWatcher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LockWatcher(QObject* parent = 0);
|
||||
|
||||
/** Start watching a file.
|
||||
*
|
||||
* If the file is not locked later on, the fileUnlocked signal will be
|
||||
* emitted once.
|
||||
*/
|
||||
void addFile(const QString& path);
|
||||
|
||||
signals:
|
||||
/** Emitted when one of the watched files is no longer
|
||||
* being locked. */
|
||||
void fileUnlocked(const QString& path);
|
||||
|
||||
private slots:
|
||||
void checkFiles();
|
||||
|
||||
private:
|
||||
QSet<QString> _watchedPaths;
|
||||
QTimer _timer;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -127,8 +127,17 @@ int main(int argc, char **argv)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
}
|
||||
#if QT_VERSION > QT_VERSION_CHECK(5, 0, 0)
|
||||
if (qgetenv("QT_QPA_PLATFORMTHEME") != "appmenu-qt5")
|
||||
// We can't call isSystemTrayAvailable with appmenu-qt5 begause it hides the systemtray
|
||||
// (issue #4693)
|
||||
#endif
|
||||
{
|
||||
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||
// If the systemtray is not there, we will wait one second for it to maybe start
|
||||
// (eg boot time) then we show the settings dialog if there is still no systemtray.
|
||||
// On XFCE however, we show a message box with explainaition how to install a systemtray.
|
||||
Utility::sleep(1);
|
||||
auto desktopSession = qgetenv("XDG_CURRENT_DESKTOP").toLower();
|
||||
if (desktopSession.isEmpty()) {
|
||||
@@ -152,6 +161,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
||||
@@ -314,7 +314,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
||||
foreach(Folder* folder, map.values()) {
|
||||
//qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
|
||||
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncPaused());
|
||||
allStatusStrings += tr("Folder %1: %2").arg(folder->aliasGui(), folderMessage);
|
||||
allStatusStrings += tr("Folder %1: %2").arg(folder->shortGuiLocalPath(), folderMessage);
|
||||
}
|
||||
trayMessage = allStatusStrings.join(QLatin1String("\n"));
|
||||
#endif
|
||||
@@ -367,7 +367,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
||||
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
|
||||
}
|
||||
|
||||
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->shortGuiPath()), this );
|
||||
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->shortGuiLocalPath()), this );
|
||||
connect(action, SIGNAL(triggered()),_folderOpenActionMapper, SLOT(map()));
|
||||
_folderOpenActionMapper->setMapping( action, folder->alias() );
|
||||
menu->addAction(action);
|
||||
|
||||
@@ -459,13 +459,6 @@ void OwncloudSetupWizard::slotAssistantFinished( int result )
|
||||
if (!startFromScratch || ensureStartFromScratch(localFolder)) {
|
||||
qDebug() << "Adding folder definition for" << localFolder << _remoteFolder;
|
||||
FolderDefinition folderDefinition;
|
||||
auto alias = Theme::instance()->appName();
|
||||
int count = 0;
|
||||
folderDefinition.alias = alias;
|
||||
while (folderMan->folder(folderDefinition.alias)) {
|
||||
// There is already a folder configured with this name and folder names need to be unique
|
||||
folderDefinition.alias = alias + QString::number(++count);
|
||||
}
|
||||
folderDefinition.localPath = localFolder;
|
||||
folderDefinition.targetPath = _remoteFolder;
|
||||
folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
|
||||
|
||||
@@ -57,7 +57,16 @@ ProtocolWidget::ProtocolWidget(QWidget *parent) :
|
||||
header << tr("Action");
|
||||
header << tr("Size");
|
||||
|
||||
int timestampColumnExtra = 0;
|
||||
#ifdef Q_OS_WIN
|
||||
timestampColumnExtra = 20; // font metrics are broken on Windows, see #4721
|
||||
#endif
|
||||
|
||||
_ui->_treeWidget->setHeaderLabels( header );
|
||||
int timestampColumnWidth =
|
||||
_ui->_treeWidget->fontMetrics().width(timeString(QDateTime::currentDateTime()))
|
||||
+ timestampColumnExtra;
|
||||
_ui->_treeWidget->setColumnWidth(0, timestampColumnWidth);
|
||||
_ui->_treeWidget->setColumnWidth(1, 180);
|
||||
_ui->_treeWidget->setColumnCount(5);
|
||||
_ui->_treeWidget->setRootIsDecorated(false);
|
||||
@@ -79,6 +88,11 @@ ProtocolWidget::ProtocolWidget(QWidget *parent) :
|
||||
_issueItemView = new QTreeWidget(this);
|
||||
header.removeLast();
|
||||
_issueItemView->setHeaderLabels( header );
|
||||
timestampColumnWidth =
|
||||
ActivityItemDelegate::rowHeight() // icon
|
||||
+ _issueItemView->fontMetrics().width(timeString(QDateTime::currentDateTime()))
|
||||
+ timestampColumnExtra;
|
||||
_issueItemView->setColumnWidth(0, timestampColumnWidth);
|
||||
_issueItemView->setColumnWidth(1, 180);
|
||||
_issueItemView->setColumnCount(4);
|
||||
_issueItemView->setRootIsDecorated(false);
|
||||
@@ -167,7 +181,7 @@ QTreeWidgetItem* ProtocolWidget::createCompletedTreewidgetItem(const QString& fo
|
||||
|
||||
columns << timeStr;
|
||||
columns << Utility::fileNameForGuiUse(item._originalFile);
|
||||
columns << f->shortGuiPath();
|
||||
columns << f->shortGuiLocalPath();
|
||||
|
||||
// If the error string is set, it's prefered because it is a useful user message.
|
||||
QString message = item._errorString;
|
||||
|
||||
@@ -222,6 +222,7 @@ void SelectiveSyncTreeView::slotUpdateDirectories(QStringList list)
|
||||
}
|
||||
}
|
||||
|
||||
list.sort();
|
||||
foreach (QString path, list) {
|
||||
auto size = job ? job->_sizes.value(path) : 0;
|
||||
path.remove(pathToRemove);
|
||||
|
||||
@@ -98,17 +98,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
return;
|
||||
}
|
||||
|
||||
// error label, red box and stuff
|
||||
_ui->errorLabel->setLineWidth(1);
|
||||
_ui->errorLabel->setFrameStyle(QFrame::Plain);
|
||||
|
||||
QPalette errPalette = _ui->errorLabel->palette();
|
||||
errPalette.setColor(QPalette::Active, QPalette::Base, QColor(0xaa, 0x4d, 0x4d));
|
||||
errPalette.setColor(QPalette::Active, QPalette::WindowText, QColor(0xaa, 0xaa, 0xaa));
|
||||
|
||||
_ui->errorLabel->setPalette(errPalette);
|
||||
_ui->errorLabel->setFrameShape(QFrame::Box);
|
||||
_ui->errorLabel->setContentsMargins(QMargins(12,12,12,12));
|
||||
_ui->errorLabel->hide();
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,43 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>123</red>
|
||||
<green>121</green>
|
||||
<blue>134</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
@@ -53,7 +90,7 @@
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="0">
|
||||
<item row="3" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
@@ -80,7 +117,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@@ -101,7 +138,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_password">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_password">
|
||||
@@ -167,7 +204,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="widget_editing" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_editing">
|
||||
<property name="leftMargin">
|
||||
@@ -222,10 +259,18 @@
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>errorLabel</zorder>
|
||||
<zorder>widget_shareLink</zorder>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<tabstops>
|
||||
<tabstop>checkBox_shareLink</tabstop>
|
||||
<tabstop>pushButton_copy</tabstop>
|
||||
<tabstop>checkBox_editing</tabstop>
|
||||
<tabstop>checkBox_password</tabstop>
|
||||
<tabstop>lineEdit_password</tabstop>
|
||||
<tabstop>pushButton_setPassword</tabstop>
|
||||
<tabstop>checkBox_expire</tabstop>
|
||||
<tabstop>calendar</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -99,6 +99,9 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
|
||||
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
||||
_ui->errorLabel->hide();
|
||||
|
||||
// Setup the sharee search progress indicator
|
||||
_ui->shareeHorizontalLayout->addWidget(&_pi_sharee);
|
||||
}
|
||||
|
||||
ShareUserGroupWidget::~ShareUserGroupWidget()
|
||||
@@ -147,6 +150,7 @@ void ShareUserGroupWidget::slotLineEditReturn()
|
||||
void ShareUserGroupWidget::searchForSharees()
|
||||
{
|
||||
_completionTimer.stop();
|
||||
_pi_sharee.startAnimation();
|
||||
ShareeModel::ShareeSet blacklist;
|
||||
|
||||
// Add the current user to _sharees since we can't share with ourself
|
||||
@@ -158,7 +162,6 @@ void ShareUserGroupWidget::searchForSharees()
|
||||
}
|
||||
_ui->errorLabel->hide();
|
||||
_completerModel->fetch(_ui->shareeLineEdit->text(), blacklist);
|
||||
|
||||
}
|
||||
|
||||
void ShareUserGroupWidget::getShares()
|
||||
@@ -218,6 +221,7 @@ void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
|
||||
|
||||
void ShareUserGroupWidget::slotShareesReady()
|
||||
{
|
||||
_pi_sharee.stopAnimation();
|
||||
if (_completerModel->rowCount() == 0) {
|
||||
displayError(0, tr("No results for '%1'").arg(_completerModel->currentSearch()));
|
||||
return;
|
||||
@@ -274,6 +278,7 @@ void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex & index)
|
||||
|
||||
void ShareUserGroupWidget::displayError(int code, const QString& message)
|
||||
{
|
||||
_pi_sharee.stopAnimation();
|
||||
qDebug() << "Error from server" << code << message;
|
||||
_ui->errorLabel->setText(message);
|
||||
_ui->errorLabel->show();
|
||||
|
||||
@@ -129,6 +129,8 @@ private:
|
||||
bool _isFile;
|
||||
bool _disableCompleterActivated; // in order to avoid that we share the contents twice
|
||||
ShareManager *_manager;
|
||||
|
||||
QProgressIndicator _pi_sharee;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>457</width>
|
||||
<height>188</height>
|
||||
<height>164</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -15,14 +15,55 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="shareeLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Share with users or groups ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="shareeHorizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="shareeLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Share with users or groups ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="errorLabel">
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="WindowText">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>123</red>
|
||||
<green>121</green>
|
||||
<blue>134</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Placeholder for Error text</string>
|
||||
</property>
|
||||
@@ -50,8 +91,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>441</width>
|
||||
<height>98</height>
|
||||
<width>437</width>
|
||||
<height>94</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3"/>
|
||||
|
||||
@@ -56,6 +56,13 @@
|
||||
// The second number should be changed when there are new features.
|
||||
#define MIRALL_SOCKET_API_VERSION "1.0"
|
||||
|
||||
static inline QString removeTrailingSlash(QString path)
|
||||
{
|
||||
Q_ASSERT(path.endsWith(QLatin1Char('/')));
|
||||
path.truncate(path.length()-1);
|
||||
return path;
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
#define DEBUG qDebug() << "SocketApi: "
|
||||
@@ -67,7 +74,8 @@ SocketApi::SocketApi(QObject* parent)
|
||||
|
||||
if (Utility::isWindows()) {
|
||||
socketPath = QLatin1String("\\\\.\\pipe\\")
|
||||
+ QLatin1String("ownCloud");
|
||||
+ QLatin1String("ownCloud") + '\\'
|
||||
+ QString::fromLocal8Bit(qgetenv("USERNAME"));
|
||||
// TODO: once the windows extension supports multiple
|
||||
// client connections, switch back to the theme name
|
||||
// See issue #2388
|
||||
@@ -140,8 +148,10 @@ void SocketApi::slotNewConnection()
|
||||
_listeners.append(socket);
|
||||
|
||||
foreach( Folder *f, FolderMan::instance()->map() ) {
|
||||
QString message = buildRegisterPathMessage(f->path());
|
||||
sendMessage(socket, message);
|
||||
if (f->canSync()) {
|
||||
QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
|
||||
sendMessage(socket, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,20 +190,31 @@ void SocketApi::slotReadSocket()
|
||||
|
||||
void SocketApi::slotRegisterPath( const QString& alias )
|
||||
{
|
||||
// Make sure not to register twice to each connected client
|
||||
if (_registeredAliases.contains(alias))
|
||||
return;
|
||||
|
||||
Folder *f = FolderMan::instance()->folder(alias);
|
||||
if (f) {
|
||||
QString message = buildRegisterPathMessage(f->path());
|
||||
QString message = buildRegisterPathMessage(removeTrailingSlash(f->path()));
|
||||
foreach(QIODevice *socket, _listeners) {
|
||||
sendMessage(socket, message);
|
||||
}
|
||||
}
|
||||
|
||||
_registeredAliases.insert(alias);
|
||||
}
|
||||
|
||||
void SocketApi::slotUnregisterPath( const QString& alias )
|
||||
{
|
||||
if (!_registeredAliases.contains(alias))
|
||||
return;
|
||||
|
||||
Folder *f = FolderMan::instance()->folder(alias);
|
||||
if (f)
|
||||
broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path(), QString::null, true );
|
||||
broadcastMessage(QLatin1String("UNREGISTER_PATH"), removeTrailingSlash(f->path()), QString::null, true );
|
||||
|
||||
_registeredAliases.remove(alias);
|
||||
}
|
||||
|
||||
void SocketApi::slotUpdateFolderView(Folder *f)
|
||||
@@ -211,10 +232,11 @@ void SocketApi::slotUpdateFolderView(Folder *f)
|
||||
f->syncResult().status() == SyncResult::Error ||
|
||||
f->syncResult().status() == SyncResult::SetupError ) {
|
||||
|
||||
broadcastMessage(QLatin1String("STATUS"), f->path() ,
|
||||
QString rootPath = removeTrailingSlash(f->path());
|
||||
broadcastMessage(QLatin1String("STATUS"), rootPath,
|
||||
f->syncEngine().syncFileStatusTracker().fileStatus("").toSocketAPIString());
|
||||
|
||||
broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() );
|
||||
broadcastMessage(QLatin1String("UPDATE_VIEW"), rootPath);
|
||||
} else {
|
||||
qDebug() << "Not sending UPDATE_VIEW for" << f->alias() << "because status() is" << f->syncResult().status();
|
||||
}
|
||||
@@ -274,8 +296,6 @@ void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QIODevic
|
||||
|
||||
void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice* socket)
|
||||
{
|
||||
const QString nopString("NOP");
|
||||
|
||||
if( !socket ) {
|
||||
qDebug() << "No valid socket object.";
|
||||
return;
|
||||
@@ -288,18 +308,16 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QIODevice*
|
||||
Folder* syncFolder = FolderMan::instance()->folderForPath( argument );
|
||||
if (!syncFolder) {
|
||||
// this can happen in offline mode e.g.: nothing to worry about
|
||||
statusString = nopString;
|
||||
statusString = QLatin1String("NOP");
|
||||
} else {
|
||||
const QString file = QDir::cleanPath(argument).mid(syncFolder->cleanPath().length()+1);
|
||||
|
||||
// future: Send more specific states for paused, disconnected etc.
|
||||
if( syncFolder->syncPaused() || !syncFolder->accountState()->isConnected() ) {
|
||||
statusString = nopString;
|
||||
} else {
|
||||
SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(file);
|
||||
|
||||
statusString = fileStatus.toSocketAPIString();
|
||||
QString relativePath = QDir::cleanPath(argument).mid(syncFolder->cleanPath().length()+1);
|
||||
if( relativePath.endsWith(QLatin1Char('/')) ) {
|
||||
relativePath.truncate(relativePath.length()-1);
|
||||
qWarning() << "Removed trailing slash for directory: " << relativePath << "Status pushes won't have one.";
|
||||
}
|
||||
SyncFileStatus fileStatus = syncFolder->syncEngine().syncFileStatusTracker().fileStatus(relativePath);
|
||||
|
||||
statusString = fileStatus.toSocketAPIString();
|
||||
}
|
||||
|
||||
const QString message = QLatin1String("STATUS:") % statusString % QLatin1Char(':') % QDir::toNativeSeparators(argument);
|
||||
|
||||
@@ -77,6 +77,7 @@ private:
|
||||
Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString& argument, QIODevice* socket);
|
||||
QString buildRegisterPathMessage(const QString& path);
|
||||
|
||||
QSet<QString> _registeredAliases;
|
||||
QList<QIODevice*> _listeners;
|
||||
SocketApiServer _localServer;
|
||||
};
|
||||
|
||||
@@ -95,4 +95,15 @@ QByteArray Capabilities::preferredUploadChecksumType() const
|
||||
return _capabilities["checksums"].toMap()["preferredUploadType"].toByteArray();
|
||||
}
|
||||
|
||||
QByteArray Capabilities::uploadChecksumType() const
|
||||
{
|
||||
QByteArray preferred = preferredUploadChecksumType();
|
||||
if (!preferred.isEmpty())
|
||||
return preferred;
|
||||
QList<QByteArray> supported = supportedChecksumTypes();
|
||||
if (!supported.isEmpty())
|
||||
return supported.first();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,13 @@ public:
|
||||
*/
|
||||
QByteArray preferredUploadChecksumType() const;
|
||||
|
||||
/**
|
||||
* Helper that returns the preferredUploadChecksumType() if set, or one
|
||||
* of the supportedChecksumTypes() if it isn't. May return an empty
|
||||
* QByteArray if no checksum types are supported.
|
||||
*/
|
||||
QByteArray uploadChecksumType() const;
|
||||
|
||||
private:
|
||||
QVariantMap _capabilities;
|
||||
};
|
||||
|
||||
@@ -219,6 +219,7 @@ void ConnectionValidator::slotAuthSuccess()
|
||||
void ConnectionValidator::checkServerCapabilities()
|
||||
{
|
||||
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/capabilities"), this);
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), this, SLOT(slotCapabilitiesRecieved(QVariantMap)));
|
||||
job->start();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ extern "C" {
|
||||
#include "csync.h"
|
||||
#include "vio/csync_vio_local.h"
|
||||
#include "std/c_path.h"
|
||||
#include "std/c_string.h"
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
@@ -589,4 +590,38 @@ bool FileSystem::remove(const QString &fileName, QString *errorString)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::isFileLocked(const QString& fileName)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
mbchar_t *wuri = c_utf8_path_to_locale(fileName.toUtf8());
|
||||
|
||||
// Check if file exists
|
||||
DWORD attr = GetFileAttributesW(wuri);
|
||||
if (attr != INVALID_FILE_ATTRIBUTES) {
|
||||
// Try to open the file with as much access as possible..
|
||||
HANDLE win_h = CreateFileW(
|
||||
wuri,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
||||
NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL);
|
||||
|
||||
c_free_locale_string(wuri);
|
||||
if (win_h == INVALID_HANDLE_VALUE) {
|
||||
/* could not be opened, so locked? */
|
||||
/* 32 == ERROR_SHARING_VIOLATION */
|
||||
return true;
|
||||
} else {
|
||||
CloseHandle(win_h);
|
||||
}
|
||||
} else {
|
||||
c_free_locale_string(wuri);
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(fileName);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -182,6 +182,11 @@ QByteArray OWNCLOUDSYNC_EXPORT calcAdler32( const QString& fileName );
|
||||
*/
|
||||
QString OWNCLOUDSYNC_EXPORT makeConflictFileName(const QString &fn, const QDateTime &dt);
|
||||
|
||||
/**
|
||||
* Returns true when a file is locked. (Windows only)
|
||||
*/
|
||||
bool OWNCLOUDSYNC_EXPORT isFileLocked(const QString& fileName);
|
||||
|
||||
}
|
||||
|
||||
/** @} */
|
||||
|
||||
@@ -369,6 +369,9 @@ signals:
|
||||
void progress(const SyncFileItem&, quint64 bytes);
|
||||
void finished();
|
||||
|
||||
/** Emitted when propagation has problems with a locked file. */
|
||||
void seenLockedFile(const QString &fileName);
|
||||
|
||||
private:
|
||||
|
||||
AccountPtr _account;
|
||||
@@ -376,6 +379,11 @@ private:
|
||||
/** Stores the time since a job touched a file. */
|
||||
QHash<QString, QElapsedTimer> _touchedFiles;
|
||||
mutable QMutex _touchedFilesMutex;
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
// access to signals which are protected in Qt4
|
||||
friend class PropagateDownloadFileQNAM;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -546,6 +546,14 @@ void PropagateDownloadFileQNAM::slotGetFinished()
|
||||
return;
|
||||
}
|
||||
|
||||
if (_tmpFile.size() == 0 && _item->_size > 0) {
|
||||
FileSystem::remove(_tmpFile.fileName());
|
||||
done(SyncFileItem::NormalError,
|
||||
tr("The downloaded file is empty despite the server announced it should have been %1.")
|
||||
.arg(Utility::octetsToString(_item->_size)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Do checksum validation for the download. If there is no checksum header, the validator
|
||||
// will also emit the validated() signal to continue the flow in slot transmissionChecksumValidated()
|
||||
// as this is (still) also correct.
|
||||
@@ -696,7 +704,14 @@ void PropagateDownloadFileQNAM::downloadFinished()
|
||||
QString renameError;
|
||||
QString conflictFileName = FileSystem::makeConflictFileName(fn, Utility::qDateTimeFromTime_t(_item->_modtime));
|
||||
if (!FileSystem::rename(fn, conflictFileName, &renameError)) {
|
||||
//If the rename fails, don't replace it.
|
||||
// If the rename fails, don't replace it.
|
||||
|
||||
// If the file is locked, we want to retry this sync when it
|
||||
// becomes available again.
|
||||
if (FileSystem::isFileLocked(fn)) {
|
||||
emit _propagator->seenLockedFile(fn);
|
||||
}
|
||||
|
||||
done(SyncFileItem::SoftError, renameError);
|
||||
return;
|
||||
}
|
||||
@@ -756,7 +771,14 @@ void PropagateDownloadFileQNAM::downloadFinished()
|
||||
_propagator->_journal->commit("download finished");
|
||||
}
|
||||
|
||||
_propagator->_anotherSyncNeeded = true;
|
||||
// If the file is locked, we want to retry this sync when it
|
||||
// becomes available again, otherwise try again directly
|
||||
if (FileSystem::isFileLocked(fn)) {
|
||||
emit _propagator->seenLockedFile(fn);
|
||||
} else {
|
||||
_propagator->_anotherSyncNeeded = true;
|
||||
}
|
||||
|
||||
done(SyncFileItem::SoftError, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ void PropagateUploadFileQNAM::slotComputeTransmissionChecksum(const QByteArray&
|
||||
// Compute the transmission checksum.
|
||||
auto computeChecksum = new ComputeChecksum(this);
|
||||
if (uploadChecksumEnabled()) {
|
||||
computeChecksum->setChecksumType(_propagator->account()->capabilities().preferredUploadChecksumType());
|
||||
computeChecksum->setChecksumType(_propagator->account()->capabilities().uploadChecksumType());
|
||||
} else {
|
||||
computeChecksum->setChecksumType(QByteArray());
|
||||
}
|
||||
@@ -398,7 +398,7 @@ qint64 UploadDevice::readData(char* data, qint64 maxlen) {
|
||||
if (isBandwidthLimited()) {
|
||||
maxlen = qMin(maxlen, _bandwidthQuota);
|
||||
if (maxlen <= 0) { // no quota
|
||||
qDebug() << "no quota";
|
||||
//qDebug() << "no quota";
|
||||
return 0;
|
||||
}
|
||||
_bandwidthQuota -= maxlen;
|
||||
@@ -544,8 +544,16 @@ void PropagateUploadFileQNAM::startNextChunk()
|
||||
_transmissionChecksumType, _transmissionChecksum);
|
||||
}
|
||||
|
||||
if (! device->prepareAndOpen(_propagator->getFilePath(_item->_file), chunkStart, currentChunkSize)) {
|
||||
const QString fileName = _propagator->getFilePath(_item->_file);
|
||||
if (! device->prepareAndOpen(fileName, chunkStart, currentChunkSize)) {
|
||||
qDebug() << "ERR: Could not prepare upload device: " << device->errorString();
|
||||
|
||||
// If the file is currently locked, we want to retry the sync
|
||||
// when it becomes available again.
|
||||
if (FileSystem::isFileLocked(fileName)) {
|
||||
emit _propagator->seenLockedFile(fileName);
|
||||
}
|
||||
|
||||
// Soft error because this is likely caused by the user modifying his files while syncing
|
||||
abortWithError( SyncFileItem::SoftError, device->errorString() );
|
||||
delete device;
|
||||
|
||||
@@ -933,6 +933,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
|
||||
connect(_propagator.data(), SIGNAL(progress(const SyncFileItem &,quint64)),
|
||||
this, SLOT(slotProgress(const SyncFileItem &,quint64)));
|
||||
connect(_propagator.data(), SIGNAL(finished()), this, SLOT(slotFinished()), Qt::QueuedConnection);
|
||||
connect(_propagator.data(), SIGNAL(seenLockedFile(QString)), SIGNAL(seenLockedFile(QString)));
|
||||
|
||||
// apply the network limits to the propagator
|
||||
setNetworkLimits(_uploadLimit, _downloadLimit);
|
||||
@@ -1341,7 +1342,7 @@ SyncFileItem* SyncEngine::findSyncItem(const QString &fileName) const
|
||||
{
|
||||
Q_FOREACH(const SyncFileItemPtr &item, _syncedItems) {
|
||||
// Directories will appear in this list as well, and will get their status set once all children have been propagated
|
||||
if ((item->_file == fileName || item->_renameTarget == fileName))
|
||||
if ((item->_file == fileName || (!item->_renameTarget.isEmpty() && item->_renameTarget == fileName)))
|
||||
return item.data();
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -143,6 +143,12 @@ signals:
|
||||
// A new folder was discovered and was not synced because of the confirmation feature
|
||||
void newBigFolder(const QString &folder);
|
||||
|
||||
/** Emitted when propagation has problems with a locked file.
|
||||
*
|
||||
* Forwarded from OwncloudPropagator::seenLockedFile.
|
||||
*/
|
||||
void seenLockedFile(const QString &fileName);
|
||||
|
||||
private slots:
|
||||
void slotRootEtagReceived(const QString &);
|
||||
void slotItemCompleted(const SyncFileItem& item, const PropagatorJob & job);
|
||||
|
||||
@@ -28,14 +28,15 @@ static SyncFileStatus::SyncFileStatusTag lookupProblem(const QString &pathToMatc
|
||||
// qDebug() << Q_FUNC_INFO << pathToMatch << severity << problemPath;
|
||||
if (problemPath == pathToMatch) {
|
||||
return severity;
|
||||
} else if (severity == SyncFileStatus::StatusError && problemPath.startsWith(pathToMatch) && problemPath.at(pathToMatch.size()) == '/') {
|
||||
Q_ASSERT(!pathToMatch.endsWith('/'));
|
||||
} else if (severity == SyncFileStatus::StatusError
|
||||
&& problemPath.startsWith(pathToMatch)
|
||||
&& (pathToMatch.isEmpty() || problemPath.at(pathToMatch.size()) == '/')) {
|
||||
return SyncFileStatus::StatusWarning;
|
||||
} else if (!problemPath.startsWith(pathToMatch)) {
|
||||
// Starting at lower_bound we get the first path that is not smaller,
|
||||
// since: "/a/" < "/a/aa" < "/a/aa/aaa" < "/a/ab/aba"
|
||||
// If problemMap keys are ["/a/aa/aaa", "/a/ab/aba"] and pathToMatch == "/a/aa",
|
||||
// lower_bound(pathToMatch) will point to "/a/aa/aaa", and the moment that
|
||||
// since: "a/" < "a/aa" < "a/aa/aaa" < "a/ab/aba"
|
||||
// If problemMap keys are ["a/aa/aaa", "a/ab/aba"] and pathToMatch == "a/aa",
|
||||
// lower_bound(pathToMatch) will point to "a/aa/aaa", and the moment that
|
||||
// problemPath.startsWith(pathToMatch) == false, we know that we've looked
|
||||
// at everything that interest us.
|
||||
break;
|
||||
@@ -68,95 +69,78 @@ static inline bool showWarningInSocketApi(const SyncFileItem& item)
|
||||
|| status == SyncFileItem::Restoration;
|
||||
}
|
||||
|
||||
static inline bool showSyncInSocketApi( const SyncFileItem& item)
|
||||
{
|
||||
const auto inst = item._instruction;
|
||||
return inst == CSYNC_INSTRUCTION_NEW;
|
||||
}
|
||||
|
||||
SyncFileStatusTracker::SyncFileStatusTracker(SyncEngine *syncEngine)
|
||||
: _syncEngine(syncEngine)
|
||||
{
|
||||
connect(syncEngine, SIGNAL(aboutToPropagate(SyncFileItemVector&)),
|
||||
this, SLOT(slotAboutToPropagate(SyncFileItemVector&)));
|
||||
SLOT(slotAboutToPropagate(SyncFileItemVector&)));
|
||||
connect(syncEngine, SIGNAL(itemCompleted(const SyncFileItem&, const PropagatorJob&)),
|
||||
this, SLOT(slotItemCompleted(const SyncFileItem&)));
|
||||
SLOT(slotItemCompleted(const SyncFileItem&)));
|
||||
connect(syncEngine, SIGNAL(started()), SLOT(slotClearDirtyPaths()));
|
||||
connect(syncEngine, SIGNAL(started()), SLOT(slotSyncEngineRunningChanged()));
|
||||
connect(syncEngine, SIGNAL(finished(bool)), SLOT(slotSyncEngineRunningChanged()));
|
||||
}
|
||||
|
||||
SyncFileStatus SyncFileStatusTracker::rootStatus()
|
||||
SyncFileItem SyncFileStatusTracker::rootSyncFileItem()
|
||||
{
|
||||
/* Possible values for the status:
|
||||
enum SyncFileStatusTag {
|
||||
StatusNone,
|
||||
StatusSync,
|
||||
StatusWarning,
|
||||
StatusUpToDate,
|
||||
StatusError,
|
||||
};
|
||||
*/
|
||||
SyncFileStatus status = SyncFileStatus::StatusUpToDate;
|
||||
|
||||
if( !_syncEngine ) return SyncFileStatus::StatusNone;
|
||||
|
||||
if( _syncEngine->isSyncRunning() ) {
|
||||
status = SyncFileStatus::StatusSync;
|
||||
} else {
|
||||
// sync is not running. Check dirty list and _syncProblems
|
||||
int errs = 0;
|
||||
for (auto it = _syncProblems.begin(); it != _syncProblems.end(); ++it) {
|
||||
if( it->second == SyncFileStatus::StatusError ) {
|
||||
errs ++;
|
||||
break; // stop if an error found at all.
|
||||
}
|
||||
}
|
||||
if( errs ) {
|
||||
status = SyncFileStatus::StatusWarning; // some files underneath had errors
|
||||
}
|
||||
// Only warnings do not change the root emblem away from ok.
|
||||
}
|
||||
return status;
|
||||
|
||||
SyncFileItem fakeRootItem;
|
||||
// It's is not entirely correct to use the sync's status as we'll show the root folder as
|
||||
// syncing even though no child might end up being propagated, but will give us something
|
||||
// better than always UpToDate for now.
|
||||
fakeRootItem._status = _syncEngine->isSyncRunning() ? SyncFileItem::NoStatus : SyncFileItem::Success;
|
||||
fakeRootItem._isDirectory = true;
|
||||
return fakeRootItem;
|
||||
}
|
||||
|
||||
SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& systemFileName)
|
||||
SyncFileStatus SyncFileStatusTracker::fileStatus(const QString& relativePath)
|
||||
{
|
||||
QString fileName = systemFileName.normalized(QString::NormalizationForm_C);
|
||||
if( fileName.endsWith(QLatin1Char('/')) ) {
|
||||
fileName.truncate(fileName.length()-1);
|
||||
qDebug() << "Removed trailing slash: " << fileName;
|
||||
Q_ASSERT(!relativePath.endsWith(QLatin1Char('/')));
|
||||
|
||||
if (relativePath.isEmpty()) {
|
||||
// This is the root sync folder, it doesn't have an entry in the database and won't be walked by csync, so create one manually.
|
||||
return syncFileItemStatus(rootSyncFileItem());
|
||||
}
|
||||
|
||||
if( fileName.isEmpty() ) {
|
||||
// this is the root sync folder.
|
||||
return rootStatus();
|
||||
|
||||
}
|
||||
// The SyncEngine won't notify us at all for CSYNC_FILE_SILENTLY_EXCLUDED
|
||||
// and CSYNC_FILE_EXCLUDE_AND_REMOVE excludes. Even though it's possible
|
||||
// that the status of CSYNC_FILE_EXCLUDE_LIST excludes will change if the user
|
||||
// update the exclude list at runtime and doing it statically here removes
|
||||
// our ability to notify changes through the fileStatusChanged signal,
|
||||
// it's an acceptable compromize to treat all exclude types the same.
|
||||
if( _syncEngine->excludedFiles().isExcluded(_syncEngine->localPath() + fileName,
|
||||
if( _syncEngine->excludedFiles().isExcluded(_syncEngine->localPath() + relativePath,
|
||||
_syncEngine->localPath(),
|
||||
_syncEngine->ignoreHiddenFiles()) ) {
|
||||
return SyncFileStatus(SyncFileStatus::StatusWarning);
|
||||
}
|
||||
|
||||
SyncFileItem* item = _syncEngine->findSyncItem(fileName);
|
||||
if ( _dirtyPaths.contains(relativePath) )
|
||||
return SyncFileStatus::StatusSync;
|
||||
|
||||
SyncFileItem* item = _syncEngine->findSyncItem(relativePath);
|
||||
if (item) {
|
||||
return fileStatus(*item);
|
||||
return syncFileItemStatus(*item);
|
||||
}
|
||||
|
||||
// If we're not currently syncing that file, look it up in the database to know if it's shared
|
||||
SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(fileName);
|
||||
SyncJournalFileRecord rec = _syncEngine->journal()->getFileRecord(relativePath);
|
||||
if (rec.isValid()) {
|
||||
return fileStatus(rec.toSyncFileItem());
|
||||
return syncFileItemStatus(rec.toSyncFileItem());
|
||||
}
|
||||
// Must be a new file, wait for the filesystem watcher to trigger a sync
|
||||
return SyncFileStatus();
|
||||
}
|
||||
|
||||
void SyncFileStatusTracker::slotPathTouched(const QString& fileName)
|
||||
{
|
||||
QString folderPath = _syncEngine->localPath();
|
||||
Q_ASSERT(fileName.startsWith(folderPath));
|
||||
|
||||
QString localPath = fileName.mid(folderPath.size());
|
||||
_dirtyPaths.insert(localPath);
|
||||
|
||||
emit fileStatusChanged(fileName, SyncFileStatus::StatusSync);
|
||||
}
|
||||
|
||||
void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items)
|
||||
{
|
||||
std::map<QString, SyncFileStatus::SyncFileStatusTag> oldProblems;
|
||||
@@ -169,10 +153,8 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items)
|
||||
_syncProblems[item->_file] = SyncFileStatus::StatusError;
|
||||
} else if (showWarningInSocketApi(*item)) {
|
||||
_syncProblems[item->_file] = SyncFileStatus::StatusWarning;
|
||||
} else if( showSyncInSocketApi(*item)) {
|
||||
_syncProblems[item->_file] = SyncFileStatus::StatusSync;
|
||||
}
|
||||
emit fileStatusChanged(getSystemDestination(*item), fileStatus(*item));
|
||||
emit fileStatusChanged(getSystemDestination(item->destination()), syncFileItemStatus(*item));
|
||||
}
|
||||
|
||||
// Make sure to push any status that might have been resolved indirectly since the last sync
|
||||
@@ -184,7 +166,7 @@ void SyncFileStatusTracker::slotAboutToPropagate(SyncFileItemVector& items)
|
||||
SyncFileStatus::SyncFileStatusTag severity = it->second;
|
||||
if (severity == SyncFileStatus::StatusError)
|
||||
invalidateParentPaths(path);
|
||||
emit fileStatusChanged(_syncEngine->localPath() + path, fileStatus(path));
|
||||
emit fileStatusChanged(getSystemDestination(path), fileStatus(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,22 +179,32 @@ void SyncFileStatusTracker::slotItemCompleted(const SyncFileItem &item)
|
||||
invalidateParentPaths(item.destination());
|
||||
} else if (showWarningInSocketApi(item)) {
|
||||
_syncProblems[item._file] = SyncFileStatus::StatusWarning;
|
||||
} else if (showSyncInSocketApi(item)) {
|
||||
// new items that were in state sync can now be erased
|
||||
_syncProblems.erase(item._file);
|
||||
} else {
|
||||
// There is currently no situation where an error status set during discovery/update is fixed by propagation.
|
||||
Q_ASSERT(_syncProblems.find(item._file) == _syncProblems.end());
|
||||
_syncProblems.erase(item._file);
|
||||
}
|
||||
|
||||
emit fileStatusChanged(getSystemDestination(item), fileStatus(item));
|
||||
emit fileStatusChanged(getSystemDestination(item.destination()), syncFileItemStatus(item));
|
||||
}
|
||||
|
||||
SyncFileStatus SyncFileStatusTracker::fileStatus(const SyncFileItem& item)
|
||||
void SyncFileStatusTracker::slotSyncEngineRunningChanged()
|
||||
{
|
||||
emit fileStatusChanged(_syncEngine->localPath(), syncFileItemStatus(rootSyncFileItem()));
|
||||
}
|
||||
|
||||
void SyncFileStatusTracker::slotClearDirtyPaths()
|
||||
{
|
||||
// We just assume that during a sync all dirty statuses will be resolved
|
||||
// one way or the other.
|
||||
_dirtyPaths.clear();
|
||||
}
|
||||
|
||||
SyncFileStatus SyncFileStatusTracker::syncFileItemStatus(const SyncFileItem& item)
|
||||
{
|
||||
// Hack to know if the item was taken from the sync engine (Sync), or from the database (UpToDate)
|
||||
bool waitingForPropagation = item._direction != SyncFileItem::None && item._status == SyncFileItem::NoStatus;
|
||||
|
||||
// Mark any directory in the SyncEngine's items as syncing, this is currently how we mark parent directories
|
||||
// of currently syncing items since the PropagateDirectory job will mark the directorie's SyncFileItem::_status as Success
|
||||
// once all child jobs have been completed.
|
||||
bool waitingForPropagation = (item._isDirectory || item._direction != SyncFileItem::None) && item._status == SyncFileItem::NoStatus;
|
||||
SyncFileStatus status(SyncFileStatus::StatusUpToDate);
|
||||
if (waitingForPropagation) {
|
||||
status.set(SyncFileStatus::StatusSync);
|
||||
@@ -239,20 +231,19 @@ void SyncFileStatusTracker::invalidateParentPaths(const QString& path)
|
||||
QStringList splitPath = path.split('/', QString::SkipEmptyParts);
|
||||
for (int i = 0; i < splitPath.size(); ++i) {
|
||||
QString parentPath = QStringList(splitPath.mid(0, i)).join(QLatin1String("/"));
|
||||
emit fileStatusChanged(_syncEngine->localPath() + parentPath, fileStatus(parentPath));
|
||||
emit fileStatusChanged(getSystemDestination(parentPath), fileStatus(parentPath));
|
||||
}
|
||||
}
|
||||
|
||||
QString SyncFileStatusTracker::getSystemDestination(const SyncFileItem& item)
|
||||
QString SyncFileStatusTracker::getSystemDestination(const QString& relativePath)
|
||||
{
|
||||
QString systemFileName = _syncEngine->localPath() + item.destination();
|
||||
// the trailing slash for directories must be appended as the filenames coming in
|
||||
// from the plugins have that too. Otherwise the matching entry item is not found
|
||||
// in the plugin.
|
||||
if( item._type == SyncFileItem::Type::Directory ) {
|
||||
systemFileName += QLatin1Char('/');
|
||||
QString systemPath = _syncEngine->localPath() + relativePath;
|
||||
// SyncEngine::localPath() has a trailing slash, make sure to remove it if the
|
||||
// destination is empty.
|
||||
if( systemPath.endsWith(QLatin1Char('/')) ) {
|
||||
systemPath.truncate(systemPath.length()-1);
|
||||
}
|
||||
return systemFileName;
|
||||
return systemPath;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "syncfileitem.h"
|
||||
#include "syncfilestatus.h"
|
||||
#include <map>
|
||||
#include <QSet>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -34,7 +35,10 @@ class OWNCLOUDSYNC_EXPORT SyncFileStatusTracker : public QObject
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SyncFileStatusTracker(SyncEngine* syncEngine);
|
||||
SyncFileStatus fileStatus(const QString& systemFileName);
|
||||
SyncFileStatus fileStatus(const QString& relativePath);
|
||||
|
||||
public slots:
|
||||
void slotPathTouched(const QString& fileName);
|
||||
|
||||
signals:
|
||||
void fileStatusChanged(const QString& systemFileName, SyncFileStatus fileStatus);
|
||||
@@ -42,17 +46,20 @@ signals:
|
||||
private slots:
|
||||
void slotAboutToPropagate(SyncFileItemVector& items);
|
||||
void slotItemCompleted(const SyncFileItem& item);
|
||||
void slotSyncEngineRunningChanged();
|
||||
void slotClearDirtyPaths();
|
||||
|
||||
private:
|
||||
SyncFileStatus fileStatus(const SyncFileItem& item);
|
||||
SyncFileStatus rootStatus();
|
||||
SyncFileStatus syncFileItemStatus(const SyncFileItem& item);
|
||||
SyncFileItem rootSyncFileItem();
|
||||
|
||||
void invalidateParentPaths(const QString& path);
|
||||
QString getSystemDestination(const SyncFileItem& syncEnginePath);
|
||||
QString getSystemDestination(const QString& relativePath);
|
||||
|
||||
SyncEngine* _syncEngine;
|
||||
|
||||
std::map<QString, SyncFileStatus::SyncFileStatusTag> _syncProblems;
|
||||
QSet<QString> _dirtyPaths;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -306,17 +306,17 @@ namespace {
|
||||
QString description(quint64 value) const
|
||||
{
|
||||
return QCoreApplication::translate(
|
||||
"Utiliy", name, 0, QCoreApplication::UnicodeUTF8,
|
||||
"Utility", name, 0, QCoreApplication::UnicodeUTF8,
|
||||
value);
|
||||
}
|
||||
};
|
||||
Q_DECL_CONSTEXPR Period periods[] = {
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln year(s)") , 365*24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln month(s)") , 30*24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln day(s)") , 24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln hour(s)") , 3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln minute(s)") , 60*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%Ln second(s)") , 1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n year(s)") , 365*24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n month(s)") , 30*24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n day(s)") , 24*3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n hour(s)") , 3600*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n minute(s)") , 60*1000LL },
|
||||
{ QT_TRANSLATE_NOOP("Utility", "%n second(s)") , 1000LL },
|
||||
{ 0, 0 }
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -48,6 +48,7 @@ list(APPEND FolderMan_SRC ../src/gui/folder.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
|
||||
list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
|
||||
list(APPEND FolderMan_SRC stub.cpp )
|
||||
#include_directories(${QTKEYCHAIN_INCLUDE_DIR})
|
||||
|
||||
@@ -11,7 +11,7 @@ macro(owncloud_add_test test_class additional_cpp)
|
||||
string(TOLOWER "${OWNCLOUD_TEST_CLASS}" OWNCLOUD_TEST_CLASS_LOWERCASE)
|
||||
|
||||
add_executable(${OWNCLOUD_TEST_CLASS}Test test${OWNCLOUD_TEST_CLASS_LOWERCASE}.cpp ${additional_cpp})
|
||||
qt5_use_modules(${OWNCLOUD_TEST_CLASS}Test Test Sql Xml Network Gui Widgets)
|
||||
qt5_use_modules(${OWNCLOUD_TEST_CLASS}Test Test Sql Xml Network)
|
||||
|
||||
target_link_libraries(${OWNCLOUD_TEST_CLASS}Test
|
||||
updater
|
||||
|
||||
@@ -150,5 +150,10 @@ using namespace OCC;
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestChecksumValidator)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
QTEST_MAIN(TestChecksumValidator)
|
||||
#else
|
||||
QTEST_GUILESS_MAIN(TestChecksumValidator)
|
||||
#endif
|
||||
|
||||
#include "testchecksumvalidator.moc"
|
||||
|
||||
@@ -100,5 +100,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestConcatUrl)
|
||||
QTEST_APPLESS_MAIN(TestConcatUrl)
|
||||
#include "testconcaturl.moc"
|
||||
|
||||
@@ -85,5 +85,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestCSyncSqlite)
|
||||
QTEST_APPLESS_MAIN(TestCSyncSqlite)
|
||||
#include "testcsyncsqlite.moc"
|
||||
|
||||
@@ -26,24 +26,25 @@ private slots:
|
||||
bool excludeHidden = true;
|
||||
bool keepHidden = false;
|
||||
|
||||
QVERIFY(!excluded.isExcluded("/a/b", "b", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/b~", "b~", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/.b", ".b", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.b", ".b", excludeHidden));
|
||||
bool x = excluded.isExcluded("/a/b", "/a", keepHidden);
|
||||
QVERIFY(!excluded.isExcluded("/a/b", "/a", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/b~", "/a", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/.b", "/a", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.b", "/a", excludeHidden));
|
||||
|
||||
QString path(BIN_PATH);
|
||||
path.append("/sync-exclude.lst");
|
||||
excluded.addExcludeFilePath(path);
|
||||
excluded.reloadExcludes();
|
||||
|
||||
QVERIFY(!excluded.isExcluded("/a/b", "b", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/b~", "b~", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/.b", ".b", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.Trashes", ".Trashes", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/foo_conflict-bar", "foo_conflict-bar", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.b", ".b", excludeHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/b", "/a", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/b~", "/a", keepHidden));
|
||||
QVERIFY(!excluded.isExcluded("/a/.b", "/a", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.Trashes", "/a", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/foo_conflict-bar", "/a", keepHidden));
|
||||
QVERIFY(excluded.isExcluded("/a/.b", "/a", excludeHidden));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestExcludedFiles)
|
||||
QTEST_APPLESS_MAIN(TestExcludedFiles)
|
||||
#include "testexcludedfiles.moc"
|
||||
|
||||
@@ -87,5 +87,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestFileSystem)
|
||||
QTEST_APPLESS_MAIN(TestFileSystem)
|
||||
#include "testfilesystem.moc"
|
||||
|
||||
@@ -41,5 +41,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestFolder)
|
||||
QTEST_APPLESS_MAIN(TestFolder)
|
||||
#include "testfolder.moc"
|
||||
|
||||
@@ -118,5 +118,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestFolderMan)
|
||||
QTEST_APPLESS_MAIN(TestFolderMan)
|
||||
#include "testfolderman.moc"
|
||||
|
||||
@@ -170,5 +170,10 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestFolderWatcher)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
QTEST_MAIN(TestFolderWatcher)
|
||||
#else
|
||||
QTEST_GUILESS_MAIN(TestFolderWatcher)
|
||||
#endif
|
||||
|
||||
#include "testfolderwatcher.moc"
|
||||
|
||||
@@ -69,5 +69,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestInotifyWatcher)
|
||||
QTEST_APPLESS_MAIN(TestInotifyWatcher)
|
||||
#include "testinotifywatcher.moc"
|
||||
|
||||
@@ -72,5 +72,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestNetrcParser)
|
||||
QTEST_APPLESS_MAIN(TestNetrcParser)
|
||||
#include "testnetrcparser.moc"
|
||||
|
||||
@@ -78,5 +78,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestOwncloudPropagator)
|
||||
QTEST_APPLESS_MAIN(TestOwncloudPropagator)
|
||||
#include "testowncloudpropagator.moc"
|
||||
|
||||
@@ -149,5 +149,5 @@ private:
|
||||
SqlDatabase _db;
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestOwnSql)
|
||||
QTEST_APPLESS_MAIN(TestOwnSql)
|
||||
#include "testownsql.moc"
|
||||
|
||||
@@ -65,5 +65,5 @@ private slots:
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSyncFileItem)
|
||||
QTEST_APPLESS_MAIN(TestSyncFileItem)
|
||||
#include "testsyncfileitem.moc"
|
||||
|
||||
@@ -171,5 +171,5 @@ private:
|
||||
SyncJournalDb _db;
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSyncJournalDB)
|
||||
QTEST_APPLESS_MAIN(TestSyncJournalDB)
|
||||
#include "testsyncjournaldb.moc"
|
||||
|
||||
@@ -30,5 +30,5 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestUpdater)
|
||||
QTEST_APPLESS_MAIN(TestUpdater)
|
||||
#include "testupdater.moc"
|
||||
|
||||
@@ -121,11 +121,13 @@ private slots:
|
||||
// Current requires an X-Server
|
||||
return;
|
||||
}
|
||||
QString ver = versionOfInstalledBinary(BIN_PATH);
|
||||
// pass the binary name owncloud to the next call. This brakes branding,
|
||||
// but branding is not supposed to work with this.
|
||||
QString ver = versionOfInstalledBinary(BIN_PATH+QLatin1String("/owncloud"));
|
||||
qDebug() << "Version of installed ownCloud Binary: " << ver;
|
||||
QVERIFY( !ver.isEmpty());
|
||||
|
||||
QRegExp rx( "ownCloud version \\d+\\.\\d+\\.\\d+.+" );
|
||||
QRegExp rx( "ownCloud version \\d+\\.\\d+\\.\\d+.*" );
|
||||
QVERIFY( rx.exactMatch(ver));
|
||||
} else {
|
||||
QVERIFY( versionOfInstalledBinary().isEmpty());
|
||||
@@ -134,36 +136,29 @@ private slots:
|
||||
|
||||
void testTimeAgo()
|
||||
{
|
||||
// Both times in local time
|
||||
// Both times in same timezone
|
||||
QDateTime d1 = QDateTime::fromString("2015-01-24T09:20:30+01:00", Qt::ISODate);
|
||||
QDateTime d2 = QDateTime::fromString("2015-01-23T09:20:30+01:00", Qt::ISODate);
|
||||
QString s = timeAgoInWords(d2, d1);
|
||||
QCOMPARE(s, QLatin1String("1 day(s) ago"));
|
||||
|
||||
//
|
||||
// Different timezones
|
||||
QDateTime earlyTS = QDateTime::fromString("2015-01-24T09:20:30+01:00", Qt::ISODate);
|
||||
earlyTS.setTimeSpec(Qt::UTC);
|
||||
QDateTime laterTS = earlyTS.toOffsetFromUtc(3600);
|
||||
laterTS.setTimeSpec(Qt::UTC);
|
||||
QDateTime laterTS = QDateTime::fromString("2015-01-24T09:20:30-01:00", Qt::ISODate);
|
||||
s = timeAgoInWords(earlyTS, laterTS);
|
||||
QCOMPARE(s, QLatin1String("1 hour(s) ago"));
|
||||
QCOMPARE(s, QLatin1String("2 hour(s) ago"));
|
||||
|
||||
// 'Now' in whatever timezone
|
||||
earlyTS = QDateTime::currentDateTime();
|
||||
|
||||
laterTS.setTimeSpec(Qt::LocalTime);
|
||||
laterTS.setTimeZone( QTimeZone("Pacific/Easter") );
|
||||
laterTS = earlyTS;
|
||||
|
||||
s = timeAgoInWords(earlyTS, laterTS );
|
||||
QCOMPARE(s, QLatin1String("now"));
|
||||
|
||||
earlyTS = earlyTS.addSecs(-6);
|
||||
s = timeAgoInWords(earlyTS, laterTS );
|
||||
QCOMPARE(s, QLatin1String("Less than a minute ago"));
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestUtility)
|
||||
QTEST_APPLESS_MAIN(TestUtility)
|
||||
#include "testutility.moc"
|
||||
|
||||
@@ -441,5 +441,10 @@ private slots:
|
||||
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestXmlParse)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
QTEST_MAIN(TestXmlParse)
|
||||
#else
|
||||
QTEST_GUILESS_MAIN(TestXmlParse)
|
||||
#endif
|
||||
|
||||
#include "testxmlparse.moc"
|
||||
|
||||