Comparar commits
132 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| c0c9fb365d | |||
| 480861efb6 | |||
| 9a252995c2 | |||
| 9a9446d13d | |||
| 3d71d6c96f | |||
| 41c3c107d4 | |||
| 24cad42a86 | |||
| 1a564b1672 | |||
| c4006795cc | |||
| cf06083d1b | |||
| 89004437c5 | |||
| f0e3eca8a1 | |||
| d15cf0c2ff | |||
| 770ad54229 | |||
| a75209d104 | |||
| ab5e543099 | |||
| 451ebd447b | |||
| 1f8af40361 | |||
| 685f2a259f | |||
| 4a2b5f7cc8 | |||
| 52dc55d044 | |||
| e6ab047751 | |||
| 4f5cf01cbc | |||
| 442cc63d22 | |||
| c24a8ba208 | |||
| db7d70a929 | |||
| d2f1c141d8 | |||
| 01faf102ba | |||
| 76d1296053 | |||
| 559bf75189 | |||
| 339caf9dc8 | |||
| b26c36b4bf | |||
| c79879aa07 | |||
| e85475780f | |||
| b033a8e731 | |||
| 0b5b4a5eea | |||
| af0d0bd2ac | |||
| 48097467f9 | |||
| 499400916d | |||
| 3f0d575df7 | |||
| 4cb9d6763b | |||
| d62c51890e | |||
| 10707c8288 | |||
| b896d8aa15 | |||
| 9e66a6bec6 | |||
| 17566febe9 | |||
| db5a86bc06 | |||
| 2d604cee25 | |||
| e3a846bb1f | |||
| aaa5c1bc5f | |||
| 1ad8e539aa | |||
| 29e2877d35 | |||
| a708f8d8ad | |||
| 8777982629 | |||
| f1435c86ed | |||
| ece164f679 | |||
| 698f47d5ad | |||
| 1aa570326e | |||
| 1cc9070a50 | |||
| 127c107094 | |||
| f1faf8745a | |||
| 60a51f8085 | |||
| df63579071 | |||
| 5216648d0b | |||
| fcdab1e804 | |||
| 9f15ba3972 | |||
| c6a65c692e | |||
| b468767184 | |||
| 76ce9ff8c4 | |||
| 23ed68c8dd | |||
| 2982c79444 | |||
| 7e56408331 | |||
| bcfdcec3ee | |||
| f96d94f143 | |||
| adea301e5b | |||
| f9dc569b0a | |||
| f7932bb0c7 | |||
| f20e052ad7 | |||
| 02daca9cb9 | |||
| b52a3a415c | |||
| 33dcb0c8d9 | |||
| 709da37be2 | |||
| 2458f07ca1 | |||
| 6cf5fc7f7d | |||
| 10db6cee6c | |||
| 3b7927366a | |||
| a25f094c4c | |||
| bdf830f691 | |||
| bd72642a58 | |||
| b3f5885a50 | |||
| aa168ec8b9 | |||
| d89cb3e575 | |||
| 9e1f215f22 | |||
| ffd0b19660 | |||
| 0020211857 | |||
| 1a59b4e208 | |||
| 94e7c762bf | |||
| 421c6a92f3 | |||
| d6aa667971 | |||
| 575fc9acbd | |||
| d581550130 | |||
| 952a134745 | |||
| 887aa952fe | |||
| 5f715a7af7 | |||
| eea982fb14 | |||
| 5a7eebe16e | |||
| 55a96af7cc | |||
| 8abaf92083 | |||
| b0c29d5c66 | |||
| 41a6f6df84 | |||
| 4984da7e0d | |||
| be88e5a2c3 | |||
| e7ad7d405c | |||
| 62d26814b2 | |||
| f2d8143511 | |||
| 3c1a605f62 | |||
| 300c1c2055 | |||
| fb42183bc7 | |||
| 65ec8a9e94 | |||
| 60598c0d34 | |||
| 3d7fc711ca | |||
| 46dbca1bf5 | |||
| f5da95a5b7 | |||
| b37361e21c | |||
| 32a208a176 | |||
| b3c2c594bd | |||
| 6123fab091 | |||
| 572d9bdf1a | |||
| 0bcb13f02e | |||
| 42ef09e3bb | |||
| 1ab44655e0 | |||
| 8f5658bc01 |
@@ -1,10 +1,53 @@
|
||||
ChangeLog
|
||||
=========
|
||||
version 2.1 (release 2015-yy-zz)
|
||||
* We removed the old libneon-based propagator.
|
||||
It was already disabled on OS X and Windows and only used on
|
||||
Linux when building with Qt < 5.4 and having bandwidth limiting enabled.
|
||||
So now you need Qt 5.4 on Linux if you want bandwidth limiting.
|
||||
version 2.1 (release 2015-12-03)
|
||||
* GUI: Added a display of server activities
|
||||
* GUI: Added a separate view for not synced items, ignores, errors
|
||||
* GUI: Improved upload/download progress UI (#3403, #3569)
|
||||
* Allowed sharing with ownCloud internal users and groups from Desktop
|
||||
* Changed files starting in .* to be considered hidden on all platforms (#4023)
|
||||
* Reflect read-only permissions in filesystem (#3244)
|
||||
* Blacklist: Clear on successful chunk upload (#3934)
|
||||
* Improved reconnecting after network change/disconnect (#4167 #3969 ...)
|
||||
* Improved performance in Windows file system discovery
|
||||
* Removed libneon-based propagator. As a consequence, The client can no
|
||||
longer provide bandwith limiting on Linux-distributions where it is
|
||||
using Qt < 5.4
|
||||
* Performance improvements in the logging functions
|
||||
* Ensured that local disk space problems are handled gracefully (#2939)
|
||||
* Improved handling of checksums: transport validation, db (#3735)
|
||||
* For *eml-files don't reupload if size and checksum are unchanged (#3235)
|
||||
* Ensured 403 reply code is handled properly (File Firewall) (#3490)
|
||||
* Reduced number of PROPFIND requests to server(#3964)
|
||||
* GUI: Added Account toolbox widget to keep account actions (#4139)
|
||||
* Tray Menu: Added fixes for Recent Activity menu (#4093, #3969)
|
||||
* FolderMan: Fixed infinite wait on pause (#4093)
|
||||
* Renamed env variables to include unit (#2939)
|
||||
* FolderStatusModel: Attempt to detect removed undecided files (#3612)
|
||||
* SyncEngine: Don't whipe the white list if the sync was aborted (#4018)
|
||||
* Quota: Handle special negative value for the quota (#3940)
|
||||
* State app name in update notification (#4020)
|
||||
* PropagateUpload: Fixed double-emission of finished (#3844)
|
||||
* GUI: Ensured folder names which are excluded from sync can be clicked
|
||||
* Shell Integration: Dolphin support, requires KF 5.16 and KDE Application 15.12
|
||||
* FolderStatusModel: Ensured reset also if a folder was renamed (#4011)
|
||||
* GUI: Fixed accessiblity of remaing items in full settings toolbar (#3795)
|
||||
* Introduced the term "folder sync connection" in more places (#3757)
|
||||
* AccountSettings: Don't disable pause when offline (#4010)
|
||||
* Fixed handling of hidden files (#3980)
|
||||
* Handle download errors while resuming as soft errors (#4000)
|
||||
* SocketAPI: Ensured that the command isn't trimmed (#3297)
|
||||
* Shutdown socket API before removing the db (#3824)
|
||||
* GUI: Made "Keep" default in the delete-all dialog (#3824)
|
||||
* owncloudcmd: Introduced return code 0 for --version and --help
|
||||
* owncloudcmd: Added --max-sync-retries (#4037)
|
||||
* owncloudcmd: Don't do a check that file are older than 2s (#4160)
|
||||
* Fixed getting size for selective sync (#3986)
|
||||
* Re-added close button in the settings window (#3713)
|
||||
* Added abililty to handle storage limitations gracefully (#3736)
|
||||
* Updated 3rdparty dependencies: sqlite version 3.9.1
|
||||
* Organized patches to our base Qt version into admin/qt/patches
|
||||
* Plus: A lot of unmentioned improvements and fixes
|
||||
|
||||
version 2.0.2 (release 2015-10-21)
|
||||
* csync_file_stat_s: Save a bit of memory
|
||||
|
||||
@@ -4,7 +4,7 @@ set( MIRALL_VERSION_PATCH 0 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||
set( MIRALL_VERSION_SUFFIX "beta1") #e.g. beta1, beta2, rc1
|
||||
set( MIRALL_VERSION_SUFFIX "") #e.g. beta1, beta2, rc1
|
||||
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||
|
||||
if( NOT DEFINED MIRALL_VERSION_BUILD )
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
From ea4dcc5931d455e4ee3e958ffa54a9f54ab022c8 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Mon, 5 Jan 2015 10:45:25 +0100
|
||||
Subject: [PATCH 1/3] Fix crash on Mac OS if PAC URL contains non-URL legal
|
||||
chars
|
||||
|
||||
macQueryInternal() was retrieving the PAC URL string as-entered by
|
||||
the user in the 'Proxies' tab of the system network settings dialog
|
||||
and passing it to CFURLCreateWithString().
|
||||
|
||||
CFURLCreateWithString() returns null if the input string contains
|
||||
non-URL legal chars or is empty.
|
||||
|
||||
Change-Id: I9166d0433a62c7b2274b5435a7dea0a16997d10e
|
||||
Patch-By: Robert Knight
|
||||
Task-number: QTBUG-36787
|
||||
Reviewed-by: Peter Hartmann <phartmann@blackberry.com>
|
||||
Reviewed-by: Markus Goetz <markus@woboq.com>
|
||||
---
|
||||
src/network/kernel/qnetworkproxy_mac.cpp | 6 +++++-
|
||||
1 file changed, 5 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
index 7d26246..81bce0c 100644
|
||||
--- a/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
+++ b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
@@ -221,7 +221,11 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
|
||||
int enabled;
|
||||
if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
|
||||
// PAC is enabled
|
||||
- CFStringRef cfPacLocation = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
|
||||
+ // kSCPropNetProxiesProxyAutoConfigURLString returns the URL string
|
||||
+ // as entered in the system proxy configuration dialog
|
||||
+ CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
|
||||
+ QCFType<CFStringRef> cfPacLocation = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, pacLocationSetting, NULL, NULL,
|
||||
+ kCFStringEncodingUTF8);
|
||||
|
||||
if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) {
|
||||
QCFType<CFDataRef> pacData;
|
||||
--
|
||||
1.8.3.4 (Apple Git-47)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
From a83e4d1d9dd90d4563ce60f27dfb7802a780e33e Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Mon, 5 Jan 2015 11:42:52 +0100
|
||||
Subject: [PATCH 2/3] Fix possible crash when passing an invalid PAC URL
|
||||
|
||||
This commit checks whether CFURLCreateWithString() succeeded.
|
||||
|
||||
It does not appear to be possible to enter an empty URL directly in the
|
||||
PAC configuration dialog but I can't rule out the possibility
|
||||
that it could find its way into the settings via some other means.
|
||||
|
||||
Change-Id: I6c2053d385503bf0330f5ae9fb1ec36a473d425d
|
||||
Patch-By: Robert Knight
|
||||
Task-number: QTBUG-36787
|
||||
Reviewed-by: Markus Goetz <markus@woboq.com>
|
||||
Reviewed-by: Peter Hartmann <phartmann@blackberry.com>
|
||||
---
|
||||
src/network/kernel/qnetworkproxy_mac.cpp | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
index 81bce0c..6be032e 100644
|
||||
--- a/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
+++ b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
@@ -230,6 +230,10 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
|
||||
if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) {
|
||||
QCFType<CFDataRef> pacData;
|
||||
QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
|
||||
+ if (!pacUrl) {
|
||||
+ qWarning("Invalid PAC URL \"%s\"", qPrintable(QCFString::toQString(cfPacLocation)));
|
||||
+ return result;
|
||||
+ }
|
||||
SInt32 errorCode;
|
||||
if (!CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, pacUrl, &pacData, NULL, NULL, &errorCode)) {
|
||||
QString pacLocation = QCFString::toQString(cfPacLocation);
|
||||
--
|
||||
1.8.3.4 (Apple Git-47)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
From 83bd9393e5564ea9168fda90c0f44456633a483a Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Mon, 5 Jan 2015 15:22:57 +0100
|
||||
Subject: [PATCH 3/3] Fix crash if PAC script retrieval returns a null CFData
|
||||
instance
|
||||
|
||||
The documentation for CFURLCreateDataAndPropertiesFromResource()
|
||||
does not make this clear but from looking at the CFNetwork implementation
|
||||
and a user stacktrace it appears that this function can return true
|
||||
but not set the data argument under certain circumstances.
|
||||
|
||||
Change-Id: I48034a640d6f47a51cd5883bbafacad4bcbd0415
|
||||
Task-number: QTBUG-36787
|
||||
Patch-By: Robert Knight
|
||||
Reviewed-by: Markus Goetz <markus@woboq.com>
|
||||
Reviewed-by: Peter Hartmann <phartmann@blackberry.com>
|
||||
---
|
||||
src/network/kernel/qnetworkproxy_mac.cpp | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
index 6be032e..a1ac349 100644
|
||||
--- a/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
+++ b/src/network/kernel/qnetworkproxy_mac.cpp
|
||||
@@ -240,7 +240,10 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
|
||||
qWarning("Unable to get the PAC script at \"%s\" (%s)", qPrintable(pacLocation), cfurlErrorDescription(errorCode));
|
||||
return result;
|
||||
}
|
||||
-
|
||||
+ if (!pacData) {
|
||||
+ qWarning("\"%s\" returned an empty PAC script", qPrintable(QCFString::toQString(cfPacLocation)));
|
||||
+ return result;
|
||||
+ }
|
||||
QCFType<CFStringRef> pacScript = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, pacData, kCFStringEncodingISOLatin1);
|
||||
if (!pacScript) {
|
||||
// This should never happen, but the documentation says it may return NULL if there was a problem creating the object.
|
||||
--
|
||||
1.8.3.4 (Apple Git-47)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 22f3d359350fd65e4bbe2e9420fcc4460e8a590a Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= <morten.sorvig@digia.com>
|
||||
Date: Tue, 10 Mar 2015 22:37:39 +0100
|
||||
Subject: [PATCH] Cocoa: Fix systray SVG icons.
|
||||
|
||||
Regression caused by f3699510.
|
||||
|
||||
Task-number: QTBUG-44686
|
||||
Change-Id: I546422a67d4da29fac196025b09bddcb45c1b641
|
||||
Reviewed-by: Timur Pocheptsov <Timur.Pocheptsov@digia.com>
|
||||
---
|
||||
src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
index e449fd3..8a35705 100755
|
||||
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
@@ -234,6 +234,10 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
|
||||
}
|
||||
}
|
||||
|
||||
+ // Handle SVG icons, which do not return anything for availableSizes().
|
||||
+ if (!selectedSize.isValid())
|
||||
+ selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode);
|
||||
+
|
||||
QPixmap pixmap = icon.pixmap(selectedSize, mode);
|
||||
|
||||
// Draw a low-resolution icon if there is not enough pixels for a retina
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
From ee7fea33383726f0bb72e8082a357820e3ee3675 Mon Sep 17 00:00:00 2001
|
||||
From: Jocelyn Turcotte <jturcotte@woboq.com>
|
||||
Date: Tue, 24 Feb 2015 17:02:02 +0100
|
||||
Subject: [PATCH] OSX Fix disapearing tray icon
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
It would happen together with an error:
|
||||
QPainter::begin: Paint device returned engine == 0
|
||||
and would be caused by the size provided to QIcon::pixmap being empty,
|
||||
itself caused by the availableSizes list being empty for the Selected
|
||||
mode.
|
||||
|
||||
This bug was most often hidden by the fact that the Selected icon mode
|
||||
was not triggered properly since we usually only set menuVisible after
|
||||
calling updateIcon, and most of the time when it did, we would overwrite
|
||||
it right after with a Normal mode icon.
|
||||
|
||||
Fix the issue by disabling the broken feature completely since the
|
||||
default Selected icon is grayed out while tray icons are now usually
|
||||
black (or white when selected). To support the dark menu bar mode on
|
||||
10.10 we'll need to use NSImage's setTemplate anyway and that
|
||||
knowing in advance if we can invert the colors ourselves would also
|
||||
better solve the menuVisible usecase.
|
||||
|
||||
Task-number: QTBUG-42910
|
||||
Change-Id: If9ec9659af28ecceb841bfc2f11721e6029fe891
|
||||
Reviewed-by: Morten Johan Sørvig <morten.sorvig@theqtcompany.com>
|
||||
---
|
||||
src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 17 +++--------------
|
||||
1 file changed, 3 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
index 8a35705..d366a3c 100755
|
||||
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
|
||||
@@ -102,7 +102,6 @@ QT_USE_NAMESPACE
|
||||
QCocoaSystemTrayIcon *systray;
|
||||
NSStatusItem *item;
|
||||
QCocoaMenu *menu;
|
||||
- bool menuVisible;
|
||||
QIcon icon;
|
||||
QT_MANGLE_NAMESPACE(QNSImageView) *imageCell;
|
||||
}
|
||||
@@ -202,8 +201,6 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
|
||||
|
||||
m_sys->item->icon = icon;
|
||||
|
||||
- const bool menuVisible = m_sys->item->menu && m_sys->item->menuVisible;
|
||||
-
|
||||
// The reccomended maximum title bar icon height is 18 points
|
||||
// (device independent pixels). The menu height on past and
|
||||
// current OS X versions is 22 points. Provide some future-proofing
|
||||
@@ -218,9 +215,8 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
|
||||
// devicePixelRatio for the "best" screen on the system.
|
||||
qreal devicePixelRatio = qApp->devicePixelRatio();
|
||||
const int maxPixmapHeight = maxImageHeight * devicePixelRatio;
|
||||
- const QIcon::Mode mode = menuVisible ? QIcon::Selected : QIcon::Normal;
|
||||
QSize selectedSize;
|
||||
- Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes(mode))) {
|
||||
+ Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) {
|
||||
// Select a pixmap based on the height. We want the largest pixmap
|
||||
// with a height smaller or equal to maxPixmapHeight. The pixmap
|
||||
// may rectangular; assume it has a reasonable size. If there is
|
||||
@@ -236,9 +232,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
|
||||
|
||||
// Handle SVG icons, which do not return anything for availableSizes().
|
||||
if (!selectedSize.isValid())
|
||||
- selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode);
|
||||
+ selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight));
|
||||
|
||||
- QPixmap pixmap = icon.pixmap(selectedSize, mode);
|
||||
+ QPixmap pixmap = icon.pixmap(selectedSize);
|
||||
|
||||
// Draw a low-resolution icon if there is not enough pixels for a retina
|
||||
// icon. This prevents showing a small icon on retina displays.
|
||||
@@ -385,9 +381,6 @@ QT_END_NAMESPACE
|
||||
Q_UNUSED(notification);
|
||||
down = NO;
|
||||
|
||||
- parent->systray->updateIcon(parent->icon);
|
||||
- parent->menuVisible = false;
|
||||
-
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
@@ -397,8 +390,6 @@ QT_END_NAMESPACE
|
||||
int clickCount = [mouseEvent clickCount];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
- parent->systray->updateIcon(parent->icon);
|
||||
-
|
||||
if (clickCount == 2) {
|
||||
[self menuTrackingDone:nil];
|
||||
[parent doubleClickSelector:self];
|
||||
@@ -454,7 +445,6 @@ QT_END_NAMESPACE
|
||||
if (self) {
|
||||
item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
||||
menu = 0;
|
||||
- menuVisible = false;
|
||||
systray = sys;
|
||||
imageCell = [[QNSImageView alloc] initWithParent:self];
|
||||
[item setView: imageCell];
|
||||
@@ -498,7 +488,6 @@ QT_END_NAMESPACE
|
||||
selector:@selector(menuTrackingDone:)
|
||||
name:NSMenuDidEndTrackingNotification
|
||||
object:m];
|
||||
- menuVisible = true;
|
||||
[item popUpStatusItemMenu: m];
|
||||
}
|
||||
}
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
From f3cd07c11e0b7327ffc629f48a89c8c457cdba75 Mon Sep 17 00:00:00 2001
|
||||
From: Jocelyn Turcotte <jturcotte@woboq.com>
|
||||
Date: Fri, 6 Mar 2015 16:12:37 +0100
|
||||
Subject: [PATCH] Fix -force-debug-info with macx-clang
|
||||
|
||||
---
|
||||
mkspecs/common/clang.conf | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/mkspecs/common/clang.conf b/mkspecs/common/clang.conf
|
||||
index 2c29bb8..110d380 100644
|
||||
--- a/mkspecs/common/clang.conf
|
||||
+++ b/mkspecs/common/clang.conf
|
||||
@@ -20,11 +20,13 @@ QMAKE_CFLAGS_ISYSTEM = -isystem
|
||||
QMAKE_CFLAGS_PRECOMPILE = -x c-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
|
||||
QMAKE_CFLAGS_USE_PRECOMPILE = -Xclang -include-pch -Xclang ${QMAKE_PCH_OUTPUT}
|
||||
QMAKE_CFLAGS_LTCG = -flto
|
||||
+QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_OPTIMIZE -g
|
||||
|
||||
QMAKE_CXXFLAGS_PRECOMPILE = -x c++-header -c ${QMAKE_PCH_INPUT} -o ${QMAKE_PCH_OUTPUT}
|
||||
QMAKE_CXXFLAGS_USE_PRECOMPILE = $$QMAKE_CFLAGS_USE_PRECOMPILE
|
||||
QMAKE_CXXFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
|
||||
QMAKE_CXXFLAGS_CXX11 = -std=c++11
|
||||
+QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
|
||||
|
||||
QMAKE_LFLAGS_CXX11 =
|
||||
QMAKE_LFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG
|
||||
--
|
||||
2.2.0
|
||||
|
||||
@@ -0,0 +1,691 @@
|
||||
From cff39fba10ffc10ee4dcfdc66ff6528eb26462d3 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Goetz <markus@woboq.com>
|
||||
Date: Fri, 10 Apr 2015 14:09:53 +0200
|
||||
Subject: [PATCH] QNAM: Fix upload corruptions when server closes connection
|
||||
|
||||
This patch fixes several upload corruptions if the server closes the connection
|
||||
while/before we send data into it. They happen inside multiple places in the HTTP
|
||||
layer and are explained in the comments.
|
||||
Corruptions are:
|
||||
* The upload byte device has an in-flight signal with pending upload data, if
|
||||
it gets reset (because server closes the connection) then the re-send of the
|
||||
request was sometimes taking this stale in-flight pending upload data.
|
||||
* Because some signals were DirectConnection and some were QueuedConnection, there
|
||||
was a chance that a direct signal overtakes a queued signal. The state machine
|
||||
then sent data down the socket which was buffered there (and sent later) although
|
||||
it did not match the current state of the state machine when it was actually sent.
|
||||
* A socket was seen as being able to have requests sent even though it was not
|
||||
encrypted yet. This relates to the previous corruption where data is stored inside
|
||||
the socket's buffer and then sent later.
|
||||
|
||||
The included auto test produces all fixed corruptions, I detected no regressions
|
||||
via the other tests.
|
||||
This code also adds a bit of sanity checking to protect from possible further
|
||||
problems.
|
||||
|
||||
[ChangeLog][QtNetwork] Fix HTTP(s) upload corruption when server closes connection
|
||||
|
||||
Change-Id: I54c883925ec897050941498f139c4b523030432e
|
||||
Reviewed-by: Peter Hartmann <peter-qt@hartmann.tk>
|
||||
---
|
||||
src/corelib/io/qnoncontiguousbytedevice.cpp | 18 +++
|
||||
src/corelib/io/qnoncontiguousbytedevice_p.h | 4 +
|
||||
.../access/qhttpnetworkconnectionchannel.cpp | 35 ++++-
|
||||
.../access/qhttpnetworkconnectionchannel_p.h | 2 +
|
||||
src/network/access/qhttpprotocolhandler.cpp | 7 +
|
||||
src/network/access/qhttpthreaddelegate_p.h | 36 ++++-
|
||||
src/network/access/qnetworkreplyhttpimpl.cpp | 25 ++-
|
||||
src/network/access/qnetworkreplyhttpimpl_p.h | 7 +-
|
||||
.../access/qnetworkreply/tst_qnetworkreply.cpp | 175 ++++++++++++++++++++-
|
||||
9 files changed, 279 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/src/corelib/io/qnoncontiguousbytedevice.cpp b/src/corelib/io/qnoncontiguousbytedevice.cpp
|
||||
index 11510a8..760ca3d 100644
|
||||
--- a/src/corelib/io/qnoncontiguousbytedevice.cpp
|
||||
+++ b/src/corelib/io/qnoncontiguousbytedevice.cpp
|
||||
@@ -236,6 +236,11 @@ qint64 QNonContiguousByteDeviceByteArrayImpl::size()
|
||||
return byteArray->size();
|
||||
}
|
||||
|
||||
+qint64 QNonContiguousByteDeviceByteArrayImpl::pos()
|
||||
+{
|
||||
+ return currentPosition;
|
||||
+}
|
||||
+
|
||||
QNonContiguousByteDeviceRingBufferImpl::QNonContiguousByteDeviceRingBufferImpl(QSharedPointer<QRingBuffer> rb)
|
||||
: QNonContiguousByteDevice(), currentPosition(0)
|
||||
{
|
||||
@@ -273,6 +278,11 @@ bool QNonContiguousByteDeviceRingBufferImpl::atEnd()
|
||||
return currentPosition >= size();
|
||||
}
|
||||
|
||||
+qint64 QNonContiguousByteDeviceRingBufferImpl::pos()
|
||||
+{
|
||||
+ return currentPosition;
|
||||
+}
|
||||
+
|
||||
bool QNonContiguousByteDeviceRingBufferImpl::reset()
|
||||
{
|
||||
if (resetDisabled)
|
||||
@@ -406,6 +416,14 @@ qint64 QNonContiguousByteDeviceIoDeviceImpl::size()
|
||||
return device->size() - initialPosition;
|
||||
}
|
||||
|
||||
+qint64 QNonContiguousByteDeviceIoDeviceImpl::pos()
|
||||
+{
|
||||
+ if (device->isSequential())
|
||||
+ return -1;
|
||||
+
|
||||
+ return device->pos();
|
||||
+}
|
||||
+
|
||||
QByteDeviceWrappingIoDevice::QByteDeviceWrappingIoDevice(QNonContiguousByteDevice *bd) : QIODevice((QObject*)0)
|
||||
{
|
||||
byteDevice = bd;
|
||||
diff --git a/src/corelib/io/qnoncontiguousbytedevice_p.h b/src/corelib/io/qnoncontiguousbytedevice_p.h
|
||||
index c05ae11..4d7b7b0 100644
|
||||
--- a/src/corelib/io/qnoncontiguousbytedevice_p.h
|
||||
+++ b/src/corelib/io/qnoncontiguousbytedevice_p.h
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
virtual const char* readPointer(qint64 maximumLength, qint64 &len) = 0;
|
||||
virtual bool advanceReadPointer(qint64 amount) = 0;
|
||||
virtual bool atEnd() = 0;
|
||||
+ virtual qint64 pos() { return -1; }
|
||||
virtual bool reset() = 0;
|
||||
void disableReset();
|
||||
bool isResetDisabled() { return resetDisabled; }
|
||||
@@ -106,6 +107,7 @@ public:
|
||||
bool atEnd();
|
||||
bool reset();
|
||||
qint64 size();
|
||||
+ qint64 pos() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
QByteArray* byteArray;
|
||||
qint64 currentPosition;
|
||||
@@ -121,6 +123,7 @@ public:
|
||||
bool atEnd();
|
||||
bool reset();
|
||||
qint64 size();
|
||||
+ qint64 pos() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
QSharedPointer<QRingBuffer> ringBuffer;
|
||||
qint64 currentPosition;
|
||||
@@ -138,6 +141,7 @@ public:
|
||||
bool atEnd();
|
||||
bool reset();
|
||||
qint64 size();
|
||||
+ qint64 pos() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
QIODevice* device;
|
||||
QByteArray* currentReadBuffer;
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
index 9f63280..49c6793 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
@@ -106,15 +106,19 @@ void QHttpNetworkConnectionChannel::init()
|
||||
socket->setProxy(QNetworkProxy::NoProxy);
|
||||
#endif
|
||||
|
||||
+ // We want all signals (except the interactive ones) be connected as QueuedConnection
|
||||
+ // because else we're falling into cases where we recurse back into the socket code
|
||||
+ // and mess up the state. Always going to the event loop (and expecting that when reading/writing)
|
||||
+ // is safer.
|
||||
QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
|
||||
this, SLOT(_q_bytesWritten(qint64)),
|
||||
- Qt::DirectConnection);
|
||||
+ Qt::QueuedConnection);
|
||||
QObject::connect(socket, SIGNAL(connected()),
|
||||
this, SLOT(_q_connected()),
|
||||
- Qt::DirectConnection);
|
||||
+ Qt::QueuedConnection);
|
||||
QObject::connect(socket, SIGNAL(readyRead()),
|
||||
this, SLOT(_q_readyRead()),
|
||||
- Qt::DirectConnection);
|
||||
+ Qt::QueuedConnection);
|
||||
|
||||
// The disconnected() and error() signals may already come
|
||||
// while calling connectToHost().
|
||||
@@ -143,13 +147,13 @@ void QHttpNetworkConnectionChannel::init()
|
||||
// won't be a sslSocket if encrypt is false
|
||||
QObject::connect(sslSocket, SIGNAL(encrypted()),
|
||||
this, SLOT(_q_encrypted()),
|
||||
- Qt::DirectConnection);
|
||||
+ Qt::QueuedConnection);
|
||||
QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
|
||||
this, SLOT(_q_sslErrors(QList<QSslError>)),
|
||||
Qt::DirectConnection);
|
||||
QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
|
||||
this, SLOT(_q_encryptedBytesWritten(qint64)),
|
||||
- Qt::DirectConnection);
|
||||
+ Qt::QueuedConnection);
|
||||
|
||||
if (ignoreAllSslErrors)
|
||||
sslSocket->ignoreSslErrors();
|
||||
@@ -186,8 +190,11 @@ void QHttpNetworkConnectionChannel::close()
|
||||
// pendingEncrypt must only be true in between connected and encrypted states
|
||||
pendingEncrypt = false;
|
||||
|
||||
- if (socket)
|
||||
+ if (socket) {
|
||||
+ // socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
|
||||
+ // there is no socket yet.
|
||||
socket->close();
|
||||
+ }
|
||||
}
|
||||
|
||||
|
||||
@@ -353,6 +360,14 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
|
||||
}
|
||||
return false;
|
||||
}
|
||||
+
|
||||
+ // This code path for ConnectedState
|
||||
+ if (pendingEncrypt) {
|
||||
+ // Let's only be really connected when we have received the encrypted() signal. Else the state machine seems to mess up
|
||||
+ // and corrupt the things sent to the server.
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -659,6 +674,12 @@ bool QHttpNetworkConnectionChannel::isSocketReading() const
|
||||
void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
|
||||
{
|
||||
Q_UNUSED(bytes);
|
||||
+ if (ssl) {
|
||||
+ // In the SSL case we want to send data from encryptedBytesWritten signal since that one
|
||||
+ // is the one going down to the actual network, not only into some SSL buffer.
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
// bytes have been written to the socket. write even more of them :)
|
||||
if (isSocketWriting())
|
||||
sendRequest();
|
||||
@@ -734,7 +755,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
|
||||
|
||||
// ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
|
||||
//channels[i].reconnectAttempts = 2;
|
||||
- if (pendingEncrypt) {
|
||||
+ if (ssl || pendingEncrypt) { // FIXME: Didn't work properly with pendingEncrypt only, we should refactor this into an EncrypingState
|
||||
#ifndef QT_NO_SSL
|
||||
if (connection->sslContext().isNull()) {
|
||||
// this socket is making the 1st handshake for this connection,
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
index 692c0e6..231fe11 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
@@ -83,6 +83,8 @@ typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
|
||||
class QHttpNetworkConnectionChannel : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
+ // TODO: Refactor this to add an EncryptingState (and remove pendingEncrypt).
|
||||
+ // Also add an Unconnected state so IdleState does not have double meaning.
|
||||
enum ChannelState {
|
||||
IdleState = 0, // ready to send request
|
||||
ConnectingState = 1, // connecting to host
|
||||
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
|
||||
index 28e10f7..3357948 100644
|
||||
--- a/src/network/access/qhttpprotocolhandler.cpp
|
||||
+++ b/src/network/access/qhttpprotocolhandler.cpp
|
||||
@@ -368,6 +368,13 @@ bool QHttpProtocolHandler::sendRequest()
|
||||
// nothing to read currently, break the loop
|
||||
break;
|
||||
} else {
|
||||
+ if (m_channel->written != uploadByteDevice->pos()) {
|
||||
+ // Sanity check. This was useful in tracking down an upload corruption.
|
||||
+ qWarning() << "QHttpProtocolHandler: Internal error in sendRequest. Expected to write at position" << m_channel->written << "but read device is at" << uploadByteDevice->pos();
|
||||
+ Q_ASSERT(m_channel->written == uploadByteDevice->pos());
|
||||
+ m_connection->d_func()->emitReplyError(m_socket, m_reply, QNetworkReply::ProtocolFailure);
|
||||
+ return false;
|
||||
+ }
|
||||
qint64 currentWriteSize = m_socket->write(readPointer, currentReadSize);
|
||||
if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
|
||||
// socket broke down
|
||||
diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
|
||||
index 1661082..b553409 100644
|
||||
--- a/src/network/access/qhttpthreaddelegate_p.h
|
||||
+++ b/src/network/access/qhttpthreaddelegate_p.h
|
||||
@@ -187,6 +187,7 @@ protected:
|
||||
QByteArray m_dataArray;
|
||||
bool m_atEnd;
|
||||
qint64 m_size;
|
||||
+ qint64 m_pos; // to match calls of haveDataSlot with the expected position
|
||||
public:
|
||||
QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
|
||||
: QNonContiguousByteDevice(),
|
||||
@@ -194,7 +195,8 @@ public:
|
||||
m_amount(0),
|
||||
m_data(0),
|
||||
m_atEnd(aE),
|
||||
- m_size(s)
|
||||
+ m_size(s),
|
||||
+ m_pos(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -202,6 +204,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
+ qint64 pos() Q_DECL_OVERRIDE
|
||||
+ {
|
||||
+ return m_pos;
|
||||
+ }
|
||||
+
|
||||
const char* readPointer(qint64 maximumLength, qint64 &len)
|
||||
{
|
||||
if (m_amount > 0) {
|
||||
@@ -229,11 +236,10 @@ public:
|
||||
|
||||
m_amount -= a;
|
||||
m_data += a;
|
||||
+ m_pos += a;
|
||||
|
||||
- // To main thread to inform about our state
|
||||
- emit processedData(a);
|
||||
-
|
||||
- // FIXME possible optimization, already ask user thread for some data
|
||||
+ // To main thread to inform about our state. The m_pos will be sent as a sanity check.
|
||||
+ emit processedData(m_pos, a);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -250,10 +256,21 @@ public:
|
||||
{
|
||||
m_amount = 0;
|
||||
m_data = 0;
|
||||
+ m_dataArray.clear();
|
||||
+
|
||||
+ if (wantDataPending) {
|
||||
+ // had requested the user thread to send some data (only 1 in-flight at any moment)
|
||||
+ wantDataPending = false;
|
||||
+ }
|
||||
|
||||
// Communicate as BlockingQueuedConnection
|
||||
bool b = false;
|
||||
emit resetData(&b);
|
||||
+ if (b) {
|
||||
+ // the reset succeeded, we're at pos 0 again
|
||||
+ m_pos = 0;
|
||||
+ // the HTTP code will anyway abort the request if !b.
|
||||
+ }
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -264,8 +281,13 @@ public:
|
||||
|
||||
public slots:
|
||||
// From user thread:
|
||||
- void haveDataSlot(QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
|
||||
+ void haveDataSlot(qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize)
|
||||
{
|
||||
+ if (pos != m_pos) {
|
||||
+ // Sometimes when re-sending a request in the qhttpnetwork* layer there is a pending haveData from the
|
||||
+ // user thread on the way to us. We need to ignore it since it is the data for the wrong(later) chunk.
|
||||
+ return;
|
||||
+ }
|
||||
wantDataPending = false;
|
||||
|
||||
m_dataArray = dataArray;
|
||||
@@ -285,7 +307,7 @@ signals:
|
||||
|
||||
// to main thread:
|
||||
void wantData(qint64);
|
||||
- void processedData(qint64);
|
||||
+ void processedData(qint64 pos, qint64 amount);
|
||||
void resetData(bool *b);
|
||||
};
|
||||
|
||||
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
|
||||
index 4ce7303..974a101 100644
|
||||
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
|
||||
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
|
||||
@@ -424,6 +424,7 @@ QNetworkReplyHttpImplPrivate::QNetworkReplyHttpImplPrivate()
|
||||
, synchronous(false)
|
||||
, state(Idle)
|
||||
, statusCode(0)
|
||||
+ , uploadByteDevicePosition(false)
|
||||
, uploadDeviceChoking(false)
|
||||
, outgoingData(0)
|
||||
, bytesUploaded(-1)
|
||||
@@ -863,9 +864,9 @@ void QNetworkReplyHttpImplPrivate::postRequest()
|
||||
q, SLOT(uploadByteDeviceReadyReadSlot()),
|
||||
Qt::QueuedConnection);
|
||||
|
||||
- // From main thread to user thread:
|
||||
- QObject::connect(q, SIGNAL(haveUploadData(QByteArray,bool,qint64)),
|
||||
- forwardUploadDevice, SLOT(haveDataSlot(QByteArray,bool,qint64)), Qt::QueuedConnection);
|
||||
+ // From user thread to http thread:
|
||||
+ QObject::connect(q, SIGNAL(haveUploadData(qint64,QByteArray,bool,qint64)),
|
||||
+ forwardUploadDevice, SLOT(haveDataSlot(qint64,QByteArray,bool,qint64)), Qt::QueuedConnection);
|
||||
QObject::connect(uploadByteDevice.data(), SIGNAL(readyRead()),
|
||||
forwardUploadDevice, SIGNAL(readyRead()),
|
||||
Qt::QueuedConnection);
|
||||
@@ -873,8 +874,8 @@ void QNetworkReplyHttpImplPrivate::postRequest()
|
||||
// From http thread to user thread:
|
||||
QObject::connect(forwardUploadDevice, SIGNAL(wantData(qint64)),
|
||||
q, SLOT(wantUploadDataSlot(qint64)));
|
||||
- QObject::connect(forwardUploadDevice, SIGNAL(processedData(qint64)),
|
||||
- q, SLOT(sentUploadDataSlot(qint64)));
|
||||
+ QObject::connect(forwardUploadDevice,SIGNAL(processedData(qint64, qint64)),
|
||||
+ q, SLOT(sentUploadDataSlot(qint64,qint64)));
|
||||
QObject::connect(forwardUploadDevice, SIGNAL(resetData(bool*)),
|
||||
q, SLOT(resetUploadDataSlot(bool*)),
|
||||
Qt::BlockingQueuedConnection); // this is the only one with BlockingQueued!
|
||||
@@ -1268,12 +1269,22 @@ void QNetworkReplyHttpImplPrivate::replySslConfigurationChanged(const QSslConfig
|
||||
void QNetworkReplyHttpImplPrivate::resetUploadDataSlot(bool *r)
|
||||
{
|
||||
*r = uploadByteDevice->reset();
|
||||
+ if (*r) {
|
||||
+ // reset our own position which is used for the inter-thread communication
|
||||
+ uploadByteDevicePosition = 0;
|
||||
+ }
|
||||
}
|
||||
|
||||
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
|
||||
-void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 amount)
|
||||
+void QNetworkReplyHttpImplPrivate::sentUploadDataSlot(qint64 pos, qint64 amount)
|
||||
{
|
||||
+ if (uploadByteDevicePosition + amount != pos) {
|
||||
+ // Sanity check, should not happen.
|
||||
+ error(QNetworkReply::UnknownNetworkError, "");
|
||||
+ return;
|
||||
+ }
|
||||
uploadByteDevice->advanceReadPointer(amount);
|
||||
+ uploadByteDevicePosition += amount;
|
||||
}
|
||||
|
||||
// Coming from QNonContiguousByteDeviceThreadForwardImpl in HTTP thread
|
||||
@@ -1298,7 +1309,7 @@ void QNetworkReplyHttpImplPrivate::wantUploadDataSlot(qint64 maxSize)
|
||||
QByteArray dataArray(data, currentUploadDataLength);
|
||||
|
||||
// Communicate back to HTTP thread
|
||||
- emit q->haveUploadData(dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
|
||||
+ emit q->haveUploadData(uploadByteDevicePosition, dataArray, uploadByteDevice->atEnd(), uploadByteDevice->size());
|
||||
}
|
||||
|
||||
void QNetworkReplyHttpImplPrivate::uploadByteDeviceReadyReadSlot()
|
||||
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
|
||||
index 77d9c5a..1940922 100644
|
||||
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
|
||||
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
|
||||
@@ -120,7 +120,7 @@ public:
|
||||
|
||||
Q_PRIVATE_SLOT(d_func(), void resetUploadDataSlot(bool *r))
|
||||
Q_PRIVATE_SLOT(d_func(), void wantUploadDataSlot(qint64))
|
||||
- Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64))
|
||||
+ Q_PRIVATE_SLOT(d_func(), void sentUploadDataSlot(qint64,qint64))
|
||||
Q_PRIVATE_SLOT(d_func(), void uploadByteDeviceReadyReadSlot())
|
||||
Q_PRIVATE_SLOT(d_func(), void emitReplyUploadProgress(qint64, qint64))
|
||||
Q_PRIVATE_SLOT(d_func(), void _q_cacheSaveDeviceAboutToClose())
|
||||
@@ -144,7 +144,7 @@ signals:
|
||||
|
||||
void startHttpRequestSynchronously();
|
||||
|
||||
- void haveUploadData(QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
|
||||
+ void haveUploadData(const qint64 pos, QByteArray dataArray, bool dataAtEnd, qint64 dataSize);
|
||||
};
|
||||
|
||||
class QNetworkReplyHttpImplPrivate: public QNetworkReplyPrivate
|
||||
@@ -195,6 +195,7 @@ public:
|
||||
// upload
|
||||
QNonContiguousByteDevice* createUploadByteDevice();
|
||||
QSharedPointer<QNonContiguousByteDevice> uploadByteDevice;
|
||||
+ qint64 uploadByteDevicePosition;
|
||||
bool uploadDeviceChoking; // if we couldn't readPointer() any data at the moment
|
||||
QIODevice *outgoingData;
|
||||
QSharedPointer<QRingBuffer> outgoingDataBuffer;
|
||||
@@ -283,7 +284,7 @@ public:
|
||||
// From QNonContiguousByteDeviceThreadForwardImpl in HTTP thread:
|
||||
void resetUploadDataSlot(bool *r);
|
||||
void wantUploadDataSlot(qint64);
|
||||
- void sentUploadDataSlot(qint64);
|
||||
+ void sentUploadDataSlot(qint64, qint64);
|
||||
|
||||
// From user's QNonContiguousByteDevice
|
||||
void uploadByteDeviceReadyReadSlot();
|
||||
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
index 3ccedf6..d2edf67 100644
|
||||
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
@@ -457,6 +457,10 @@ private Q_SLOTS:
|
||||
|
||||
void putWithRateLimiting();
|
||||
|
||||
+#ifndef QT_NO_SSL
|
||||
+ void putWithServerClosingConnectionImmediately();
|
||||
+#endif
|
||||
+
|
||||
// NOTE: This test must be last!
|
||||
void parentingRepliesToTheApp();
|
||||
private:
|
||||
@@ -4718,18 +4722,22 @@ void tst_QNetworkReply::ioPostToHttpNoBufferFlag()
|
||||
class SslServer : public QTcpServer {
|
||||
Q_OBJECT
|
||||
public:
|
||||
- SslServer() : socket(0) {};
|
||||
+ SslServer() : socket(0), m_ssl(true) {}
|
||||
void incomingConnection(qintptr socketDescriptor) {
|
||||
QSslSocket *serverSocket = new QSslSocket;
|
||||
serverSocket->setParent(this);
|
||||
|
||||
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
|
||||
+ connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
|
||||
+ if (!m_ssl) {
|
||||
+ emit newPlainConnection(serverSocket);
|
||||
+ return;
|
||||
+ }
|
||||
QString testDataDir = QFileInfo(QFINDTESTDATA("rfc3252.txt")).absolutePath();
|
||||
if (testDataDir.isEmpty())
|
||||
testDataDir = QCoreApplication::applicationDirPath();
|
||||
|
||||
connect(serverSocket, SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
|
||||
- connect(serverSocket, SIGNAL(readyRead()), this, SLOT(readyReadSlot()));
|
||||
serverSocket->setProtocol(QSsl::AnyProtocol);
|
||||
connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), serverSocket, SLOT(ignoreSslErrors()));
|
||||
serverSocket->setLocalCertificate(testDataDir + "/certs/server.pem");
|
||||
@@ -4740,11 +4748,12 @@ public:
|
||||
}
|
||||
}
|
||||
signals:
|
||||
- void newEncryptedConnection();
|
||||
+ void newEncryptedConnection(QSslSocket *s);
|
||||
+ void newPlainConnection(QSslSocket *s);
|
||||
public slots:
|
||||
void encryptedSlot() {
|
||||
socket = (QSslSocket*) sender();
|
||||
- emit newEncryptedConnection();
|
||||
+ emit newEncryptedConnection(socket);
|
||||
}
|
||||
void readyReadSlot() {
|
||||
// for the incoming sockets, not the server socket
|
||||
@@ -4753,6 +4762,7 @@ public slots:
|
||||
|
||||
public:
|
||||
QSslSocket *socket;
|
||||
+ bool m_ssl;
|
||||
};
|
||||
|
||||
// very similar to ioPostToHttpUploadProgress but for SSL
|
||||
@@ -4780,7 +4790,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
|
||||
QNetworkReplyPtr reply(manager.post(request, sourceFile));
|
||||
|
||||
QSignalSpy spy(reply.data(), SIGNAL(uploadProgress(qint64,qint64)));
|
||||
- connect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
+ connect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply.data(), SLOT(ignoreSslErrors()));
|
||||
|
||||
// get the request started and the incoming socket connected
|
||||
@@ -4788,7 +4798,7 @@ void tst_QNetworkReply::ioPostToHttpsUploadProgress()
|
||||
QVERIFY(!QTestEventLoop::instance().timeout());
|
||||
QTcpSocket *incomingSocket = server.socket;
|
||||
QVERIFY(incomingSocket);
|
||||
- disconnect(&server, SIGNAL(newEncryptedConnection()), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
+ disconnect(&server, SIGNAL(newEncryptedConnection(QSslSocket*)), &QTestEventLoop::instance(), SLOT(exitLoop()));
|
||||
|
||||
|
||||
incomingSocket->setReadBufferSize(1*1024);
|
||||
@@ -7905,6 +7915,159 @@ void tst_QNetworkReply::putWithRateLimiting()
|
||||
}
|
||||
|
||||
|
||||
+#ifndef QT_NO_SSL
|
||||
+
|
||||
+class PutWithServerClosingConnectionImmediatelyHandler: public QObject
|
||||
+{
|
||||
+ Q_OBJECT
|
||||
+public:
|
||||
+ bool m_parsedHeaders;
|
||||
+ QByteArray m_receivedData;
|
||||
+ QByteArray m_expectedData;
|
||||
+ QSslSocket *m_socket;
|
||||
+ PutWithServerClosingConnectionImmediatelyHandler(QSslSocket *s, QByteArray expected) :m_parsedHeaders(false), m_expectedData(expected), m_socket(s)
|
||||
+ {
|
||||
+ m_socket->setParent(this);
|
||||
+ connect(m_socket, SIGNAL(readyRead()), SLOT(readyReadSlot()));
|
||||
+ connect(m_socket, SIGNAL(disconnected()), SLOT(disconnectedSlot()));
|
||||
+ }
|
||||
+signals:
|
||||
+ void correctFileUploadReceived();
|
||||
+ void corruptFileUploadReceived();
|
||||
+
|
||||
+public slots:
|
||||
+ void closeDelayed() {
|
||||
+ m_socket->close();
|
||||
+ }
|
||||
+
|
||||
+ void readyReadSlot()
|
||||
+ {
|
||||
+ QByteArray data = m_socket->readAll();
|
||||
+ m_receivedData += data;
|
||||
+ if (!m_parsedHeaders && m_receivedData.contains("\r\n\r\n")) {
|
||||
+ m_parsedHeaders = true;
|
||||
+ QTimer::singleShot(qrand()%10, this, SLOT(closeDelayed())); // simulate random network latency
|
||||
+ // This server simulates a web server connection closing, e.g. because of Apaches MaxKeepAliveRequests or KeepAliveTimeout
|
||||
+ // In this case QNAM needs to re-send the upload data but it had a bug which then corrupts the upload
|
||||
+ // This test catches that.
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+ void disconnectedSlot()
|
||||
+ {
|
||||
+ if (m_parsedHeaders) {
|
||||
+ //qDebug() << m_receivedData.left(m_receivedData.indexOf("\r\n\r\n"));
|
||||
+ m_receivedData = m_receivedData.mid(m_receivedData.indexOf("\r\n\r\n")+4); // check only actual data
|
||||
+ }
|
||||
+ if (m_receivedData.length() > 0 && !m_expectedData.startsWith(m_receivedData)) {
|
||||
+ // We had received some data but it is corrupt!
|
||||
+ qDebug() << "CORRUPT" << m_receivedData.count();
|
||||
+
|
||||
+ // Use this to track down the pattern of the corruption and conclude the source
|
||||
+// QFile a("/tmp/corrupt");
|
||||
+// a.open(QIODevice::WriteOnly);
|
||||
+// a.write(m_receivedData);
|
||||
+// a.close();
|
||||
+
|
||||
+// QFile b("/tmp/correct");
|
||||
+// b.open(QIODevice::WriteOnly);
|
||||
+// b.write(m_expectedData);
|
||||
+// b.close();
|
||||
+ //exit(1);
|
||||
+ emit corruptFileUploadReceived();
|
||||
+ } else {
|
||||
+ emit correctFileUploadReceived();
|
||||
+ }
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+class PutWithServerClosingConnectionImmediatelyServer: public SslServer
|
||||
+{
|
||||
+ Q_OBJECT
|
||||
+public:
|
||||
+ int m_correctUploads;
|
||||
+ int m_corruptUploads;
|
||||
+ int m_repliesFinished;
|
||||
+ int m_expectedReplies;
|
||||
+ QByteArray m_expectedData;
|
||||
+ PutWithServerClosingConnectionImmediatelyServer() : SslServer(), m_correctUploads(0), m_corruptUploads(0), m_repliesFinished(0), m_expectedReplies(0)
|
||||
+ {
|
||||
+ QObject::connect(this, SIGNAL(newEncryptedConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
|
||||
+ QObject::connect(this, SIGNAL(newPlainConnection(QSslSocket*)), this, SLOT(createHandlerForConnection(QSslSocket*)));
|
||||
+ }
|
||||
+
|
||||
+public slots:
|
||||
+ void createHandlerForConnection(QSslSocket* s) {
|
||||
+ PutWithServerClosingConnectionImmediatelyHandler *handler = new PutWithServerClosingConnectionImmediatelyHandler(s, m_expectedData);
|
||||
+ handler->setParent(this);
|
||||
+ QObject::connect(handler, SIGNAL(correctFileUploadReceived()), this, SLOT(increaseCorrect()));
|
||||
+ QObject::connect(handler, SIGNAL(corruptFileUploadReceived()), this, SLOT(increaseCorrupt()));
|
||||
+ }
|
||||
+ void increaseCorrect() {
|
||||
+ m_correctUploads++;
|
||||
+ }
|
||||
+ void increaseCorrupt() {
|
||||
+ m_corruptUploads++;
|
||||
+ }
|
||||
+ void replyFinished() {
|
||||
+ m_repliesFinished++;
|
||||
+ if (m_repliesFinished == m_expectedReplies) {
|
||||
+ QTestEventLoop::instance().exitLoop();
|
||||
+ }
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+
|
||||
+
|
||||
+void tst_QNetworkReply::putWithServerClosingConnectionImmediately()
|
||||
+{
|
||||
+ const int numUploads = 40;
|
||||
+ qint64 wantedSize = 512*1024; // 512 kB
|
||||
+ QByteArray sourceFile;
|
||||
+ for (int i = 0; i < wantedSize; ++i) {
|
||||
+ sourceFile += (char)'a' +(i%26);
|
||||
+ }
|
||||
+ bool withSsl = false;
|
||||
+
|
||||
+ for (int s = 0; s <= 1; s++) {
|
||||
+ withSsl = (s == 1);
|
||||
+ // Test also needs to run several times because of 9c2ecf89
|
||||
+ for (int j = 0; j < 20; j++) {
|
||||
+ // emulate a minimal https server
|
||||
+ PutWithServerClosingConnectionImmediatelyServer server;
|
||||
+ server.m_ssl = withSsl;
|
||||
+ server.m_expectedData = sourceFile;
|
||||
+ server.m_expectedReplies = numUploads;
|
||||
+ server.listen(QHostAddress(QHostAddress::LocalHost), 0);
|
||||
+
|
||||
+ for (int i = 0; i < numUploads; i++) {
|
||||
+ // create the request
|
||||
+ QUrl url = QUrl(QString("http%1://127.0.0.1:%2/file=%3").arg(withSsl ? "s" : "").arg(server.serverPort()).arg(i));
|
||||
+ QNetworkRequest request(url);
|
||||
+ QNetworkReply *reply = manager.put(request, sourceFile);
|
||||
+ connect(reply, SIGNAL(sslErrors(QList<QSslError>)), reply, SLOT(ignoreSslErrors()));
|
||||
+ connect(reply, SIGNAL(finished()), &server, SLOT(replyFinished()));
|
||||
+ reply->setParent(&server);
|
||||
+ }
|
||||
+
|
||||
+ // get the request started and the incoming socket connected
|
||||
+ QTestEventLoop::instance().enterLoop(10);
|
||||
+
|
||||
+ //qDebug() << "correct=" << server.m_correctUploads << "corrupt=" << server.m_corruptUploads << "expected=" <<numUploads;
|
||||
+
|
||||
+ // Sanity check because ecause of 9c2ecf89 most replies will error out but we want to make sure at least some of them worked
|
||||
+ QVERIFY(server.m_correctUploads > 5);
|
||||
+ // Because actually important is that we don't get any corruption:
|
||||
+ QCOMPARE(server.m_corruptUploads, 0);
|
||||
+
|
||||
+ server.close();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+}
|
||||
+
|
||||
+#endif
|
||||
|
||||
// NOTE: This test must be last testcase in tst_qnetworkreply!
|
||||
void tst_QNetworkReply::parentingRepliesToTheApp()
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,434 @@
|
||||
From eae0cb09f1310e755c2aff7c1112f7a6c09d7a53 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Goetz <markus@woboq.com>
|
||||
Date: Fri, 19 Jun 2015 15:35:34 +0200
|
||||
Subject: [PATCH] Network: Fix up previous corruption patch
|
||||
|
||||
This is a fix-up for cff39fba10ffc10ee4dcfdc66ff6528eb26462d3.
|
||||
That patch lead to some internal state issues that lead to the QTBUG-47048
|
||||
or to QNetworkReply objects erroring with "Connection Closed" when
|
||||
the server closed the Keep-Alive connection.
|
||||
|
||||
This patch changes the QNAM socket slot connections to be DirectConnection.
|
||||
We don't close the socket anymore in slots where it is anyway in a closed state
|
||||
afterwards. This prevents event/stack recursions.
|
||||
We also flush QSslSocket/QTcpSocket receive buffers when receiving a disconnect
|
||||
so that the developer always gets the full decrypted data from the buffers.
|
||||
|
||||
[ChangeLog][QtNetwork] Fix HTTP issues with "Unknown Error" and "Connection Closed"
|
||||
[ChangeLog][QtNetwork][Sockets] Read OS/encrypted read buffers when connection
|
||||
closed by server.
|
||||
|
||||
Change-Id: Ib4d6a2d0d988317e3a5356f36e8dbcee4590beed
|
||||
Task-number: QTBUG-47048
|
||||
Reviewed-by: Kai Koehne <kai.koehne@theqtcompany.com>
|
||||
Reviewed-by: Richard J. Moore <rich@kde.org>
|
||||
---
|
||||
src/network/access/qhttpnetworkconnection.cpp | 1 -
|
||||
.../access/qhttpnetworkconnectionchannel.cpp | 108 +++++++++++++--------
|
||||
.../access/qhttpnetworkconnectionchannel_p.h | 1 +
|
||||
src/network/access/qhttpnetworkreply.cpp | 2 +-
|
||||
src/network/access/qhttpprotocolhandler.cpp | 1 -
|
||||
src/network/socket/qabstractsocket.cpp | 7 +-
|
||||
src/network/ssl/qsslsocket.cpp | 8 ++
|
||||
src/network/ssl/qsslsocket_openssl.cpp | 7 ++
|
||||
.../access/qnetworkreply/tst_qnetworkreply.cpp | 9 +-
|
||||
9 files changed, 94 insertions(+), 50 deletions(-)
|
||||
|
||||
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
|
||||
index 365ce55..543c70e 100644
|
||||
--- a/src/network/access/qhttpnetworkconnection.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnection.cpp
|
||||
@@ -917,7 +917,6 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
|
||||
for (int i = 0; i < channelCount; ++i) {
|
||||
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
|
||||
channels[i].resendCurrent = false;
|
||||
- channels[i].state = QHttpNetworkConnectionChannel::IdleState;
|
||||
|
||||
// if this is not possible, error will be emitted and connection terminated
|
||||
if (!channels[i].resetUploadData())
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
index 49c6793..e2f6307 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
@@ -58,6 +58,11 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
|
||||
|
||||
+// Because in-flight when sending a request, the server might close our connection (because the persistent HTTP
|
||||
+// connection times out)
|
||||
+// We use 3 because we can get a _q_error 3 times depending on the timing:
|
||||
+static const int reconnectAttemptsDefault = 3;
|
||||
+
|
||||
QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
|
||||
: socket(0)
|
||||
, ssl(false)
|
||||
@@ -69,7 +74,7 @@ QHttpNetworkConnectionChannel::QHttpNetworkConnectionChannel()
|
||||
, resendCurrent(false)
|
||||
, lastStatus(0)
|
||||
, pendingEncrypt(false)
|
||||
- , reconnectAttempts(2)
|
||||
+ , reconnectAttempts(reconnectAttemptsDefault)
|
||||
, authMethod(QAuthenticatorPrivate::None)
|
||||
, proxyAuthMethod(QAuthenticatorPrivate::None)
|
||||
, authenticationCredentialsSent(false)
|
||||
@@ -106,19 +111,18 @@ void QHttpNetworkConnectionChannel::init()
|
||||
socket->setProxy(QNetworkProxy::NoProxy);
|
||||
#endif
|
||||
|
||||
- // We want all signals (except the interactive ones) be connected as QueuedConnection
|
||||
- // because else we're falling into cases where we recurse back into the socket code
|
||||
- // and mess up the state. Always going to the event loop (and expecting that when reading/writing)
|
||||
- // is safer.
|
||||
+ // After some back and forth in all the last years, this is now a DirectConnection because otherwise
|
||||
+ // the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
|
||||
+ // which behave slightly differently on Windows vs Linux
|
||||
QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
|
||||
this, SLOT(_q_bytesWritten(qint64)),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
QObject::connect(socket, SIGNAL(connected()),
|
||||
this, SLOT(_q_connected()),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
QObject::connect(socket, SIGNAL(readyRead()),
|
||||
this, SLOT(_q_readyRead()),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
|
||||
// The disconnected() and error() signals may already come
|
||||
// while calling connectToHost().
|
||||
@@ -129,10 +133,10 @@ void QHttpNetworkConnectionChannel::init()
|
||||
qRegisterMetaType<QAbstractSocket::SocketError>();
|
||||
QObject::connect(socket, SIGNAL(disconnected()),
|
||||
this, SLOT(_q_disconnected()),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
|
||||
this, SLOT(_q_error(QAbstractSocket::SocketError)),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
|
||||
|
||||
#ifndef QT_NO_NETWORKPROXY
|
||||
@@ -147,13 +151,13 @@ void QHttpNetworkConnectionChannel::init()
|
||||
// won't be a sslSocket if encrypt is false
|
||||
QObject::connect(sslSocket, SIGNAL(encrypted()),
|
||||
this, SLOT(_q_encrypted()),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
|
||||
this, SLOT(_q_sslErrors(QList<QSslError>)),
|
||||
Qt::DirectConnection);
|
||||
QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
|
||||
this, SLOT(_q_encryptedBytesWritten(qint64)),
|
||||
- Qt::QueuedConnection);
|
||||
+ Qt::DirectConnection);
|
||||
|
||||
if (ignoreAllSslErrors)
|
||||
sslSocket->ignoreSslErrors();
|
||||
@@ -397,7 +401,7 @@ void QHttpNetworkConnectionChannel::allDone()
|
||||
|
||||
// reset the reconnection attempts after we receive a complete reply.
|
||||
// in case of failures, each channel will attempt two reconnects before emitting error.
|
||||
- reconnectAttempts = 2;
|
||||
+ reconnectAttempts = reconnectAttemptsDefault;
|
||||
|
||||
// now the channel can be seen as free/idle again, all signal emissions for the reply have been done
|
||||
if (state != QHttpNetworkConnectionChannel::ClosingState)
|
||||
@@ -651,6 +655,15 @@ void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
|
||||
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
+void QHttpNetworkConnectionChannel::resendCurrentRequest()
|
||||
+{
|
||||
+ requeueCurrentlyPipelinedRequests();
|
||||
+ if (reply)
|
||||
+ resendCurrent = true;
|
||||
+ if (qobject_cast<QHttpNetworkConnection*>(connection))
|
||||
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
||||
+}
|
||||
+
|
||||
bool QHttpNetworkConnectionChannel::isSocketBusy() const
|
||||
{
|
||||
return (state & QHttpNetworkConnectionChannel::BusyState);
|
||||
@@ -694,8 +707,8 @@ void QHttpNetworkConnectionChannel::_q_disconnected()
|
||||
return;
|
||||
}
|
||||
|
||||
- // read the available data before closing
|
||||
- if (isSocketWaiting() || isSocketReading()) {
|
||||
+ // read the available data before closing (also done in _q_error for other codepaths)
|
||||
+ if ((isSocketWaiting() || isSocketReading()) && socket->bytesAvailable()) {
|
||||
if (reply) {
|
||||
state = QHttpNetworkConnectionChannel::ReadingState;
|
||||
_q_receiveReply();
|
||||
@@ -707,7 +720,8 @@ void QHttpNetworkConnectionChannel::_q_disconnected()
|
||||
state = QHttpNetworkConnectionChannel::IdleState;
|
||||
|
||||
requeueCurrentlyPipelinedRequests();
|
||||
- close();
|
||||
+
|
||||
+ pendingEncrypt = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -789,11 +803,19 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
errorCode = QNetworkReply::ConnectionRefusedError;
|
||||
break;
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
- // try to reconnect/resend before sending an error.
|
||||
- // while "Reading" the _q_disconnected() will handle this.
|
||||
- if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
|
||||
+ // This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer.
|
||||
+ // Depending on timing it can also come three times in a row (first time when we try to write into a closing QSslSocket).
|
||||
+ // The reconnectAttempts handling catches the cases where we can re-send the request.
|
||||
+ if (!reply && state == QHttpNetworkConnectionChannel::IdleState) {
|
||||
+ // Not actually an error, it is normal for Keep-Alive connections to close after some time if no request
|
||||
+ // is sent on them. No need to error the other replies below. Just bail out here.
|
||||
+ // The _q_disconnected will handle the possibly pipelined replies
|
||||
+ return;
|
||||
+ } else if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
|
||||
+ // Try to reconnect/resend before sending an error.
|
||||
+ // While "Reading" the _q_disconnected() will handle this.
|
||||
if (reconnectAttempts-- > 0) {
|
||||
- closeAndResendCurrentRequest();
|
||||
+ resendCurrentRequest();
|
||||
return;
|
||||
} else {
|
||||
errorCode = QNetworkReply::RemoteHostClosedError;
|
||||
@@ -818,24 +840,15 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
// we can ignore the readbuffersize as the data is already
|
||||
// in memory and we will not receive more data on the socket.
|
||||
reply->setReadBufferSize(0);
|
||||
+ reply->setDownstreamLimited(false);
|
||||
_q_receiveReply();
|
||||
-#ifndef QT_NO_SSL
|
||||
- if (ssl) {
|
||||
- // QT_NO_OPENSSL. The QSslSocket can still have encrypted bytes in the plainsocket.
|
||||
- // So we need to check this if the socket is a QSslSocket. When the socket is flushed
|
||||
- // it will force a decrypt of the encrypted data in the plainsocket.
|
||||
- QSslSocket *sslSocket = static_cast<QSslSocket*>(socket);
|
||||
- qint64 beforeFlush = sslSocket->encryptedBytesAvailable();
|
||||
- while (sslSocket->encryptedBytesAvailable()) {
|
||||
- sslSocket->flush();
|
||||
- _q_receiveReply();
|
||||
- qint64 afterFlush = sslSocket->encryptedBytesAvailable();
|
||||
- if (afterFlush == beforeFlush)
|
||||
- break;
|
||||
- beforeFlush = afterFlush;
|
||||
- }
|
||||
+ if (!reply) {
|
||||
+ // No more reply assigned after the previous call? Then it had been finished successfully.
|
||||
+ requeueCurrentlyPipelinedRequests();
|
||||
+ state = QHttpNetworkConnectionChannel::IdleState;
|
||||
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
|
||||
+ return;
|
||||
}
|
||||
-#endif
|
||||
}
|
||||
|
||||
errorCode = QNetworkReply::RemoteHostClosedError;
|
||||
@@ -846,7 +859,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
case QAbstractSocket::SocketTimeoutError:
|
||||
// try to reconnect/resend before sending an error.
|
||||
if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
|
||||
- closeAndResendCurrentRequest();
|
||||
+ resendCurrentRequest();
|
||||
return;
|
||||
}
|
||||
errorCode = QNetworkReply::TimeoutError;
|
||||
@@ -860,7 +873,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
case QAbstractSocket::ProxyConnectionClosedError:
|
||||
// try to reconnect/resend before sending an error.
|
||||
if (reconnectAttempts-- > 0) {
|
||||
- closeAndResendCurrentRequest();
|
||||
+ resendCurrentRequest();
|
||||
return;
|
||||
}
|
||||
errorCode = QNetworkReply::ProxyConnectionClosedError;
|
||||
@@ -868,7 +881,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
case QAbstractSocket::ProxyConnectionTimeoutError:
|
||||
// try to reconnect/resend before sending an error.
|
||||
if (reconnectAttempts-- > 0) {
|
||||
- closeAndResendCurrentRequest();
|
||||
+ resendCurrentRequest();
|
||||
return;
|
||||
}
|
||||
errorCode = QNetworkReply::ProxyTimeoutError;
|
||||
@@ -916,8 +929,18 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
// send the next request
|
||||
QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
|
||||
|
||||
- if (that) //signal emission triggered event loop
|
||||
- close();
|
||||
+ if (that) {
|
||||
+ //signal emission triggered event loop
|
||||
+ if (!socket)
|
||||
+ state = QHttpNetworkConnectionChannel::IdleState;
|
||||
+ else if (socket->state() == QAbstractSocket::UnconnectedState)
|
||||
+ state = QHttpNetworkConnectionChannel::IdleState;
|
||||
+ else
|
||||
+ state = QHttpNetworkConnectionChannel::ClosingState;
|
||||
+
|
||||
+ // pendingEncrypt must only be true in between connected and encrypted states
|
||||
+ pendingEncrypt = false;
|
||||
+ }
|
||||
}
|
||||
|
||||
#ifndef QT_NO_NETWORKPROXY
|
||||
@@ -941,7 +964,8 @@ void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetwor
|
||||
|
||||
void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
|
||||
{
|
||||
- sendRequest();
|
||||
+ if (reply)
|
||||
+ sendRequest();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_SSL
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
index 231fe11..a834b7d 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
|
||||
@@ -169,6 +169,7 @@ public:
|
||||
|
||||
void handleUnexpectedEOF();
|
||||
void closeAndResendCurrentRequest();
|
||||
+ void resendCurrentRequest();
|
||||
|
||||
bool isSocketBusy() const;
|
||||
bool isSocketWriting() const;
|
||||
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
|
||||
index 55863a3..8b71bd8 100644
|
||||
--- a/src/network/access/qhttpnetworkreply.cpp
|
||||
+++ b/src/network/access/qhttpnetworkreply.cpp
|
||||
@@ -191,7 +191,7 @@ QByteArray QHttpNetworkReply::readAny()
|
||||
return QByteArray();
|
||||
|
||||
// we'll take the last buffer, so schedule another read from http
|
||||
- if (d->downstreamLimited && d->responseData.bufferCount() == 1)
|
||||
+ if (d->downstreamLimited && d->responseData.bufferCount() == 1 && !isFinished())
|
||||
d->connection->d_func()->readMoreLater(this);
|
||||
return d->responseData.read();
|
||||
}
|
||||
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
|
||||
index 3357948..380aaac 100644
|
||||
--- a/src/network/access/qhttpprotocolhandler.cpp
|
||||
+++ b/src/network/access/qhttpprotocolhandler.cpp
|
||||
@@ -250,7 +250,6 @@ bool QHttpProtocolHandler::sendRequest()
|
||||
if (!m_reply) {
|
||||
// heh, how should that happen!
|
||||
qWarning() << "QAbstractProtocolHandler::sendRequest() called without QHttpNetworkReply";
|
||||
- m_channel->state = QHttpNetworkConnectionChannel::IdleState;
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp
|
||||
index 2666771..0e82d4a 100644
|
||||
--- a/src/network/socket/qabstractsocket.cpp
|
||||
+++ b/src/network/socket/qabstractsocket.cpp
|
||||
@@ -768,6 +768,7 @@ bool QAbstractSocketPrivate::canReadNotification()
|
||||
void QAbstractSocketPrivate::canCloseNotification()
|
||||
{
|
||||
Q_Q(QAbstractSocket);
|
||||
+ // Note that this method is only called on Windows. Other platforms close in the canReadNotification()
|
||||
|
||||
#if defined (QABSTRACTSOCKET_DEBUG)
|
||||
qDebug("QAbstractSocketPrivate::canCloseNotification()");
|
||||
@@ -777,7 +778,11 @@ void QAbstractSocketPrivate::canCloseNotification()
|
||||
if (isBuffered) {
|
||||
// Try to read to the buffer, if the read fail we can close the socket.
|
||||
newBytes = buffer.size();
|
||||
- if (!readFromSocket()) {
|
||||
+ qint64 oldReadBufferMaxSize = readBufferMaxSize;
|
||||
+ readBufferMaxSize = 0; // temporarily disable max read buffer, we want to empty the OS buffer
|
||||
+ bool hadReadFromSocket = readFromSocket();
|
||||
+ readBufferMaxSize = oldReadBufferMaxSize;
|
||||
+ if (!hadReadFromSocket) {
|
||||
q->disconnectFromHost();
|
||||
return;
|
||||
}
|
||||
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
|
||||
index c1fab94..2b9e923 100644
|
||||
--- a/src/network/ssl/qsslsocket.cpp
|
||||
+++ b/src/network/ssl/qsslsocket.cpp
|
||||
@@ -2294,6 +2294,14 @@ void QSslSocketPrivate::_q_errorSlot(QAbstractSocket::SocketError error)
|
||||
qCDebug(lcSsl) << "\tstate =" << q->state();
|
||||
qCDebug(lcSsl) << "\terrorString =" << q->errorString();
|
||||
#endif
|
||||
+ // this moves encrypted bytes from plain socket into our buffer
|
||||
+ if (plainSocket->bytesAvailable()) {
|
||||
+ qint64 tmpReadBufferMaxSize = readBufferMaxSize;
|
||||
+ readBufferMaxSize = 0; // reset temporarily so the plain sockets completely drained drained
|
||||
+ transmit();
|
||||
+ readBufferMaxSize = tmpReadBufferMaxSize;
|
||||
+ }
|
||||
+
|
||||
q->setSocketError(plainSocket->error());
|
||||
q->setErrorString(plainSocket->errorString());
|
||||
emit q->error(error);
|
||||
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
index ac4336a..94655fe 100644
|
||||
--- a/src/network/ssl/qsslsocket_openssl.cpp
|
||||
+++ b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
@@ -1419,6 +1419,13 @@ void QSslSocketBackendPrivate::disconnected()
|
||||
{
|
||||
if (plainSocket->bytesAvailable() <= 0)
|
||||
destroySslContext();
|
||||
+ else {
|
||||
+ // Move all bytes into the plain buffer
|
||||
+ qint64 tmpReadBufferMaxSize = readBufferMaxSize;
|
||||
+ readBufferMaxSize = 0; // reset temporarily so the plain socket buffer is completely drained
|
||||
+ transmit();
|
||||
+ readBufferMaxSize = tmpReadBufferMaxSize;
|
||||
+ }
|
||||
//if there is still buffered data in the plain socket, don't destroy the ssl context yet.
|
||||
//it will be destroyed when the socket is deleted.
|
||||
}
|
||||
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
index d2edf67..138f528 100644
|
||||
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
|
||||
@@ -1051,7 +1051,7 @@ protected:
|
||||
// clean up QAbstractSocket's residue:
|
||||
while (client->bytesToWrite() > 0) {
|
||||
qDebug() << "Still having" << client->bytesToWrite() << "bytes to write, doing that now";
|
||||
- if (!client->waitForBytesWritten(2000)) {
|
||||
+ if (!client->waitForBytesWritten(10000)) {
|
||||
qDebug() << "ERROR: FastSender:" << client->error() << "cleaning up residue";
|
||||
return;
|
||||
}
|
||||
@@ -1071,7 +1071,7 @@ protected:
|
||||
measuredSentBytes += writeNextData(client, bytesToWrite);
|
||||
|
||||
while (client->bytesToWrite() > 0) {
|
||||
- if (!client->waitForBytesWritten(2000)) {
|
||||
+ if (!client->waitForBytesWritten(10000)) {
|
||||
qDebug() << "ERROR: FastSender:" << client->error() << "during blocking write";
|
||||
return;
|
||||
}
|
||||
@@ -7946,7 +7946,7 @@ public slots:
|
||||
m_receivedData += data;
|
||||
if (!m_parsedHeaders && m_receivedData.contains("\r\n\r\n")) {
|
||||
m_parsedHeaders = true;
|
||||
- QTimer::singleShot(qrand()%10, this, SLOT(closeDelayed())); // simulate random network latency
|
||||
+ QTimer::singleShot(qrand()%60, this, SLOT(closeDelayed())); // simulate random network latency
|
||||
// This server simulates a web server connection closing, e.g. because of Apaches MaxKeepAliveRequests or KeepAliveTimeout
|
||||
// In this case QNAM needs to re-send the upload data but it had a bug which then corrupts the upload
|
||||
// This test catches that.
|
||||
@@ -8052,11 +8052,12 @@ void tst_QNetworkReply::putWithServerClosingConnectionImmediately()
|
||||
|
||||
// get the request started and the incoming socket connected
|
||||
QTestEventLoop::instance().enterLoop(10);
|
||||
+ QVERIFY(!QTestEventLoop::instance().timeout());
|
||||
|
||||
//qDebug() << "correct=" << server.m_correctUploads << "corrupt=" << server.m_corruptUploads << "expected=" <<numUploads;
|
||||
|
||||
// Sanity check because ecause of 9c2ecf89 most replies will error out but we want to make sure at least some of them worked
|
||||
- QVERIFY(server.m_correctUploads > 5);
|
||||
+ QVERIFY(server.m_correctUploads > 2);
|
||||
// Because actually important is that we don't get any corruption:
|
||||
QCOMPARE(server.m_corruptUploads, 0);
|
||||
|
||||
--
|
||||
1.9.1
|
||||
@@ -0,0 +1,64 @@
|
||||
From bc32c0ebc0bc00db84ca2f28eb16ab2e5b53a1b6 Mon Sep 17 00:00:00 2001
|
||||
From: Markus Goetz <markus@woboq.com>
|
||||
Date: Fri, 24 Jul 2015 09:53:20 +0200
|
||||
Subject: [PATCH] QNAM: Fix reply deadlocks on server closing connection
|
||||
|
||||
The _q_readyRead can also be called from readMoreLater() because we implemented
|
||||
it so that bandwidth limited reading can be implemented.
|
||||
This can lead to a race condition if the socket is closing at the specific moment
|
||||
and then deadlock the channel: It will stay unusable with a zombie request.
|
||||
The fix in QHttpProtocolaHandler checks if there is actually bytes available to read
|
||||
from the socket and only then continue.
|
||||
|
||||
The fix in the HTTP channel needs to be done to properly finish the reply in
|
||||
cases of a server replying with HTTP/1.0 or "Connection: close".
|
||||
The delayed incovation of _q_receiveReply will properly finish up the reply.
|
||||
|
||||
Change-Id: I19ce2ae595f91d56386cc7406ccacc9935672b6b
|
||||
Reviewed-by: Richard J. Moore <rich@kde.org>
|
||||
---
|
||||
src/network/access/qhttpnetworkconnectionchannel.cpp | 4 ++++
|
||||
src/network/access/qhttpprotocolhandler.cpp | 7 ++++++-
|
||||
2 files changed, 10 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
index 7428f9b..257aa13 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
@@ -829,11 +829,15 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
|
||||
|
||||
if (!reply->d_func()->expectContent()) {
|
||||
// No content expected, this is a valid way to have the connection closed by the server
|
||||
+ // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
|
||||
+ QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
if (reply->contentLength() == -1 && !reply->d_func()->isChunked()) {
|
||||
// There was no content-length header and it's not chunked encoding,
|
||||
// so this is a valid way to have the connection closed by the server
|
||||
+ // We need to invoke this asynchronously to make sure the state() of the socket is on QAbstractSocket::UnconnectedState
|
||||
+ QMetaObject::invokeMethod(this, "_q_receiveReply", Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
// ok, we got a disconnect even though we did not expect it
|
||||
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
|
||||
index ab2e3da..a208315 100644
|
||||
--- a/src/network/access/qhttpprotocolhandler.cpp
|
||||
+++ b/src/network/access/qhttpprotocolhandler.cpp
|
||||
@@ -237,7 +237,12 @@ void QHttpProtocolHandler::_q_readyRead()
|
||||
}
|
||||
|
||||
if (m_channel->isSocketWaiting() || m_channel->isSocketReading()) {
|
||||
- m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
|
||||
+ if (m_socket->bytesAvailable()) {
|
||||
+ // We might get a spurious call from readMoreLater()
|
||||
+ // call of the QHttpNetworkConnection even while the socket is disconnecting.
|
||||
+ // Therefore check if there is actually bytes available before changing the channel state.
|
||||
+ m_channel->state = QHttpNetworkConnectionChannel::ReadingState;
|
||||
+ }
|
||||
if (m_reply)
|
||||
_q_receiveReply();
|
||||
}
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
From 63cf5d3d26a6f65938c3cdec1734eac9faaaf8cb Mon Sep 17 00:00:00 2001
|
||||
From: Markus Goetz <markus@woboq.com>
|
||||
Date: Tue, 22 Sep 2015 14:26:24 -0400
|
||||
Subject: [PATCH] QNAM: Assign proper channel before sslErrors() emission
|
||||
|
||||
There can be a race condition where another channel connects
|
||||
and gets the sslErrors() from the socket first. Then the
|
||||
QSslConfiguration from the wrong socket (the default
|
||||
channel 0's socket) was used.
|
||||
|
||||
Task-number: QTBUG-18722
|
||||
Change-Id: Ibbfa48c27f181563745daf540fa792a57cc09682
|
||||
Reviewed-by: Richard J. Moore <rich@kde.org>
|
||||
---
|
||||
src/network/access/qhttpnetworkconnectionchannel.cpp | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
index 257aa13..477cba2 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
@@ -1066,6 +1066,8 @@ void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
|
||||
connection->d_func()->pauseConnection();
|
||||
if (pendingEncrypt && !reply)
|
||||
connection->d_func()->dequeueRequest(socket);
|
||||
+ if (reply) // a reply was actually dequeued.
|
||||
+ reply->d_func()->connectionChannel = this; // set correct channel like in sendRequest() and queueRequest();
|
||||
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP) {
|
||||
if (reply)
|
||||
emit reply->sslErrors(errors);
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
From 0df5d079290b4c3b13e58e9397fabdc1dfdba96b Mon Sep 17 00:00:00 2001
|
||||
From: Ulf Hermann <ulf.hermann@theqtcompany.com>
|
||||
Date: Fri, 25 Sep 2015 13:23:46 +0200
|
||||
Subject: [PATCH] Don't let closed http sockets pass as valid connections
|
||||
|
||||
A QAbstractSocket can be close()'d at any time, independently of its
|
||||
current connection state. being closed means that we cannot use it to
|
||||
read or write data, but internally it might still have some data to
|
||||
send or receive, for example to an http server. We can even get a
|
||||
connected() signal after close()'ing the socket.
|
||||
|
||||
We need to catch this condition and mark any pending data not yet
|
||||
written to the socket for resending.
|
||||
|
||||
Task-number: QTBUG-48326
|
||||
Change-Id: I6f61c35f2c567f2a138f8cfe9ade7fd1ec039be6
|
||||
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
|
||||
---
|
||||
.../access/qhttpnetworkconnectionchannel.cpp | 7 ++-
|
||||
.../tst_qhttpnetworkconnection.cpp | 54 ++++++++++++++++++++++
|
||||
2 files changed, 60 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
index 293909c..b4eda34 100644
|
||||
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
|
||||
@@ -272,7 +272,12 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
|
||||
QAbstractSocket::SocketState socketState = socket->state();
|
||||
|
||||
// resend this request after we receive the disconnected signal
|
||||
- if (socketState == QAbstractSocket::ClosingState) {
|
||||
+ // If !socket->isOpen() then we have already called close() on the socket, but there was still a
|
||||
+ // pending connectToHost() for which we hadn't seen a connected() signal, yet. The connected()
|
||||
+ // has now arrived (as indicated by socketState != ClosingState), but we cannot send anything on
|
||||
+ // such a socket anymore.
|
||||
+ if (socketState == QAbstractSocket::ClosingState ||
|
||||
+ (socketState != QAbstractSocket::UnconnectedState && !socket->isOpen())) {
|
||||
if (reply)
|
||||
resendCurrent = true;
|
||||
return false;
|
||||
diff --git a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp
|
||||
index 5d072af..0d188a8 100644
|
||||
--- a/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp
|
||||
+++ b/tests/auto/network/access/qhttpnetworkconnection/tst_qhttpnetworkconnection.cpp
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "private/qhttpnetworkconnection_p.h"
|
||||
#include "private/qnoncontiguousbytedevice_p.h"
|
||||
#include <QAuthenticator>
|
||||
+#include <QTcpServer>
|
||||
|
||||
#include "../../../network-settings.h"
|
||||
|
||||
@@ -106,6 +107,8 @@ private Q_SLOTS:
|
||||
|
||||
void getAndThenDeleteObject();
|
||||
void getAndThenDeleteObject_data();
|
||||
+
|
||||
+ void overlappingCloseAndWrite();
|
||||
};
|
||||
|
||||
tst_QHttpNetworkConnection::tst_QHttpNetworkConnection()
|
||||
@@ -1112,6 +1115,57 @@ void tst_QHttpNetworkConnection::getAndThenDeleteObject()
|
||||
}
|
||||
}
|
||||
|
||||
+class TestTcpServer : public QTcpServer
|
||||
+{
|
||||
+ Q_OBJECT
|
||||
+public:
|
||||
+ TestTcpServer() : errorCodeReports(0)
|
||||
+ {
|
||||
+ connect(this, &QTcpServer::newConnection, this, &TestTcpServer::onNewConnection);
|
||||
+ QVERIFY(listen(QHostAddress::LocalHost));
|
||||
+ }
|
||||
+
|
||||
+ int errorCodeReports;
|
||||
+
|
||||
+public slots:
|
||||
+ void onNewConnection()
|
||||
+ {
|
||||
+ QTcpSocket *socket = nextPendingConnection();
|
||||
+ if (!socket)
|
||||
+ return;
|
||||
+ // close socket instantly!
|
||||
+ connect(socket, &QTcpSocket::readyRead, socket, &QTcpSocket::close);
|
||||
+ }
|
||||
+
|
||||
+ void onReply(QNetworkReply::NetworkError code)
|
||||
+ {
|
||||
+ QCOMPARE(code, QNetworkReply::RemoteHostClosedError);
|
||||
+ ++errorCodeReports;
|
||||
+ }
|
||||
+};
|
||||
+
|
||||
+void tst_QHttpNetworkConnection::overlappingCloseAndWrite()
|
||||
+{
|
||||
+ // server accepts connections, but closes the socket instantly
|
||||
+ TestTcpServer server;
|
||||
+ QNetworkAccessManager accessManager;
|
||||
+
|
||||
+ // ten requests are scheduled. All should result in an RemoteHostClosed...
|
||||
+ QUrl url;
|
||||
+ url.setScheme(QStringLiteral("http"));
|
||||
+ url.setHost(server.serverAddress().toString());
|
||||
+ url.setPort(server.serverPort());
|
||||
+ for (int i = 0; i < 10; ++i) {
|
||||
+ QNetworkRequest request(url);
|
||||
+ QNetworkReply *reply = accessManager.get(request);
|
||||
+ // Not using Qt5 connection syntax here because of overly baroque syntax to discern between
|
||||
+ // different error() methods.
|
||||
+ QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
|
||||
+ &server, SLOT(onReply(QNetworkReply::NetworkError)));
|
||||
+ }
|
||||
+
|
||||
+ QTRY_COMPARE(server.errorCodeReports, 10);
|
||||
+}
|
||||
|
||||
|
||||
QTEST_MAIN(tst_QHttpNetworkConnection)
|
||||
--
|
||||
1.9.1
|
||||
@@ -0,0 +1,113 @@
|
||||
From c056e63cea1915667997c982f48296ce5acdcc80 Mon Sep 17 00:00:00 2001
|
||||
From: Lorn Potter <lorn.potter@gmail.com>
|
||||
Date: Tue, 2 Jun 2015 13:22:23 +1000
|
||||
Subject: [PATCH] Make sure to report correct NetworkAccessibility
|
||||
|
||||
Task-number: QTBUG-46323
|
||||
Change-Id: Ibdeb3280091a97d785d4314340678a63e88fb219
|
||||
Reviewed-by: Markus Goetz (Woboq GmbH) <markus@woboq.com>
|
||||
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
|
||||
---
|
||||
src/network/access/qnetworkaccessmanager.cpp | 25 +++++++++++++++++--------
|
||||
src/network/access/qnetworkaccessmanager_p.h | 2 ++
|
||||
2 files changed, 19 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
|
||||
index e878feb..84931cb 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager.cpp
|
||||
+++ b/src/network/access/qnetworkaccessmanager.cpp
|
||||
@@ -472,11 +472,11 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent)
|
||||
// the QNetworkSession's signals
|
||||
connect(&d->networkConfigurationManager, SIGNAL(onlineStateChanged(bool)),
|
||||
SLOT(_q_onlineStateChanged(bool)));
|
||||
- // we would need all active configurations to check for
|
||||
- // d->networkConfigurationManager.isOnline(), which is asynchronous
|
||||
- // and potentially expensive. We can just check the configuration here
|
||||
- d->online = (d->networkConfiguration.state() & QNetworkConfiguration::Active);
|
||||
}
|
||||
+ // we would need all active configurations to check for
|
||||
+ // d->networkConfigurationManager.isOnline(), which is asynchronous
|
||||
+ // and potentially expensive. We can just check the configuration here
|
||||
+ d->online = (d->networkConfiguration.state() & QNetworkConfiguration::Active);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -946,6 +946,7 @@ QNetworkConfiguration QNetworkAccessManager::activeConfiguration() const
|
||||
void QNetworkAccessManager::setNetworkAccessible(QNetworkAccessManager::NetworkAccessibility accessible)
|
||||
{
|
||||
Q_D(QNetworkAccessManager);
|
||||
+ d->defaultAccessControl = false;
|
||||
|
||||
if (d->networkAccessible != accessible) {
|
||||
NetworkAccessibility previous = networkAccessible();
|
||||
@@ -964,7 +965,6 @@ void QNetworkAccessManager::setNetworkAccessible(QNetworkAccessManager::NetworkA
|
||||
QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccessible() const
|
||||
{
|
||||
Q_D(const QNetworkAccessManager);
|
||||
-
|
||||
if (d->networkSessionRequired) {
|
||||
QSharedPointer<QNetworkSession> networkSession(d->getNetworkSession());
|
||||
if (networkSession) {
|
||||
@@ -975,7 +975,13 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess
|
||||
return NotAccessible;
|
||||
} else {
|
||||
// Network accessibility is either disabled or unknown.
|
||||
- return (d->networkAccessible == NotAccessible) ? NotAccessible : UnknownAccessibility;
|
||||
+ if (d->defaultAccessControl) {
|
||||
+ if (d->online)
|
||||
+ return d->networkAccessible;
|
||||
+ else
|
||||
+ return NotAccessible;
|
||||
+ }
|
||||
+ return (d->networkAccessible);
|
||||
}
|
||||
} else {
|
||||
if (d->online)
|
||||
@@ -1568,7 +1574,7 @@ void QNetworkAccessManagerPrivate::createSession(const QNetworkConfiguration &co
|
||||
if (!networkSessionStrongRef) {
|
||||
online = false;
|
||||
|
||||
- if (networkAccessible == QNetworkAccessManager::NotAccessible)
|
||||
+ if (networkAccessible == QNetworkAccessManager::NotAccessible || !online)
|
||||
emit q->networkAccessibleChanged(QNetworkAccessManager::NotAccessible);
|
||||
else
|
||||
emit q->networkAccessibleChanged(QNetworkAccessManager::UnknownAccessibility);
|
||||
@@ -1616,11 +1622,14 @@ void QNetworkAccessManagerPrivate::_q_networkSessionStateChanged(QNetworkSession
|
||||
if (online) {
|
||||
if (state != QNetworkSession::Connected && state != QNetworkSession::Roaming) {
|
||||
online = false;
|
||||
- emit q->networkAccessibleChanged(QNetworkAccessManager::NotAccessible);
|
||||
+ networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
}
|
||||
} else {
|
||||
if (state == QNetworkSession::Connected || state == QNetworkSession::Roaming) {
|
||||
online = true;
|
||||
+ if (defaultAccessControl)
|
||||
+ networkAccessible = QNetworkAccessManager::Accessible;
|
||||
emit q->networkAccessibleChanged(networkAccessible);
|
||||
}
|
||||
}
|
||||
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
|
||||
index f513324..c715da0 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager_p.h
|
||||
+++ b/src/network/access/qnetworkaccessmanager_p.h
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
initializeSession(true),
|
||||
#endif
|
||||
cookieJarCreated(false),
|
||||
+ defaultAccessControl(true),
|
||||
authenticationManager(QSharedPointer<QNetworkAccessAuthenticationManager>::create())
|
||||
{ }
|
||||
~QNetworkAccessManagerPrivate();
|
||||
@@ -164,6 +165,7 @@ public:
|
||||
#endif
|
||||
|
||||
bool cookieJarCreated;
|
||||
+ bool defaultAccessControl;
|
||||
|
||||
// The cache with authorization data:
|
||||
QSharedPointer<QNetworkAccessAuthenticationManager> authenticationManager;
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
From bb281eea179d50a413f4ec1ff172d27ee48d3a41 Mon Sep 17 00:00:00 2001
|
||||
From: Lorn Potter <lorn.potter@gmail.com>
|
||||
Date: Fri, 17 Jul 2015 15:32:23 +1000
|
||||
Subject: [PATCH] Make sure networkAccessibilityChanged is emitted
|
||||
|
||||
Task-number: QTBUG-46323
|
||||
Change-Id: I8297072b62763136f457ca6ae15282d1c22244f4
|
||||
Reviewed-by: Timo Jyrinki <timo.jyrinki@canonical.com>
|
||||
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
|
||||
---
|
||||
src/network/access/qnetworkaccessmanager.cpp | 70 +++++++++++++++-------
|
||||
src/network/access/qnetworkaccessmanager_p.h | 14 ++++-
|
||||
.../tst_qnetworkaccessmanager.cpp | 31 +++++-----
|
||||
3 files changed, 77 insertions(+), 38 deletions(-)
|
||||
|
||||
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
|
||||
index 84931cb..f9e9513 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager.cpp
|
||||
+++ b/src/network/access/qnetworkaccessmanager.cpp
|
||||
@@ -278,7 +278,8 @@ static void ensureInitialized()
|
||||
|
||||
\snippet code/src_network_access_qnetworkaccessmanager.cpp 4
|
||||
|
||||
- Network requests can be reenabled again by calling
|
||||
+ Network requests can be re-enabled again, and this property will resume to
|
||||
+ reflect the actual device state by calling
|
||||
|
||||
\snippet code/src_network_access_qnetworkaccessmanager.cpp 5
|
||||
|
||||
@@ -467,16 +468,12 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent)
|
||||
qRegisterMetaType<QSharedPointer<char> >();
|
||||
|
||||
#ifndef QT_NO_BEARERMANAGEMENT
|
||||
- if (!d->networkSessionRequired) {
|
||||
- // if a session is required, we track online state through
|
||||
- // the QNetworkSession's signals
|
||||
- connect(&d->networkConfigurationManager, SIGNAL(onlineStateChanged(bool)),
|
||||
- SLOT(_q_onlineStateChanged(bool)));
|
||||
- }
|
||||
- // we would need all active configurations to check for
|
||||
- // d->networkConfigurationManager.isOnline(), which is asynchronous
|
||||
- // and potentially expensive. We can just check the configuration here
|
||||
- d->online = (d->networkConfiguration.state() & QNetworkConfiguration::Active);
|
||||
+ // if a session is required, we track online state through
|
||||
+ // the QNetworkSession's signals if a request is already made.
|
||||
+ // we need to track current accessibility state by default
|
||||
+ //
|
||||
+ connect(&d->networkConfigurationManager, SIGNAL(onlineStateChanged(bool)),
|
||||
+ SLOT(_q_onlineStateChanged(bool)));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -946,7 +943,8 @@ QNetworkConfiguration QNetworkAccessManager::activeConfiguration() const
|
||||
void QNetworkAccessManager::setNetworkAccessible(QNetworkAccessManager::NetworkAccessibility accessible)
|
||||
{
|
||||
Q_D(QNetworkAccessManager);
|
||||
- d->defaultAccessControl = false;
|
||||
+
|
||||
+ d->defaultAccessControl = accessible == NotAccessible ? false : true;
|
||||
|
||||
if (d->networkAccessible != accessible) {
|
||||
NetworkAccessibility previous = networkAccessible();
|
||||
@@ -965,6 +963,10 @@ void QNetworkAccessManager::setNetworkAccessible(QNetworkAccessManager::NetworkA
|
||||
QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccessible() const
|
||||
{
|
||||
Q_D(const QNetworkAccessManager);
|
||||
+
|
||||
+ if (d->networkConfiguration.state().testFlag(QNetworkConfiguration::Undefined))
|
||||
+ return UnknownAccessibility;
|
||||
+
|
||||
if (d->networkSessionRequired) {
|
||||
QSharedPointer<QNetworkSession> networkSession(d->getNetworkSession());
|
||||
if (networkSession) {
|
||||
@@ -1622,32 +1624,56 @@ void QNetworkAccessManagerPrivate::_q_networkSessionStateChanged(QNetworkSession
|
||||
if (online) {
|
||||
if (state != QNetworkSession::Connected && state != QNetworkSession::Roaming) {
|
||||
online = false;
|
||||
- networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
- emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ if (networkAccessible != QNetworkAccessManager::NotAccessible) {
|
||||
+ networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ }
|
||||
}
|
||||
} else {
|
||||
if (state == QNetworkSession::Connected || state == QNetworkSession::Roaming) {
|
||||
online = true;
|
||||
if (defaultAccessControl)
|
||||
- networkAccessible = QNetworkAccessManager::Accessible;
|
||||
- emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ if (networkAccessible != QNetworkAccessManager::Accessible) {
|
||||
+ networkAccessible = QNetworkAccessManager::Accessible;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QNetworkAccessManagerPrivate::_q_onlineStateChanged(bool isOnline)
|
||||
{
|
||||
- // if the user set a config, we only care whether this one is active.
|
||||
+ Q_Q(QNetworkAccessManager);
|
||||
+ // if the user set a config, we only care whether this one is active.
|
||||
// Otherwise, this QNAM is online if there is an online config.
|
||||
if (customNetworkConfiguration) {
|
||||
online = (networkConfiguration.state() & QNetworkConfiguration::Active);
|
||||
} else {
|
||||
- if (isOnline && online != isOnline) {
|
||||
- networkSessionStrongRef.clear();
|
||||
- networkSessionWeakRef.clear();
|
||||
+ if (online != isOnline) {
|
||||
+ if (isOnline) {
|
||||
+ networkSessionStrongRef.clear();
|
||||
+ networkSessionWeakRef.clear();
|
||||
+ }
|
||||
+ online = isOnline;
|
||||
+ }
|
||||
+ }
|
||||
+ if (online) {
|
||||
+ if (defaultAccessControl) {
|
||||
+ if (networkAccessible != QNetworkAccessManager::Accessible) {
|
||||
+ networkAccessible = QNetworkAccessManager::Accessible;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ }
|
||||
+ }
|
||||
+ } else if (networkConfiguration.state().testFlag(QNetworkConfiguration::Undefined)) {
|
||||
+ if (networkAccessible != QNetworkAccessManager::UnknownAccessibility) {
|
||||
+ networkAccessible = QNetworkAccessManager::UnknownAccessibility;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
+ }
|
||||
+ } else {
|
||||
+ if (networkAccessible != QNetworkAccessManager::NotAccessible) {
|
||||
+ networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
+ emit q->networkAccessibleChanged(networkAccessible);
|
||||
}
|
||||
-
|
||||
- online = isOnline;
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
|
||||
index c715da0..54ae114 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager_p.h
|
||||
+++ b/src/network/access/qnetworkaccessmanager_p.h
|
||||
@@ -78,7 +78,6 @@ public:
|
||||
customNetworkConfiguration(false),
|
||||
networkSessionRequired(networkConfigurationManager.capabilities()
|
||||
& QNetworkConfigurationManager::NetworkSessionRequired),
|
||||
- networkAccessible(QNetworkAccessManager::Accessible),
|
||||
activeReplyCount(0),
|
||||
online(false),
|
||||
initializeSession(true),
|
||||
@@ -86,7 +85,18 @@ public:
|
||||
cookieJarCreated(false),
|
||||
defaultAccessControl(true),
|
||||
authenticationManager(QSharedPointer<QNetworkAccessAuthenticationManager>::create())
|
||||
- { }
|
||||
+ {
|
||||
+#ifndef QT_NO_BEARERMANAGEMENT
|
||||
+ // we would need all active configurations to check for
|
||||
+ // d->networkConfigurationManager.isOnline(), which is asynchronous
|
||||
+ // and potentially expensive. We can just check the configuration here
|
||||
+ online = (networkConfiguration.state().testFlag(QNetworkConfiguration::Active));
|
||||
+ if (online)
|
||||
+ networkAccessible = QNetworkAccessManager::Accessible;
|
||||
+ else
|
||||
+ networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
+#endif
|
||||
+ }
|
||||
~QNetworkAccessManagerPrivate();
|
||||
|
||||
void _q_replyFinished();
|
||||
diff --git a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp
|
||||
index b4e4b9c..8ecb57d 100644
|
||||
--- a/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp
|
||||
+++ b/tests/auto/network/access/qnetworkaccessmanager/tst_qnetworkaccessmanager.cpp
|
||||
@@ -74,6 +74,10 @@ void tst_QNetworkAccessManager::networkAccessible()
|
||||
// if there is no session, we cannot know in which state we are in
|
||||
QNetworkAccessManager::NetworkAccessibility initialAccessibility =
|
||||
manager.networkAccessible();
|
||||
+
|
||||
+ if (initialAccessibility == QNetworkAccessManager::UnknownAccessibility)
|
||||
+ QSKIP("Unknown accessibility", SkipAll);
|
||||
+
|
||||
QCOMPARE(manager.networkAccessible(), initialAccessibility);
|
||||
|
||||
manager.setNetworkAccessible(QNetworkAccessManager::NotAccessible);
|
||||
@@ -94,29 +98,28 @@ void tst_QNetworkAccessManager::networkAccessible()
|
||||
QCOMPARE(manager.networkAccessible(), initialAccessibility);
|
||||
|
||||
QNetworkConfigurationManager configManager;
|
||||
- bool sessionRequired = (configManager.capabilities()
|
||||
- & QNetworkConfigurationManager::NetworkSessionRequired);
|
||||
QNetworkConfiguration defaultConfig = configManager.defaultConfiguration();
|
||||
if (defaultConfig.isValid()) {
|
||||
manager.setConfiguration(defaultConfig);
|
||||
|
||||
- // the accessibility has not changed if no session is required
|
||||
- if (sessionRequired) {
|
||||
+ QCOMPARE(spy.count(), 0);
|
||||
+
|
||||
+ if (defaultConfig.state().testFlag(QNetworkConfiguration::Active))
|
||||
+ QCOMPARE(manager.networkAccessible(), QNetworkAccessManager::Accessible);
|
||||
+ else
|
||||
+ QCOMPARE(manager.networkAccessible(), QNetworkAccessManager::NotAccessible);
|
||||
+
|
||||
+ manager.setNetworkAccessible(QNetworkAccessManager::NotAccessible);
|
||||
+
|
||||
+ if (defaultConfig.state().testFlag(QNetworkConfiguration::Active)) {
|
||||
QCOMPARE(spy.count(), 1);
|
||||
- QCOMPARE(spy.takeFirst().at(0).value<QNetworkAccessManager::NetworkAccessibility>(),
|
||||
- QNetworkAccessManager::Accessible);
|
||||
+ QCOMPARE(QNetworkAccessManager::NetworkAccessibility(spy.takeFirst().at(0).toInt()),
|
||||
+ QNetworkAccessManager::NotAccessible);
|
||||
} else {
|
||||
QCOMPARE(spy.count(), 0);
|
||||
}
|
||||
- QCOMPARE(manager.networkAccessible(), QNetworkAccessManager::Accessible);
|
||||
-
|
||||
- manager.setNetworkAccessible(QNetworkAccessManager::NotAccessible);
|
||||
-
|
||||
- QCOMPARE(spy.count(), 1);
|
||||
- QCOMPARE(QNetworkAccessManager::NetworkAccessibility(spy.takeFirst().at(0).toInt()),
|
||||
- QNetworkAccessManager::NotAccessible);
|
||||
- QCOMPARE(manager.networkAccessible(), QNetworkAccessManager::NotAccessible);
|
||||
}
|
||||
+ QCOMPARE(manager.networkAccessible(), QNetworkAccessManager::NotAccessible);
|
||||
#endif
|
||||
}
|
||||
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
From e996d68f6130847637ba287518cff1289cfa48e5 Mon Sep 17 00:00:00 2001
|
||||
From: Lorn Potter <lorn.potter@gmail.com>
|
||||
Date: Fri, 6 Nov 2015 14:22:44 +1000
|
||||
Subject: [PATCH] Make UnknownAccessibility not block requests
|
||||
|
||||
This allows requests to proceed without needing bearer plugins.
|
||||
|
||||
Task-number: QTBUG-49267
|
||||
Change-Id: Ie5ce188ddefebd14d666bb5846e8f93ee2925ed1
|
||||
Reviewed-by: Markus Goetz (Woboq GmbH) <markus@woboq.com>
|
||||
---
|
||||
src/network/access/qnetworkaccessmanager.cpp | 3 +--
|
||||
src/network/access/qnetworkaccessmanager_p.h | 2 ++
|
||||
2 files changed, 3 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
|
||||
index 086140f..0e5870a 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager.cpp
|
||||
+++ b/src/network/access/qnetworkaccessmanager.cpp
|
||||
@@ -976,7 +976,6 @@ QNetworkAccessManager::NetworkAccessibility QNetworkAccessManager::networkAccess
|
||||
else
|
||||
return NotAccessible;
|
||||
} else {
|
||||
- // Network accessibility is either disabled or unknown.
|
||||
if (d->defaultAccessControl) {
|
||||
if (d->online)
|
||||
return d->networkAccessible;
|
||||
@@ -1161,7 +1160,7 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
|
||||
#ifndef QT_NO_BEARERMANAGEMENT
|
||||
// Return a disabled network reply if network access is disabled.
|
||||
// Except if the scheme is empty or file://.
|
||||
- if (!d->networkAccessible && !isLocalFile) {
|
||||
+ if (d->networkAccessible == NotAccessible && !isLocalFile) {
|
||||
return new QDisabledNetworkReply(this, req, op);
|
||||
}
|
||||
|
||||
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
|
||||
index 54ae114..3fc33b5 100644
|
||||
--- a/src/network/access/qnetworkaccessmanager_p.h
|
||||
+++ b/src/network/access/qnetworkaccessmanager_p.h
|
||||
@@ -93,6 +93,8 @@ public:
|
||||
online = (networkConfiguration.state().testFlag(QNetworkConfiguration::Active));
|
||||
if (online)
|
||||
networkAccessible = QNetworkAccessManager::Accessible;
|
||||
+ else if (networkConfiguration.state().testFlag(QNetworkConfiguration::Undefined))
|
||||
+ networkAccessible = QNetworkAccessManager::UnknownAccessibility;
|
||||
else
|
||||
networkAccessible = QNetworkAccessManager::NotAccessible;
|
||||
#endif
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
From 06818f6d1c602aa3c4f9356324866432d2dd0195 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Mon, 16 Nov 2015 15:02:37 +0100
|
||||
Subject: [PATCH 1/2] Remove legacy platform code in QSslSocket for OS X < 10.5
|
||||
|
||||
This avoids manual symbol lookups and makes the code more readable.
|
||||
Mark identical code.
|
||||
|
||||
Also use smart pointers instead of manual memory management.
|
||||
|
||||
(Backport of d42d7781f1cd62c3c7c008859507f24a1ff5bb2a to Qt 5.4)
|
||||
|
||||
Change-Id: I62820313dce87de6623cdc87b6e1361200ed7822
|
||||
Reviewed-by: Markus Goetz (Woboq GmbH) <markus@woboq.com>
|
||||
|
||||
Conflicts:
|
||||
src/network/ssl/qsslsocket_openssl.cpp
|
||||
---
|
||||
src/network/ssl/qsslsocket_openssl.cpp | 83 +++++++++++-----------------------
|
||||
src/network/ssl/qsslsocket_p.h | 6 +--
|
||||
2 files changed, 28 insertions(+), 61 deletions(-)
|
||||
|
||||
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
index 13fc534..7d0fe00 100644
|
||||
--- a/src/network/ssl/qsslsocket_openssl.cpp
|
||||
+++ b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
@@ -69,14 +69,19 @@
|
||||
#include <QtCore/qvarlengtharray.h>
|
||||
#include <QLibrary> // for loading the security lib for the CA store
|
||||
|
||||
+#include <string.h>
|
||||
+
|
||||
+#ifdef Q_OS_DARWIN
|
||||
+# include <private/qcore_mac_p.h>
|
||||
+#endif
|
||||
+
|
||||
+#ifdef Q_OS_OSX
|
||||
+# include <Security/Security.h>
|
||||
+#endif
|
||||
+
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
-#if defined(Q_OS_MACX)
|
||||
-#define kSecTrustSettingsDomainSystem 2 // so we do not need to include the header file
|
||||
- PtrSecCertificateCopyData QSslSocketPrivate::ptrSecCertificateCopyData = 0;
|
||||
- PtrSecTrustSettingsCopyCertificates QSslSocketPrivate::ptrSecTrustSettingsCopyCertificates = 0;
|
||||
- PtrSecTrustCopyAnchorCertificates QSslSocketPrivate::ptrSecTrustCopyAnchorCertificates = 0;
|
||||
-#elif defined(Q_OS_WIN)
|
||||
+#if defined(Q_OS_WIN)
|
||||
PtrCertOpenSystemStoreW QSslSocketPrivate::ptrCertOpenSystemStoreW = 0;
|
||||
PtrCertFindCertificateInStore QSslSocketPrivate::ptrCertFindCertificateInStore = 0;
|
||||
PtrCertCloseStore QSslSocketPrivate::ptrCertCloseStore = 0;
|
||||
@@ -482,23 +487,7 @@ void QSslSocketPrivate::ensureCiphersAndCertsLoaded()
|
||||
|
||||
#ifndef QT_NO_LIBRARY
|
||||
//load symbols needed to receive certificates from system store
|
||||
-#if defined(Q_OS_MACX)
|
||||
- QLibrary securityLib("/System/Library/Frameworks/Security.framework/Versions/Current/Security");
|
||||
- if (securityLib.load()) {
|
||||
- ptrSecCertificateCopyData = (PtrSecCertificateCopyData) securityLib.resolve("SecCertificateCopyData");
|
||||
- if (!ptrSecCertificateCopyData)
|
||||
- qWarning("could not resolve symbols in security library"); // should never happen
|
||||
-
|
||||
- ptrSecTrustSettingsCopyCertificates = (PtrSecTrustSettingsCopyCertificates) securityLib.resolve("SecTrustSettingsCopyCertificates");
|
||||
- if (!ptrSecTrustSettingsCopyCertificates) { // method was introduced in Leopard, use legacy method if it's not there
|
||||
- ptrSecTrustCopyAnchorCertificates = (PtrSecTrustCopyAnchorCertificates) securityLib.resolve("SecTrustCopyAnchorCertificates");
|
||||
- if (!ptrSecTrustCopyAnchorCertificates)
|
||||
- qWarning("could not resolve symbols in security library"); // should never happen
|
||||
- }
|
||||
- } else {
|
||||
- qWarning("could not load security library");
|
||||
- }
|
||||
-#elif defined(Q_OS_WIN)
|
||||
+#if defined(Q_OS_WIN)
|
||||
HINSTANCE hLib = LoadLibraryW(L"Crypt32");
|
||||
if (hLib) {
|
||||
#if defined(Q_OS_WINCE)
|
||||
@@ -635,40 +624,22 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
||||
timer.start();
|
||||
#endif
|
||||
QList<QSslCertificate> systemCerts;
|
||||
-#if defined(Q_OS_MACX)
|
||||
- CFArrayRef cfCerts;
|
||||
- OSStatus status = 1;
|
||||
-
|
||||
- CFDataRef SecCertificateCopyData (
|
||||
- SecCertificateRef certificate
|
||||
- );
|
||||
-
|
||||
- if (ptrSecCertificateCopyData) {
|
||||
- if (ptrSecTrustSettingsCopyCertificates)
|
||||
- status = ptrSecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts);
|
||||
- else if (ptrSecTrustCopyAnchorCertificates)
|
||||
- status = ptrSecTrustCopyAnchorCertificates(&cfCerts);
|
||||
- if (!status) {
|
||||
- CFIndex size = CFArrayGetCount(cfCerts);
|
||||
- for (CFIndex i = 0; i < size; ++i) {
|
||||
- SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
|
||||
- CFDataRef data;
|
||||
-
|
||||
- data = ptrSecCertificateCopyData(cfCert);
|
||||
-
|
||||
- if (data == NULL) {
|
||||
- qWarning("error retrieving a CA certificate from the system store");
|
||||
- } else {
|
||||
- QByteArray rawCert = QByteArray::fromRawData((const char *)CFDataGetBytePtr(data), CFDataGetLength(data));
|
||||
- systemCerts.append(QSslCertificate::fromData(rawCert, QSsl::Der));
|
||||
- CFRelease(data);
|
||||
- }
|
||||
+ // note: also check implementation in openssl_mac.cpp
|
||||
+#if defined(Q_OS_OSX)
|
||||
+ // SecTrustSettingsCopyCertificates is not defined on iOS.
|
||||
+ QCFType<CFArrayRef> cfCerts;
|
||||
+
|
||||
+ OSStatus status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts);
|
||||
+ if (status == noErr ) {
|
||||
+ const CFIndex size = CFArrayGetCount(cfCerts);
|
||||
+ for (CFIndex i = 0; i < size; ++i) {
|
||||
+ SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
|
||||
+ QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
|
||||
+ if (derData == NULL) {
|
||||
+ qWarning("error retrieving a CA certificate from the system store");
|
||||
+ } else {
|
||||
+ systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
|
||||
}
|
||||
- CFRelease(cfCerts);
|
||||
- }
|
||||
- else {
|
||||
- // no detailed error handling here
|
||||
- qWarning("could not retrieve system CA certificates");
|
||||
}
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
diff --git a/src/network/ssl/qsslsocket_p.h b/src/network/ssl/qsslsocket_p.h
|
||||
index 6e7a2c5..c1a6f05 100644
|
||||
--- a/src/network/ssl/qsslsocket_p.h
|
||||
+++ b/src/network/ssl/qsslsocket_p.h
|
||||
@@ -145,11 +145,7 @@ public:
|
||||
static bool isMatchingHostname(const QSslCertificate &cert, const QString &peerName);
|
||||
Q_AUTOTEST_EXPORT static bool isMatchingHostname(const QString &cn, const QString &hostname);
|
||||
|
||||
-#if defined(Q_OS_MACX)
|
||||
- static PtrSecCertificateCopyData ptrSecCertificateCopyData;
|
||||
- static PtrSecTrustSettingsCopyCertificates ptrSecTrustSettingsCopyCertificates;
|
||||
- static PtrSecTrustCopyAnchorCertificates ptrSecTrustCopyAnchorCertificates;
|
||||
-#elif defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
|
||||
+#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
|
||||
static PtrCertOpenSystemStoreW ptrCertOpenSystemStoreW;
|
||||
static PtrCertFindCertificateInStore ptrCertFindCertificateInStore;
|
||||
static PtrCertCloseStore ptrCertCloseStore;
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
From 6b9366e7748857f14d5b0f92ced70c08ab5235b7 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <danimo@owncloud.com>
|
||||
Date: Wed, 25 Nov 2015 12:37:27 +0100
|
||||
Subject: [PATCH 2/2] QSslSocket: evaluate CAs in all keychain categories
|
||||
|
||||
This will make sure that certs in the domainUser (login),
|
||||
and domainAdmin (per machine) keychain are being picked up
|
||||
in systemCaCertificates() in addition to the (usually immutable)
|
||||
DomainSystem keychain.
|
||||
|
||||
Also consider the trust settings on OS X: If a certificate
|
||||
is either fully trusted or trusted for the purpose of SSL,
|
||||
it will be accepted.
|
||||
|
||||
[ChangeLog][Platform Specific Changes] OS X now accepts trusted
|
||||
certificates from the login and system keychains.
|
||||
|
||||
(Backport of fe3a84138e266c425f11353f7d8dc28a588af89e to Qt 5.4)
|
||||
|
||||
Task-number: QTBUG-32898
|
||||
Change-Id: Ia23083d5af74388eeee31ba07239735cbbe64368
|
||||
Reviewed-by: Markus Goetz (Woboq GmbH) <markus@woboq.com>
|
||||
---
|
||||
src/network/ssl/qsslsocket.cpp | 4 +
|
||||
src/network/ssl/qsslsocket_mac_shared.cpp | 148 ++++++++++++++++++++++++++++++
|
||||
src/network/ssl/qsslsocket_openssl.cpp | 30 +-----
|
||||
src/network/ssl/ssl.pri | 4 +-
|
||||
4 files changed, 158 insertions(+), 28 deletions(-)
|
||||
create mode 100644 src/network/ssl/qsslsocket_mac_shared.cpp
|
||||
|
||||
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
|
||||
index 8887f47..6347c20 100644
|
||||
--- a/src/network/ssl/qsslsocket.cpp
|
||||
+++ b/src/network/ssl/qsslsocket.cpp
|
||||
@@ -1446,6 +1446,10 @@ QList<QSslCertificate> QSslSocket::defaultCaCertificates()
|
||||
returned by defaultCaCertificates(). You can replace that database
|
||||
with your own with setDefaultCaCertificates().
|
||||
|
||||
+ \note: On OS X, only certificates that are either trusted for all
|
||||
+ purposes or trusted for the purpose of SSL in the keychain will be
|
||||
+ returned.
|
||||
+
|
||||
\sa caCertificates(), defaultCaCertificates(), setDefaultCaCertificates()
|
||||
*/
|
||||
QList<QSslCertificate> QSslSocket::systemCaCertificates()
|
||||
diff --git a/src/network/ssl/qsslsocket_mac_shared.cpp b/src/network/ssl/qsslsocket_mac_shared.cpp
|
||||
new file mode 100644
|
||||
index 0000000..60fea4c
|
||||
--- /dev/null
|
||||
+++ b/src/network/ssl/qsslsocket_mac_shared.cpp
|
||||
@@ -0,0 +1,148 @@
|
||||
+/****************************************************************************
|
||||
+**
|
||||
+** Copyright (C) 2015 The Qt Company Ltd.
|
||||
+** Copyright (C) 2015 ownCloud Inc
|
||||
+** Contact: http://www.qt.io/licensing/
|
||||
+**
|
||||
+** This file is part of the QtNetwork module of the Qt Toolkit.
|
||||
+**
|
||||
+** $QT_BEGIN_LICENSE:LGPL21$
|
||||
+** Commercial License Usage
|
||||
+** Licensees holding valid commercial Qt licenses may use this file in
|
||||
+** accordance with the commercial license agreement provided with the
|
||||
+** Software or, alternatively, in accordance with the terms contained in
|
||||
+** a written agreement between you and The Qt Company. For licensing terms
|
||||
+** and conditions see http://www.qt.io/terms-conditions. For further
|
||||
+** information use the contact form at http://www.qt.io/contact-us.
|
||||
+**
|
||||
+** GNU Lesser General Public License Usage
|
||||
+** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
+** General Public License version 2.1 or version 3 as published by the Free
|
||||
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
+** following information to ensure the GNU Lesser General Public License
|
||||
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
+**
|
||||
+** As a special exception, The Qt Company gives you certain additional
|
||||
+** rights. These rights are described in The Qt Company LGPL Exception
|
||||
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
+**
|
||||
+** $QT_END_LICENSE$
|
||||
+**
|
||||
+****************************************************************************/
|
||||
+
|
||||
+//#define QSSLSOCKET_DEBUG
|
||||
+//#define QT_DECRYPT_SSL_TRAFFIC
|
||||
+
|
||||
+#include "qsslsocket.h"
|
||||
+
|
||||
+#ifndef QT_NO_OPENSSL
|
||||
+# include "qsslsocket_openssl_p.h"
|
||||
+# include "qsslsocket_openssl_symbols_p.h"
|
||||
+#endif
|
||||
+
|
||||
+#include "qsslcertificate_p.h"
|
||||
+
|
||||
+#ifdef Q_OS_DARWIN
|
||||
+# include <private/qcore_mac_p.h>
|
||||
+#endif
|
||||
+
|
||||
+#include <QtCore/qdebug.h>
|
||||
+
|
||||
+#ifdef Q_OS_OSX
|
||||
+# include <Security/Security.h>
|
||||
+#endif
|
||||
+
|
||||
+
|
||||
+QT_BEGIN_NAMESPACE
|
||||
+
|
||||
+#ifdef Q_OS_OSX
|
||||
+namespace {
|
||||
+
|
||||
+bool hasTrustedSslServerPolicy(SecPolicyRef policy, CFDictionaryRef props) {
|
||||
+ QCFType<CFDictionaryRef> policyProps = SecPolicyCopyProperties(policy);
|
||||
+ // only accept certificates with policies for SSL server validation for now
|
||||
+ if (CFEqual(CFDictionaryGetValue(policyProps, kSecPolicyOid), kSecPolicyAppleSSL)) {
|
||||
+ CFBooleanRef policyClient;
|
||||
+ if (CFDictionaryGetValueIfPresent(policyProps, kSecPolicyClient, reinterpret_cast<const void**>(&policyClient)) &&
|
||||
+ CFEqual(policyClient, kCFBooleanTrue)) {
|
||||
+ return false; // no client certs
|
||||
+ }
|
||||
+ if (!CFDictionaryContainsKey(props, kSecTrustSettingsResult)) {
|
||||
+ // as per the docs, no trust settings result implies full trust
|
||||
+ return true;
|
||||
+ }
|
||||
+ CFNumberRef number = static_cast<CFNumberRef>(CFDictionaryGetValue(props, kSecTrustSettingsResult));
|
||||
+ SecTrustSettingsResult settingsResult;
|
||||
+ CFNumberGetValue(number, kCFNumberSInt32Type, &settingsResult);
|
||||
+ switch (settingsResult) {
|
||||
+ case kSecTrustSettingsResultTrustRoot:
|
||||
+ case kSecTrustSettingsResultTrustAsRoot:
|
||||
+ return true;
|
||||
+ default:
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain)
|
||||
+{
|
||||
+ QCFType<CFArrayRef> cfTrustSettings;
|
||||
+ OSStatus status = SecTrustSettingsCopyTrustSettings(cfCert, domain, &cfTrustSettings);
|
||||
+ if (status == noErr) {
|
||||
+ CFIndex size = CFArrayGetCount(cfTrustSettings);
|
||||
+ // if empty, trust for everything (as per the Security Framework documentation)
|
||||
+ if (size == 0) {
|
||||
+ return true;
|
||||
+ } else {
|
||||
+ for (CFIndex i = 0; i < size; ++i) {
|
||||
+ CFDictionaryRef props = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(cfTrustSettings, i));
|
||||
+ if (CFDictionaryContainsKey(props, kSecTrustSettingsPolicy)) {
|
||||
+ if (hasTrustedSslServerPolicy((SecPolicyRef)CFDictionaryGetValue(props, kSecTrustSettingsPolicy), props))
|
||||
+ return true;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
+ qWarning("Error receiving trust for a CA certificate");
|
||||
+ }
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+} // anon namespace
|
||||
+#endif // Q_OS_OSX
|
||||
+
|
||||
+QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
||||
+{
|
||||
+ ensureInitialized();
|
||||
+
|
||||
+ QList<QSslCertificate> systemCerts;
|
||||
+ // SecTrustSettingsCopyCertificates is not defined on iOS.
|
||||
+#ifdef Q_OS_OSX
|
||||
+ QCFType<CFArrayRef> cfCerts;
|
||||
+ // iterate through all enum members, order:
|
||||
+ // kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin, kSecTrustSettingsDomainSystem
|
||||
+ for (int dom = kSecTrustSettingsDomainUser; dom <= kSecTrustSettingsDomainSystem; dom++) {
|
||||
+ OSStatus status = SecTrustSettingsCopyCertificates(dom, &cfCerts);
|
||||
+ if (status == noErr) {
|
||||
+ const CFIndex size = CFArrayGetCount(cfCerts);
|
||||
+ for (CFIndex i = 0; i < size; ++i) {
|
||||
+ SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
|
||||
+ QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
|
||||
+ if (::isCaCertificateTrusted(cfCert, dom)) {
|
||||
+ if (derData == NULL) {
|
||||
+ qWarning("Error retrieving a CA certificate from the system store");
|
||||
+ } else {
|
||||
+ systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+#endif
|
||||
+ return systemCerts;
|
||||
+}
|
||||
+
|
||||
+QT_END_NAMESPACE
|
||||
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
index 7d0fe00..7415e32 100644
|
||||
--- a/src/network/ssl/qsslsocket_openssl.cpp
|
||||
+++ b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
@@ -71,14 +71,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
-#ifdef Q_OS_DARWIN
|
||||
-# include <private/qcore_mac_p.h>
|
||||
-#endif
|
||||
-
|
||||
-#ifdef Q_OS_OSX
|
||||
-# include <Security/Security.h>
|
||||
-#endif
|
||||
-
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
@@ -616,6 +608,7 @@ void QSslSocketPrivate::resetDefaultCiphers()
|
||||
setDefaultCiphers(defaultCiphers);
|
||||
}
|
||||
|
||||
+#ifndef Q_OS_DARWIN // Apple implementation in qsslsocket_mac_shared.cpp
|
||||
QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
||||
{
|
||||
ensureInitialized();
|
||||
@@ -624,25 +617,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
||||
timer.start();
|
||||
#endif
|
||||
QList<QSslCertificate> systemCerts;
|
||||
- // note: also check implementation in openssl_mac.cpp
|
||||
-#if defined(Q_OS_OSX)
|
||||
- // SecTrustSettingsCopyCertificates is not defined on iOS.
|
||||
- QCFType<CFArrayRef> cfCerts;
|
||||
-
|
||||
- OSStatus status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainSystem, &cfCerts);
|
||||
- if (status == noErr ) {
|
||||
- const CFIndex size = CFArrayGetCount(cfCerts);
|
||||
- for (CFIndex i = 0; i < size; ++i) {
|
||||
- SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
|
||||
- QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
|
||||
- if (derData == NULL) {
|
||||
- qWarning("error retrieving a CA certificate from the system store");
|
||||
- } else {
|
||||
- systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-#elif defined(Q_OS_WIN)
|
||||
+#if defined(Q_OS_WIN)
|
||||
if (ptrCertOpenSystemStoreW && ptrCertFindCertificateInStore && ptrCertCloseStore) {
|
||||
HCERTSTORE hSystemStore;
|
||||
#if defined(Q_OS_WINCE)
|
||||
@@ -719,6 +694,7 @@ QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
||||
|
||||
return systemCerts;
|
||||
}
|
||||
+#endif // Q_OS_DARWIN
|
||||
|
||||
void QSslSocketBackendPrivate::startClientEncryption()
|
||||
{
|
||||
diff --git a/src/network/ssl/ssl.pri b/src/network/ssl/ssl.pri
|
||||
index 384e149..9546f18 100644
|
||||
--- a/src/network/ssl/ssl.pri
|
||||
+++ b/src/network/ssl/ssl.pri
|
||||
@@ -45,7 +45,9 @@ contains(QT_CONFIG, openssl) | contains(QT_CONFIG, openssl-linked) {
|
||||
ssl/qsslsocket_openssl.cpp \
|
||||
ssl/qsslsocket_openssl_symbols.cpp
|
||||
|
||||
-android:!android-no-sdk: SOURCES += ssl/qsslsocket_openssl_android.cpp
|
||||
+ darwin:SOURCES += ssl/qsslsocket_mac_shared.cpp
|
||||
+
|
||||
+ android:!android-no-sdk: SOURCES += ssl/qsslsocket_openssl_android.cpp
|
||||
|
||||
# Add optional SSL libs
|
||||
# Static linking of OpenSSL with msvc:
|
||||
--
|
||||
1.9.1
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
From ae9d3f4c6c1a732788cd1f24c6a928cee16c3991 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Tue, 27 Jan 2015 16:58:32 +0100
|
||||
Subject: [PATCH] Win32: Re-init system proxy if internet settings change
|
||||
|
||||
Because Proxy Auto Configuration performs DNS lookups,
|
||||
the proxy settings are being cached. For long-running
|
||||
programs this means that once users switch e.g. from or
|
||||
to company networks with a proxy, they instantly will
|
||||
lose connectivity because we cache the old setting.
|
||||
|
||||
To remedy this, we monitor the Registry (locations
|
||||
courtesy of Chromium's platform support) for changes
|
||||
in its settings, and requery for the current proxy in
|
||||
that case.
|
||||
|
||||
Task-number: QTBUG-3470
|
||||
Task-number: QTBUG-29990
|
||||
Change-Id: Id25a51387bcd232c5f879cea0371038986d0e2de
|
||||
Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
|
||||
---
|
||||
src/network/kernel/qnetworkproxy_win.cpp | 86 +++++++++++++++++++++++++++++++-
|
||||
1 file changed, 84 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/network/kernel/qnetworkproxy_win.cpp b/src/network/kernel/qnetworkproxy_win.cpp
|
||||
index da2c020..f7741ce 100644
|
||||
--- a/src/network/kernel/qnetworkproxy_win.cpp
|
||||
+++ b/src/network/kernel/qnetworkproxy_win.cpp
|
||||
@@ -345,12 +345,66 @@ static QList<QNetworkProxy> parseServerList(const QNetworkProxyQuery &query, con
|
||||
return removeDuplicateProxies(result);
|
||||
}
|
||||
|
||||
+#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
|
||||
+namespace {
|
||||
+class QRegistryWatcher {
|
||||
+public:
|
||||
+ void addLocation(HKEY hive, const QString& path)
|
||||
+ {
|
||||
+ HKEY openedKey;
|
||||
+ if (RegOpenKeyEx(hive, reinterpret_cast<const wchar_t*>(path.utf16()), 0, KEY_READ, &openedKey) != ERROR_SUCCESS)
|
||||
+ return;
|
||||
+
|
||||
+ const DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES |
|
||||
+ REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY;
|
||||
+
|
||||
+ // Watch the registry key for a change of value.
|
||||
+ HANDLE handle = CreateEvent(NULL, true, false, NULL);
|
||||
+ if (RegNotifyChangeKeyValue(openedKey, true, filter, handle, true) != ERROR_SUCCESS) {
|
||||
+ CloseHandle(handle);
|
||||
+ return;
|
||||
+ }
|
||||
+ m_watchEvents.append(handle);
|
||||
+ m_registryHandles.append(openedKey);
|
||||
+ }
|
||||
+
|
||||
+ bool hasChanged() const {
|
||||
+ return !isEmpty() &&
|
||||
+ WaitForMultipleObjects(m_watchEvents.size(), m_watchEvents.data(), false, 0) < WAIT_OBJECT_0 + m_watchEvents.size();
|
||||
+ }
|
||||
+
|
||||
+ bool isEmpty() const {
|
||||
+ return m_watchEvents.isEmpty();
|
||||
+ }
|
||||
+
|
||||
+ void clear() {
|
||||
+ foreach (HANDLE event, m_watchEvents)
|
||||
+ CloseHandle(event);
|
||||
+ foreach (HKEY key, m_registryHandles)
|
||||
+ RegCloseKey(key);
|
||||
+
|
||||
+ m_watchEvents.clear();
|
||||
+ m_registryHandles.clear();
|
||||
+ }
|
||||
+
|
||||
+ ~QRegistryWatcher() {
|
||||
+ clear();
|
||||
+ }
|
||||
+
|
||||
+private:
|
||||
+ QVector<HANDLE> m_watchEvents;
|
||||
+ QVector<HKEY> m_registryHandles;
|
||||
+};
|
||||
+} // namespace
|
||||
+#endif // !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
|
||||
+
|
||||
class QWindowsSystemProxy
|
||||
{
|
||||
public:
|
||||
QWindowsSystemProxy();
|
||||
~QWindowsSystemProxy();
|
||||
void init();
|
||||
+ void reset();
|
||||
|
||||
QMutex mutex;
|
||||
|
||||
@@ -361,7 +415,9 @@ public:
|
||||
QStringList proxyServerList;
|
||||
QStringList proxyBypass;
|
||||
QList<QNetworkProxy> defaultResult;
|
||||
-
|
||||
+#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
|
||||
+ QRegistryWatcher proxySettingsWatcher;
|
||||
+#endif
|
||||
bool initialized;
|
||||
bool functional;
|
||||
bool isAutoConfig;
|
||||
@@ -381,16 +437,42 @@ QWindowsSystemProxy::~QWindowsSystemProxy()
|
||||
ptrWinHttpCloseHandle(hHttpSession);
|
||||
}
|
||||
|
||||
+void QWindowsSystemProxy::reset()
|
||||
+{
|
||||
+ autoConfigUrl.clear();
|
||||
+ proxyServerList.clear();
|
||||
+ proxyBypass.clear();
|
||||
+ defaultResult.clear();
|
||||
+ defaultResult << QNetworkProxy::NoProxy;
|
||||
+ functional = false;
|
||||
+ isAutoConfig = false;
|
||||
+}
|
||||
+
|
||||
void QWindowsSystemProxy::init()
|
||||
{
|
||||
- if (initialized)
|
||||
+ bool proxySettingsChanged = false;
|
||||
+#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
|
||||
+ proxySettingsChanged = proxySettingsWatcher.hasChanged();
|
||||
+#endif
|
||||
+
|
||||
+ if (initialized && !proxySettingsChanged)
|
||||
return;
|
||||
initialized = true;
|
||||
|
||||
+ reset();
|
||||
+
|
||||
#ifdef Q_OS_WINCE
|
||||
// Windows CE does not have any of the following API
|
||||
return;
|
||||
#else
|
||||
+
|
||||
+#if !defined(Q_OS_WINCE) && !defined(Q_OS_WINRT)
|
||||
+ proxySettingsWatcher.clear(); // needs reset to trigger a new detection
|
||||
+ proxySettingsWatcher.addLocation(HKEY_CURRENT_USER, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
|
||||
+ proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
|
||||
+ proxySettingsWatcher.addLocation(HKEY_LOCAL_MACHINE, QStringLiteral("Software\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"));
|
||||
+#endif
|
||||
+
|
||||
// load the winhttp.dll library
|
||||
QSystemLibrary lib(L"winhttp");
|
||||
if (!lib.load())
|
||||
--
|
||||
1.9.1
|
||||
@@ -0,0 +1,32 @@
|
||||
From c1a67e7dc3a6f8876efa32cdbabbfde1c5a37bc6 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Molkentin <daniel@molkentin.de>
|
||||
Date: Tue, 31 Mar 2015 17:43:44 +0200
|
||||
Subject: [PATCH] Windows: Do not crash if SSL context is gone after root cert
|
||||
lookup
|
||||
|
||||
On Windows, we perform an extra certificate lookup for root CAs that
|
||||
are not in Windows' (minimal) root store. This check can take up to
|
||||
15 seconds. The SSL context can already be gone once we return. Hence
|
||||
we now check for a non-null SSL context on Windows before proceeding.
|
||||
|
||||
Change-Id: I1951569d9b17da33fa604f7c9d8b33255acf200d
|
||||
Reviewed-by: Richard J. Moore <rich@kde.org>
|
||||
---
|
||||
src/network/ssl/qsslsocket_openssl.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/network/ssl/qsslsocket_openssl.cpp b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
index 0e1a3e5..b132aec 100644
|
||||
--- a/src/network/ssl/qsslsocket_openssl.cpp
|
||||
+++ b/src/network/ssl/qsslsocket_openssl.cpp
|
||||
@@ -1281,7 +1281,7 @@ void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertifi
|
||||
if (plainSocket)
|
||||
plainSocket->resume();
|
||||
paused = false;
|
||||
- if (checkSslErrors())
|
||||
+ if (checkSslErrors() && ssl)
|
||||
continueHandshake();
|
||||
}
|
||||
|
||||
--
|
||||
1.9.1
|
||||
@@ -0,0 +1,46 @@
|
||||
## 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
|
||||
have been sent upstream and are part of newer Qt releases.
|
||||
|
||||
All changes are designed to up upstream, and all those that are
|
||||
special hacks to Qt will bear a NOUPSTREAM in their name
|
||||
|
||||
The git-style numeration is ordered by order of creation, their
|
||||
purpose is outlined in each patches' front matter.
|
||||
|
||||
### Part of Qt v5.4.1 and later
|
||||
* 0001-Fix-crash-on-Mac-OS-if-PAC-URL-contains-non-URL-lega.patch
|
||||
* 0002-Fix-possible-crash-when-passing-an-invalid-PAC-URL.patch
|
||||
* 0003-Fix-crash-if-PAC-script-retrieval-returns-a-null-CFD.patch
|
||||
|
||||
### Part of Qt v5.4.2 and later
|
||||
* 0004-Cocoa-Fix-systray-SVG-icons.patch
|
||||
* 0005-OSX-Fix-disapearing-tray-icon.patch
|
||||
* 0007-QNAM-Fix-upload-corruptions-when-server-closes-conne.patch
|
||||
* 0018-Windows-Do-not-crash-if-SSL-context-is-gone-after-ro.patch
|
||||
|
||||
### Part of Qt v5.5.0 and later
|
||||
* 0017-Win32-Re-init-system-proxy-if-internet-settings-chan.patch
|
||||
|
||||
### Part of Qt v5.5.1 and later
|
||||
* 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
|
||||
|
||||
### Upstreamed but not in any release yet (as of 2015-11-16)
|
||||
* 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
|
||||
|
||||
### Not submitted 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.
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ StrCpy $PageReinstall_NEW_Field_3 "Nicht entfernen"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Bereits installiert"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Wählen Sie die Methode, mit der sie ${APPLICATION_NAME} installieren wollen."
|
||||
StrCpy $PageReinstall_OLD_Field_1 "Eine neuere Version von ${APPLICATION_NAME} ist bereits installiert! Es wird nicht empfohlen, eine ältere Version zu installieren. Wollen Sie dies trotzdem tun, so sollten Sie die aktuelle Version zunächst entfernen. Wählen Sie eine Vorgehensweise und wählen dann $\"Weiter$\"."
|
||||
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} ist bereits installiert. $\nWählen Sie eine Vorgehensweise und klicken Sie auf $\"Weiter$\"."
|
||||
StrCpy $PageReinstall_SAME_Field_2 "Komponenten hinzufügen"
|
||||
StrCpy $PageReinstall_SAME_Field_3 "${APPLICATION_NAME} entfernen"
|
||||
StrCpy $UNINSTALLER_APPDATA_TITLE "${APPLICATION_NAME} entfernen"
|
||||
@@ -39,5 +40,4 @@ StrCpy $INIT_INSTALLER_RUNNING "Das Installationsprogramm wird bereits ausgef
|
||||
StrCpy $UAC_UNINSTALLER_REQUIRE_ADMIN "Das Deinstallationsprogramm erfordert Administrator-Rechte. Bitte erneut versuchen."
|
||||
StrCpy $INIT_UNINSTALLER_RUNNING "Das Deinstallationsprogramm wird bereits ausgeführt."
|
||||
StrCpy $SectionGroup_Shortcuts "Verknüpfungen"
|
||||
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} is already installed.$\r$\nSelect the operation you want to perform and click Next to continue."
|
||||
StrCpy $UAC_ERROR_LOGON_SERVICE "Logon service is not running, aborting!"
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
# Auto-generated - do not modify
|
||||
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Kiadási jegyzetek megtekintése"
|
||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "A következő folyamatot(okat) meg kell állítani ${APPLICATION_EXECUTABLE}.$\nSzeretné ha a telepítő program megállítani ezeket a folyamatokat?"
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Folyamat ${APPLICATION_EXECUTABLE} kilövése."
|
||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "Kilövésre szánt folyamat nem található."
|
||||
StrCpy $PageReinstall_NEW_Field_1 "Az ${APPLICATION_NAME} alkalmazás egy régebbi verziója telepítva van a rendszeren. Ajánlott a régi alkalmazás eltávolítása mielőtt a legfrissebb verziót telepítané. Válassza ki milyen műveletet szeretne végrehajtani, és nyomja meg a $\"Következő$\" gombot a folytatáshoz."
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "${APPLICATION_EXECUTABLE} folyamat kilövése."
|
||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "A kilövésre szánt folyamat nem található!"
|
||||
StrCpy $PageReinstall_NEW_Field_1 "Az ${APPLICATION_NAME} alkalmazás egy régebbi verziója telepítve van a rendszeren. Ajánlott a régi alkalmazás eltávolítása mielőtt a legfrissebb verziót telepítené. Válassza ki milyen műveletet szeretne végrehajtani, és nyomja meg a $\"Következő$\" gombot a folytatáshoz."
|
||||
StrCpy $PageReinstall_NEW_Field_2 "Eltávolítás telepítés előtt"
|
||||
StrCpy $PageReinstall_NEW_Field_3 "Ne távolítsa el"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Már telepítve"
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Válaszd ki, hogy szeretnéd telepíteni a következő alkalmazást ${APPLICATION_NAME}."
|
||||
StrCpy $PageReinstall_OLD_Field_1 "Az ${APPLICATION_NAME} alklamazás egy újabb verziója már megtalálható a rendszeren. Nem ajánlott egy régebbi verzió telepítése. Ha valóban szeretné a régebbi verziót telepíteni, akkor ajánlott a jelenleg telepített verzió eltávolítása. Válassza ki milyen műveletet szeretne végrehajtani, és nyomja meg a $\"Következő$\" gombot a folytatáshoz."
|
||||
StrCpy $PageReinstall_OLD_Field_1 "Az ${APPLICATION_NAME} alkalmazás egy újabb verziója már megtalálható a rendszeren. Nem ajánlott egy régebbi verzió telepítése. Ha valóban szeretné a régebbi verziót telepíteni, akkor ajánlott a jelenleg telepített verzió eltávolítása. Válassza ki milyen műveletet szeretne végrehajtani, és nyomja meg a $\"Következő$\" gombot a folytatáshoz."
|
||||
StrCpy $PageReinstall_SAME_Field_1 "Az ${APPLICATION_NAME} alkalmazás ${VERSION} verziója már telepítve van.$↩$\nKérjük válaszd ki milyen műveletet szeretnél végrehajtani, és nyomd meg a „Következő” gombot."
|
||||
StrCpy $PageReinstall_SAME_Field_2 "Komponens hozzáadása/újratelepítése"
|
||||
StrCpy $PageReinstall_SAME_Field_3 "${APPLICATION_NAME} eltávolítása"
|
||||
StrCpy $UNINSTALLER_APPDATA_TITLE "${APPLICATION_NAME} eltávolítása"
|
||||
StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Kérem válassza ki milyen karbantartási műveletet szeretne elvégezni?"
|
||||
StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Válassza ki milyen karbantartási műveletet szeretne elvégezni."
|
||||
StrCpy $SEC_APPLICATION_DETAILS "Az ${APPLICATION_NAME} alkalmazás lényeges komponenseinek telepítése."
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Windows Explorer Integráció"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Windows Explorer Integráció Telepítése"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Start Menü Parancsikonok"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Windows Explorer integráció"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Windows Explorer integráció telepítése"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Start Menü parancsikonok"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "A ${APPLICATION_NAME} parancsikon hozzáadása a Start Menühöz"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Asztali Parancsikon"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Asztali Parancsikon Létrehozása"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Gyorsindítás Eszköztár Parancsikon"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Gyorsindítás Eszköztár Parancsikon Létrehozása"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Asztali parancsikon"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Asztali parancsikon létrehozása"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Gyorsindító eszköztár parancsikon"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Gyorsindító eszköztár parancsikon létrehozása"
|
||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} lényeges komponensek."
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "${APPLICATION_NAME} parancsikon"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Asztali parancsikon a ${APPLICATION_NAME} alkalmazásnak."
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Gyorsindítás eszköztár parancsikon a ${APPLICATION_NAME} alkalmazásnak."
|
||||
StrCpy $UNINSTALLER_FILE_Detail "Program Eltávolító Írása"
|
||||
StrCpy $UNINSTALLER_FILE_Detail "Elltávolító írása"
|
||||
StrCpy $UNINSTALLER_REGISTRY_Detail "Telepítési registry kulcsok írása"
|
||||
StrCpy $UNINSTALLER_FINISHED_Detail "Befejezve"
|
||||
StrCpy $UNINSTALLER_FINISHED_Detail "Befejezve!"
|
||||
StrCpy $UNINSTALL_MESSAGEBOX "Nem sikerült az ${APPLICATION_NAME} alkalmazás telepítése a '$INSTDIR' könyvtárba.$\n$\nSzeretné mindenképpen folytatni (nem ajánlott)?"
|
||||
StrCpy $UNINSTALL_ABORT "Az eltávolítást egy felhasználó megszakította"
|
||||
StrCpy $INIT_NO_QUICK_LAUNCH "Gyorsindító Hivatkozás (N/A)"
|
||||
StrCpy $INIT_NO_DESKTOP "Asztali Hivatkozás (felülírja a meglévőt)"
|
||||
StrCpy $INIT_NO_QUICK_LAUNCH "Gyorsindító hivatkozás (N/A)"
|
||||
StrCpy $INIT_NO_DESKTOP "Asztali hivatkozás (felülírja a meglévőt)"
|
||||
StrCpy $UAC_ERROR_ELEVATE "Nem sikerült felemelni, hiba:"
|
||||
StrCpy $UAC_INSTALLER_REQUIRE_ADMIN "A telepítő futtatásához adminisztrátori hozzáférés szükséges, próbáld újra."
|
||||
StrCpy $INIT_INSTALLER_RUNNING "A telepítő már fut."
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Auto-generated - do not modify
|
||||
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Mostrar notas de la versión"
|
||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "Se encontrarion ${APPLICATION_EXECUTABLE} proceso(s) que debe/n ser detenidos.$\"$\\n$\"¿Quiere que el instalador lo haga por usted?"
|
||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Parando el proceso ${APPLICATION_EXECUTABLE}."
|
||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "Proceso a detener no encontrado!"
|
||||
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} esta instalada en el sistema. Es recomendado que quite esta versión antes de instalar. Elija la operación a realizar y seleccione Siguiente para continuar."
|
||||
@@ -8,36 +9,35 @@ StrCpy $PageReinstall_NEW_Field_3 "No des-instalar."
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Actualmente Instalado."
|
||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Elija como desea instalar ${APPLICATION_NAME}."
|
||||
StrCpy $PageReinstall_OLD_Field_1 "Una versión mas reciente de ${APPLICATION_NAME} esta actualmente instalada! No es recomendado que instale una versión antigua. Si realmente desea instalar esta versión obsoleta, es mejor que des-instale la versión actual primero. Seleccione la operación que desea realizar y presione en Siguiente para continuar. "
|
||||
StrCpy $PageReinstall_SAME_Field_1 "La ${APPLICATION_NAME} ${VERSION} ya está instalado.$\n$\nSeleccione la operación que desea realizar y haga click en Siguiente para continuar."
|
||||
StrCpy $PageReinstall_SAME_Field_2 "Agregar/Re-Instalar componentes"
|
||||
StrCpy $PageReinstall_SAME_Field_3 "Des-instalar ${APPLICATION_NAME}"
|
||||
StrCpy $UNINSTALLER_APPDATA_TITLE "Des-instalar ${APPLICATION_NAME}"
|
||||
StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Elija la opción de mantenimiento a realizar."
|
||||
StrCpy $SEC_APPLICATION_DETAILS "Instalar esenciales ${APPLICATION_NAME}."
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integración para Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Instalando la integración para Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Acceso Directo en Menú de Programas"
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "Agregando el Acceso Directo al Menú de Inicio para ${APPLICATION_NAME}."
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Acceso directo en Escritorio"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Creando Accesos Directos en Escritorio"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atajo de Acceso Rápido"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Creando Atajo de Acceso Rápido"
|
||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} esencial."
|
||||
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Acceso directo de ${APPLICATION_NAME}"
|
||||
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Acceso Directo al Escritorio para ${APPLICATION_NAME}."
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Atajo de Acceso Rápido para ${APPLICATION_NAME}."
|
||||
StrCpy $UNINSTALLER_FILE_Detail "Escribiendo Des-Instalador."
|
||||
StrCpy $UNINSTALLER_REGISTRY_Detail "Escribiendo claves de Registro del Instalador"
|
||||
StrCpy $UNINSTALLER_FINISHED_Detail "Terminado"
|
||||
StrCpy $UNINSTALL_MESSAGEBOX "Parece que ${APPLICATION_NAME} no esta instalado en el directorio '$INSTDIR'.$\n$\n¿Continuar de todos modos? (No recomendado)"
|
||||
StrCpy $UNINSTALL_ABORT "Des-instalación abortada por el usuario"
|
||||
StrCpy $INIT_NO_QUICK_LAUNCH "Atajo de Acceso Rápido (N/A)"
|
||||
StrCpy $INIT_NO_DESKTOP "Acceso Directo en Escritorio (Sobrescribe existentes)"
|
||||
StrCpy $UAC_ERROR_ELEVATE "No se ha podido elevar, error:"
|
||||
StrCpy $UAC_INSTALLER_REQUIRE_ADMIN "Este instalador requiere acceso Administrador, intente de nuevo. "
|
||||
StrCpy $INIT_INSTALLER_RUNNING "El instalador ya esta corriendo."
|
||||
StrCpy $UAC_UNINSTALLER_REQUIRE_ADMIN "Este des-instalador requiere acceso administrador, intente de nuevo"
|
||||
StrCpy $UAC_ERROR_LOGON_SERVICE "Servicio Inicio de sesión no se está ejecutando, abortando!"
|
||||
StrCpy $INIT_UNINSTALLER_RUNNING "El des-instalador ya esta corriendo"
|
||||
StrCpy $SectionGroup_Shortcuts "Accesos Directos"
|
||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "Found ${APPLICATION_EXECUTABLE} process(s) which need to be stopped.$\nDo you want the installer to stop these for you?"
|
||||
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} is already installed.$\r$\nSelect the operation you want to perform and click Next to continue."
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integration for Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Installing Integration for Windows Explorer"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Quick Launch Shortcut"
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Creating Quick Launch Shortcut"
|
||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} essentials."
|
||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Quick Launch shortcut for ${APPLICATION_NAME}."
|
||||
StrCpy $UNINSTALL_MESSAGEBOX "It does not appear that ${APPLICATION_NAME} is installed in the directory '$INSTDIR'.$\r$\nContinue anyway (not recommended)?"
|
||||
StrCpy $INIT_NO_QUICK_LAUNCH "Quick Launch Shortcut (N/A)"
|
||||
StrCpy $UAC_ERROR_ELEVATE "Unable to elevate, error:"
|
||||
StrCpy $UAC_ERROR_LOGON_SERVICE "Logon service is not running, aborting!"
|
||||
|
||||
@@ -41,10 +41,6 @@ if (UNIX)
|
||||
"${LIB_INSTALL_DIR}"
|
||||
CACHE PATH "The subdirectory relative to the install prefix where private libs are installed"
|
||||
)
|
||||
SET(PLUGIN_INSTALL_DIR
|
||||
"${LIB_INSTALL_DIR}"
|
||||
CACHE PATH "The subdirectory relative to the install prefix where plugins will be installed (default is prefix/lib/${APPLICATION_SHORTNAME})"
|
||||
)
|
||||
SET(INCLUDE_INSTALL_DIR
|
||||
"${CMAKE_INSTALL_PREFIX}/include"
|
||||
CACHE PATH "The subdirectory to the header prefix (default prefix/include)"
|
||||
@@ -106,7 +102,6 @@ if (WIN32)
|
||||
set(SBIN_INSTALL_DIR "." CACHE PATH "-")
|
||||
set(LIB_INSTALL_DIR "lib" CACHE PATH "-")
|
||||
set(INCLUDE_INSTALL_DIR "include" CACHE PATH "-")
|
||||
set(PLUGIN_INSTALL_DIR "plugins" CACHE PATH "-")
|
||||
set(HTML_INSTALL_DIR "doc/HTML" CACHE PATH "-")
|
||||
set(ICON_INSTALL_DIR "." CACHE PATH "-")
|
||||
set(SOUND_INSTALL_DIR "." CACHE PATH "-")
|
||||
|
||||
@@ -21,10 +21,6 @@ if( Qt5Core_FOUND )
|
||||
if(NOT TOKEN_AUTH_ONLY)
|
||||
find_package(Qt5WebKitWidgets REQUIRED)
|
||||
find_package(Qt5WebKit REQUIRED)
|
||||
find_package(Qt5PrintSupport REQUIRED)
|
||||
if(NOT APPLE)
|
||||
find_package(Qt5Quick REQUIRED) # only needed on Windows because of OBS dependencies(?)
|
||||
endif()
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
if(APPLE)
|
||||
find_package(Qt5MacExtras REQUIRED)
|
||||
@@ -76,11 +72,16 @@ endif()
|
||||
endmacro()
|
||||
|
||||
if(NOT TOKEN_AUTH_ONLY)
|
||||
find_package(Qt5LinguistTools REQUIRED)
|
||||
macro(qt_add_translation)
|
||||
qt5_add_translation(${ARGN})
|
||||
endmacro()
|
||||
else()
|
||||
find_package(Qt5LinguistTools)
|
||||
if(Qt5LinguistTools_FOUND)
|
||||
macro(qt_add_translation)
|
||||
qt5_add_translation(${ARGN})
|
||||
endmacro()
|
||||
else()
|
||||
macro(qt_add_translation)
|
||||
endmacro()
|
||||
endif()
|
||||
else()
|
||||
macro(qt_add_translation)
|
||||
endmacro()
|
||||
endif()
|
||||
|
||||
@@ -9,7 +9,6 @@ set(PACKAGE ${APPLICATION_NAME})
|
||||
set(VERSION ${APPLICATION_VERSION})
|
||||
set(DATADIR ${DATA_INSTALL_DIR})
|
||||
set(LIBDIR ${LIB_INSTALL_DIR})
|
||||
set(PLUGINDIR "${PLUGIN_INSTALL_DIR}-${LIBRARY_SOVERSION}")
|
||||
set(SYSCONFDIR ${SYSCONF_INSTALL_DIR})
|
||||
|
||||
set(BINARYDIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#cmakedefine VERSION "${APPLICATION_VERSION}"
|
||||
#cmakedefine LOCALEDIR "${LOCALE_INSTALL_DIR}"
|
||||
#cmakedefine LIBDIR "${LIBDIR}"
|
||||
#cmakedefine PLUGINDIR "${PLUGINDIR}"
|
||||
#cmakedefine SYSCONFDIR "${SYSCONFDIR}"
|
||||
#cmakedefine BINARYDIR "${BINARYDIR}"
|
||||
#cmakedefine SOURCEDIR "${SOURCEDIR}"
|
||||
|
||||
@@ -421,6 +421,8 @@ static int _csync_treewalk_visitor(void *obj, void *data) {
|
||||
trav.error_status = cur->error_status;
|
||||
trav.should_update_metadata = cur->should_update_metadata;
|
||||
trav.has_ignored_files = cur->has_ignored_files;
|
||||
trav.checksum = cur->checksum;
|
||||
trav.checksumTypeId = cur->checksumTypeId;
|
||||
|
||||
if( other_node ) {
|
||||
csync_file_stat_t *other_stat = (csync_file_stat_t*)other_node->data;
|
||||
@@ -742,6 +744,7 @@ void csync_file_stat_free(csync_file_stat_t *st)
|
||||
SAFE_FREE(st->directDownloadCookies);
|
||||
SAFE_FREE(st->etag);
|
||||
SAFE_FREE(st->destpath);
|
||||
SAFE_FREE(st->checksum);
|
||||
SAFE_FREE(st);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,8 @@ enum csync_status_codes_e {
|
||||
CYSNC_STATUS_FILE_LOCKED_OR_OPEN,
|
||||
CSYNC_STATUS_INDIVIDUAL_EXCLUDE_HIDDEN,
|
||||
CSYNC_STATUS_INVALID_CHARACTERS,
|
||||
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED
|
||||
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED,
|
||||
CSYNC_STATUS_FORBIDDEN
|
||||
};
|
||||
|
||||
typedef enum csync_status_codes_e CSYNC_STATUS;
|
||||
@@ -261,6 +262,10 @@ struct csync_tree_walk_file_s {
|
||||
const char *remotePerm;
|
||||
char *directDownloadUrl;
|
||||
char *directDownloadCookies;
|
||||
|
||||
const char *checksum;
|
||||
uint32_t checksumTypeId;
|
||||
|
||||
struct {
|
||||
int64_t size;
|
||||
time_t modtime;
|
||||
@@ -300,6 +305,10 @@ typedef void (*csync_vio_closedir_hook) (csync_vio_handle_t *dhhandle,
|
||||
typedef int (*csync_vio_stat_hook) (csync_vio_handle_t *dhhandle,
|
||||
void *userdata);
|
||||
|
||||
/* Compute the checksum of the given \a checksumTypeId for \a path. */
|
||||
typedef const char* (*csync_checksum_hook) (
|
||||
const char *path, uint32_t checksumTypeId, void *userdata);
|
||||
|
||||
/**
|
||||
* @brief Allocate a csync context.
|
||||
*
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#define ERRNO_SERVICE_UNAVAILABLE CSYNC_CUSTOM_ERRNO_BASE+14
|
||||
#define ERRNO_USER_ABORT CSYNC_CUSTOM_ERRNO_BASE+16
|
||||
#define ERRNO_STORAGE_UNAVAILABLE CSYNC_CUSTOM_ERRNO_BASE+17
|
||||
#define ERRNO_FORBIDDEN CSYNC_CUSTOM_ERRNO_BASE+18
|
||||
|
||||
#endif /* _CSYNC_MACROS_H */
|
||||
/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */
|
||||
|
||||
@@ -96,6 +96,11 @@ struct csync_s {
|
||||
csync_vio_readdir_hook remote_readdir_hook;
|
||||
csync_vio_closedir_hook remote_closedir_hook;
|
||||
void *vio_userdata;
|
||||
|
||||
/* hook for comparing checksums of files during discovery */
|
||||
csync_checksum_hook checksum_hook;
|
||||
void *checksum_userdata;
|
||||
|
||||
} callbacks;
|
||||
c_strlist_t *excludes;
|
||||
|
||||
@@ -192,6 +197,9 @@ struct csync_file_stat_s {
|
||||
char *directDownloadCookies;
|
||||
char remotePerm[REMOTE_PERM_BUF_SIZE+1];
|
||||
|
||||
const char *checksum;
|
||||
uint32_t checksumTypeId;
|
||||
|
||||
CSYNC_STATUS error_status;
|
||||
|
||||
enum csync_instructions_e instruction; /* u32 */
|
||||
|
||||
@@ -226,6 +226,8 @@ int csync_statedb_close(CSYNC *ctx) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define METADATA_COLUMNS "phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote, contentChecksum, contentChecksumTypeId"
|
||||
|
||||
// This funciton parses a line from the metadata table into the given csync_file_stat
|
||||
// structure which it is also allocating.
|
||||
// Note that this function calls laso sqlite3_step to actually get the info from db and
|
||||
@@ -286,6 +288,11 @@ static int _csync_file_stat_from_metadata_table( csync_file_stat_t **st, sqlite3
|
||||
if(column_count > 13) {
|
||||
(*st)->has_ignored_files = sqlite3_column_int(stmt, 13);
|
||||
}
|
||||
if(column_count > 15 && sqlite3_column_int(stmt, 15)) {
|
||||
(*st)->checksum = c_strdup( (char*) sqlite3_column_text(stmt, 14));
|
||||
(*st)->checksumTypeId = sqlite3_column_int(stmt, 15);
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if( rc != SQLITE_DONE ) {
|
||||
@@ -307,7 +314,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_hash(CSYNC *ctx,
|
||||
}
|
||||
|
||||
if( ctx->statedb.by_hash_stmt == NULL ) {
|
||||
const char *hash_query = "SELECT * FROM metadata WHERE phash=?1";
|
||||
const char *hash_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE phash=?1";
|
||||
|
||||
SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, hash_query, strlen(hash_query), &ctx->statedb.by_hash_stmt, NULL));
|
||||
ctx->statedb.lastReturnValue = rc;
|
||||
@@ -350,7 +357,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_file_id(CSYNC *ctx,
|
||||
}
|
||||
|
||||
if( ctx->statedb.by_fileid_stmt == NULL ) {
|
||||
const char *query = "SELECT * FROM metadata WHERE fileid=?1";
|
||||
const char *query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE fileid=?1";
|
||||
|
||||
SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, query, strlen(query), &ctx->statedb.by_fileid_stmt, NULL));
|
||||
ctx->statedb.lastReturnValue = rc;
|
||||
@@ -390,7 +397,7 @@ csync_file_stat_t *csync_statedb_get_stat_by_inode(CSYNC *ctx,
|
||||
}
|
||||
|
||||
if( ctx->statedb.by_inode_stmt == NULL ) {
|
||||
const char *inode_query = "SELECT * FROM metadata WHERE inode=?1";
|
||||
const char *inode_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE inode=?1";
|
||||
|
||||
SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, inode_query, strlen(inode_query), &ctx->statedb.by_inode_stmt, NULL));
|
||||
ctx->statedb.lastReturnValue = rc;
|
||||
@@ -433,7 +440,7 @@ int csync_statedb_get_below_path( CSYNC *ctx, const char *path ) {
|
||||
* In other words, anything that is between path+'/' and path+'0',
|
||||
* (because '0' follows '/' in ascii)
|
||||
*/
|
||||
const char *below_path_query = "SELECT phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5, fileid, remotePerm, filesize, ignoredChildrenRemote FROM metadata WHERE path > (?||'/') AND path < (?||'0')";
|
||||
const char *below_path_query = "SELECT " METADATA_COLUMNS " FROM metadata WHERE path > (?||'/') AND path < (?||'0')";
|
||||
SQLITE_BUSY_HANDLED(sqlite3_prepare_v2(ctx->statedb.db, below_path_query, -1, &stmt, NULL));
|
||||
ctx->statedb.lastReturnValue = rc;
|
||||
if( rc != SQLITE_OK ) {
|
||||
|
||||
@@ -270,25 +270,32 @@ static int _csync_detect_update(CSYNC *ctx, const char *file,
|
||||
((int64_t) fs->mtime), ((int64_t) tmp->modtime),
|
||||
fs->etag, tmp->etag, (uint64_t) fs->inode, (uint64_t) tmp->inode,
|
||||
(uint64_t) fs->size, (uint64_t) tmp->size, fs->remotePerm, tmp->remotePerm, tmp->has_ignored_files );
|
||||
if((ctx->current == REMOTE_REPLICA && !c_streq(fs->etag, tmp->etag ))
|
||||
|| (ctx->current == LOCAL_REPLICA && (!_csync_mtime_equal(fs->mtime, tmp->modtime)
|
||||
// zero size in statedb can happen during migration
|
||||
|| (tmp->size != 0 && fs->size != tmp->size)
|
||||
#if 0
|
||||
|| fs->inode != tmp->inode
|
||||
#endif
|
||||
))) {
|
||||
/* Comparison of the local inode is disabled because people reported problems
|
||||
* on windows with flacky inode values, see github bug #779
|
||||
*
|
||||
* The inode needs to be observed because:
|
||||
* $> echo a > a.txt ; echo b > b.txt
|
||||
* both files have the same mtime
|
||||
* sync them.
|
||||
* $> rm a.txt && mv b.txt a.txt
|
||||
* makes b.txt appearing as a.txt yet a sync is not performed because
|
||||
* both have the same modtime as mv does not change that.
|
||||
*/
|
||||
if (ctx->current == REMOTE_REPLICA && !c_streq(fs->etag, tmp->etag)) {
|
||||
st->instruction = CSYNC_INSTRUCTION_EVAL;
|
||||
goto out;
|
||||
}
|
||||
if (ctx->current == LOCAL_REPLICA &&
|
||||
(!_csync_mtime_equal(fs->mtime, tmp->modtime)
|
||||
// zero size in statedb can happen during migration
|
||||
|| (tmp->size != 0 && fs->size != tmp->size))) {
|
||||
|
||||
if (fs->size == tmp->size && tmp->checksumTypeId) {
|
||||
if (ctx->callbacks.checksum_hook) {
|
||||
st->checksum = ctx->callbacks.checksum_hook(
|
||||
file, tmp->checksumTypeId,
|
||||
ctx->callbacks.checksum_userdata);
|
||||
}
|
||||
bool checksumIdentical = false;
|
||||
if (st->checksum) {
|
||||
st->checksumTypeId = tmp->checksumTypeId;
|
||||
checksumIdentical = strncmp(st->checksum, tmp->checksum, 1000) == 0;
|
||||
}
|
||||
if (checksumIdentical) {
|
||||
st->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
st->should_update_metadata = true;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
st->instruction = CSYNC_INSTRUCTION_EVAL;
|
||||
goto out;
|
||||
}
|
||||
@@ -573,6 +580,26 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* set the current item to an ignored state.
|
||||
* If the item is set to ignored, the update phase continues, ie. its not a hard error */
|
||||
static bool mark_current_item_ignored( CSYNC *ctx, csync_file_stat_t *previous_fs, CSYNC_STATUS status )
|
||||
{
|
||||
if(!ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->current_fs) {
|
||||
ctx->current_fs->instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
ctx->current_fs->error_status = status;
|
||||
/* If a directory has ignored files, put the flag on the parent directory as well */
|
||||
if( previous_fs ) {
|
||||
previous_fs->has_ignored_files = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* File tree walker */
|
||||
int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
||||
unsigned int depth) {
|
||||
@@ -625,13 +652,8 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
||||
/* permission denied */
|
||||
ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_OPENDIR_ERROR);
|
||||
if (errno == EACCES) {
|
||||
if (ctx->current_fs) {
|
||||
ctx->current_fs->instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
ctx->current_fs->error_status = CSYNC_STATUS_PERMISSION_DENIED;
|
||||
/* If a directory has ignored files, put the flag on the parent directory as well */
|
||||
if( previous_fs ) {
|
||||
previous_fs->has_ignored_files = true;
|
||||
}
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Permission denied.");
|
||||
if (mark_current_item_ignored(ctx, previous_fs, CSYNC_STATUS_PERMISSION_DENIED)) {
|
||||
goto done;
|
||||
}
|
||||
} else if(errno == ENOENT) {
|
||||
@@ -640,19 +662,22 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "asprintf failed!");
|
||||
}
|
||||
}
|
||||
// 403 Forbidden can be sent by the server if the file firewall is active.
|
||||
// A file or directory should be ignored and sync must continue. See #3490
|
||||
else if(errno == ERRNO_FORBIDDEN) {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Directory access Forbidden (File Firewall?)");
|
||||
if( mark_current_item_ignored(ctx, previous_fs, CSYNC_STATUS_FORBIDDEN) ) {
|
||||
goto done;
|
||||
}
|
||||
/* if current_fs is not defined here, better throw an error */
|
||||
}
|
||||
// The server usually replies with the custom "503 Storage not available"
|
||||
// if some path is temporarily unavailable. But in some cases a standard 503
|
||||
// is returned too. Thus we can't distinguish the two and will treat any
|
||||
// 503 as request to ignore the folder. See #3113 #2884.
|
||||
else if(errno == ERRNO_STORAGE_UNAVAILABLE || errno == ERRNO_SERVICE_UNAVAILABLE) {
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Storage was not available!");
|
||||
if (ctx->current_fs) {
|
||||
ctx->current_fs->instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||
ctx->current_fs->error_status = CSYNC_STATUS_STORAGE_UNAVAILABLE;
|
||||
/* If a directory has ignored files, put the flag on the parent directory as well */
|
||||
if( previous_fs ) {
|
||||
previous_fs->has_ignored_files = true;
|
||||
}
|
||||
if( mark_current_item_ignored(ctx, previous_fs, CSYNC_STATUS_STORAGE_UNAVAILABLE ) ) {
|
||||
goto done;
|
||||
}
|
||||
/* if current_fs is not defined here, better throw an error */
|
||||
|
||||
@@ -41,6 +41,12 @@ static void statedb_create_metadata_table(sqlite3 *db)
|
||||
"modtime INTEGER(8),"
|
||||
"type INTEGER,"
|
||||
"md5 VARCHAR(32),"
|
||||
"fileid VARCHAR(128),"
|
||||
"remotePerm VARCHAR(128),"
|
||||
"filesize BIGINT,"
|
||||
"ignoredChildrenRemote INT,"
|
||||
"contentChecksum TEXT,"
|
||||
"contentChecksumTypeId INTEGER,"
|
||||
"PRIMARY KEY(phash));";
|
||||
|
||||
rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
|
||||
|
||||
@@ -66,7 +66,7 @@ our %config;
|
||||
assertLocalDirs assertLocalAndRemoteDir glob_put put_to_dir
|
||||
putToDirLWP localDir remoteDir localCleanup createLocalFile md5OfFile
|
||||
remoteCleanup server initLocalDir initRemoteDir moveRemoteFile
|
||||
printInfo remoteFileId createShare removeShare assert
|
||||
printInfo remoteFileProp remoteFileId createShare removeShare assert
|
||||
configValue testDirUrl getToFileLWP getToFileCurl);
|
||||
|
||||
sub server
|
||||
@@ -679,28 +679,38 @@ sub printInfo($)
|
||||
$infoCnt++;
|
||||
}
|
||||
|
||||
sub remoteFileId($$)
|
||||
sub remoteFileProp($$)
|
||||
{
|
||||
my ($fromDir, $file) = @_;
|
||||
my $fromUrl = testDirUrl() . $fromDir;
|
||||
my $id;
|
||||
my $result;
|
||||
|
||||
if( my $r = $d->propfind( -url => $fromUrl, -depth => 1 ) ) {
|
||||
if ( $r->is_collection ) {
|
||||
# print "Collection\n";
|
||||
|
||||
foreach my $res ( $r->get_resourcelist->get_resources() ) {
|
||||
my $filename = $res->get_property("rel_uri");
|
||||
# print "OOOOOOOOOOOOOO $filename " . $res->get_property('id') . "\n";
|
||||
if( $file eq $filename || $filename eq $file . "/" ) {
|
||||
$id = $res->get_property('id') || "";
|
||||
}
|
||||
my $filename = $res->get_property("rel_uri");
|
||||
# print "OOOOOOOOOOOOOO $filename " . $res->get_property('id') . "\n";
|
||||
if( $file eq $filename || $filename eq $file . "/" ) {
|
||||
$result = $res;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# print "OOOOOOOOOOOOOOOOOOO " . $r->get_property("rel_uri");
|
||||
$id = $r->get_property('id') || "";
|
||||
$result = $r;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub remoteFileId($$)
|
||||
{
|
||||
my ($fromDir, $file) = @_;
|
||||
my $id;
|
||||
if( my $res = remoteFileProp($fromDir, $file) ) {
|
||||
$id = $res->get_property('id') || "";
|
||||
}
|
||||
print "## ID of $file: $id\n";
|
||||
return $id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/perl
|
||||
#
|
||||
# Test script for the ownCloud module of csync.
|
||||
# This script requires a running ownCloud instance accessible via HTTP.
|
||||
# It does quite some fancy tests and asserts the results.
|
||||
#
|
||||
# Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
|
||||
use lib ".";
|
||||
|
||||
use File::Copy;
|
||||
use ownCloud::Test;
|
||||
|
||||
use strict;
|
||||
|
||||
print "Hello, this is t9, a tester for content checksums.\n";
|
||||
|
||||
initTesting();
|
||||
|
||||
printInfo( "Add some files to local");
|
||||
my $locDir = localDir();
|
||||
copy( "testfiles/test.txt", "$locDir/test.txt");
|
||||
copy( "testfiles/test.txt", "$locDir/test.eml");
|
||||
|
||||
csync( );
|
||||
print "\nAssert local and remote dirs.\n";
|
||||
assertLocalAndRemoteDir( '', 0);
|
||||
|
||||
# Get file properties before syncing again
|
||||
my $txtpropbefore = remoteFileProp("", "test.txt");
|
||||
my $emlpropbefore = remoteFileProp("", "test.eml");
|
||||
assert($txtpropbefore);
|
||||
assert($emlpropbefore);
|
||||
|
||||
printInfo( "Touch local files");
|
||||
system( "touch $locDir/test.txt" );
|
||||
system( "touch $locDir/test.eml" );
|
||||
|
||||
csync( );
|
||||
|
||||
# Get file properties afterwards
|
||||
my $txtpropafter = remoteFileProp("", "test.txt");
|
||||
my $emlpropafter = remoteFileProp("", "test.eml");
|
||||
assert($txtpropafter);
|
||||
assert($emlpropafter);
|
||||
|
||||
# The txt file is uploaded normally, etag and mtime differ
|
||||
assert($txtpropafter->get_property( "getetag" ) ne
|
||||
$txtpropbefore->get_property( "getetag" ));
|
||||
assert($txtpropafter->get_property( "getlastmodified" ) ne
|
||||
$txtpropbefore->get_property( "getlastmodified" ));
|
||||
# The eml was not uploaded, nothing differs
|
||||
assert($emlpropafter->get_property( "getetag" ) eq
|
||||
$emlpropbefore->get_property( "getetag" ));
|
||||
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" );
|
||||
|
||||
csync( );
|
||||
|
||||
# Get file properties afterwards
|
||||
my $emlpropchanged = remoteFileProp("", "test.eml");
|
||||
assert($emlpropchanged);
|
||||
assert($emlpropafter->get_property( "getetag" ) ne
|
||||
$emlpropchanged->get_property( "getetag" ));
|
||||
assert($emlpropafter->get_property( "getlastmodified" ) ne
|
||||
$emlpropchanged->get_property( "getlastmodified" ));
|
||||
|
||||
# ==================================================================
|
||||
|
||||
cleanup();
|
||||
|
||||
# --
|
||||
|
||||
@@ -9,12 +9,12 @@ Options
|
||||
.. index:: command line switches, command line, options, parameters
|
||||
.. include:: options.rst
|
||||
|
||||
Config File
|
||||
-----------
|
||||
Configuration File
|
||||
------------------
|
||||
.. index:: config file
|
||||
.. include:: conffile.rst
|
||||
|
||||
ownCloud Commandline Client
|
||||
---------------------------
|
||||
ownCloud Command Line Client
|
||||
----------------------------
|
||||
.. index:: owncloudcmd
|
||||
.. include:: owncloudcmd.rst
|
||||
|
||||
@@ -31,9 +31,6 @@ If an update is available, and has been successfully downloaded, the ownCloud
|
||||
client starts a silent update prior to its next launch and then restarts
|
||||
itself. Should the silent update fail, the client offers a manual download.
|
||||
|
||||
When you upgrade from 1.7 you should restart Windows to ensure that all the new
|
||||
features in 1.8 are enabled.
|
||||
|
||||
.. note:: Administrative privileges are required to perform the update.
|
||||
|
||||
Mac OS X
|
||||
@@ -65,9 +62,15 @@ auto-update mechanism for different operating systems.
|
||||
Preventing Automatic Updates in Windows Environments
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can prevent automatic updates from occuring in Windows environments using
|
||||
one of two methods. The first method allows users to override the automatic
|
||||
update check mechanism whereas the second method prevents any manual overrides.
|
||||
Users may disable automatic updates by adding this line to the [General]
|
||||
section of their ``owncloud.cfg`` files::
|
||||
|
||||
skipUpdateCheck=true
|
||||
|
||||
Windows administrators have more options for preventing automatic updates in
|
||||
Windows environments by using one of two methods. The first method allows users
|
||||
to override the automatic update check mechanism, whereas the second method
|
||||
prevents any manual overrides.
|
||||
|
||||
To prevent automatic updates, but allow manual overrides:
|
||||
|
||||
@@ -125,8 +128,8 @@ Preventing Automatic Updates in Linux Environments
|
||||
|
||||
Because the Linux client does not provide automatic updating functionality, there is no
|
||||
need to remove the automatic-update check. However, if you want to disable it edit your desktop
|
||||
client configuration file, ``$HOME/.local/share/data/ownCloud/owncloud.cfg``. Add these lines:
|
||||
client configuration file, ``$HOME/.local/share/data/ownCloud/owncloud.cfg``.
|
||||
Add these lines::
|
||||
|
||||
[General]
|
||||
skipUpdateCheck=true
|
||||
|
||||
|
||||
|
Antes Largura: | Altura: | Tamanho: 5.5 KiB Depois Largura: | Altura: | Tamanho: 4.3 KiB |
|
Depois Largura: | Altura: | Tamanho: 82 KiB |
|
Depois Largura: | Altura: | Tamanho: 52 KiB |
|
Antes Largura: | Altura: | Tamanho: 52 KiB Depois Largura: | Altura: | Tamanho: 49 KiB |
|
Antes Largura: | Altura: | Tamanho: 4.9 KiB Depois Largura: | Altura: | Tamanho: 99 KiB |
@@ -46,109 +46,106 @@ so you should click it to see what it has to tell you.
|
||||
The red circle with the white "x" indicates a configuration error, such as an
|
||||
incorrect login or server URL.
|
||||
|
||||
Using the Right-Click Menu
|
||||
--------------------------
|
||||
Systray Icon
|
||||
------------
|
||||
|
||||
A right-click on the icon opens a menu for quick access to multiple operations.
|
||||
A right-click on the systray icon opens a menu for quick access to multiple
|
||||
operations.
|
||||
|
||||
.. image:: images/menu.png
|
||||
:alt: the right-click sync client menu
|
||||
|
||||
The Desktop Client menu provides the following options:
|
||||
This menu provides the following options:
|
||||
|
||||
* Open ownCloud in browser
|
||||
* Open folder [your local sync folder]
|
||||
* Up to date
|
||||
* Recent changes
|
||||
* Settings
|
||||
* Help
|
||||
* Log out
|
||||
* Quick access to your accounts
|
||||
* Recent Changes, showing latest activities
|
||||
* Status of your client version (whether it is up to date)
|
||||
* Help menu
|
||||
* An option to log in or log out of all of your accounts at once
|
||||
* Quit ownCloud
|
||||
|
||||
Using the Account Settings Window
|
||||
---------------------------------
|
||||
|
||||
.. index:: account settings, user, password, Server URL
|
||||
|
||||
Click **Settings** in the right-click menu to see a summary of your ownCloud
|
||||
account settings, or left-click your systray icon. This shows which ownCloud
|
||||
account you are connected to (or accounts, if you have more than one) your
|
||||
quota status, and a window for managing your synchronization settings.
|
||||
A left-click on your systray icon opens the desktop client to the account
|
||||
settings window.
|
||||
|
||||
.. image:: images/client6.png
|
||||
:alt: Account settings window
|
||||
|
||||
Configuring ownCloud Account Settings
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. index:: account settings, user, password, Server URL
|
||||
|
||||
At the top of the window are tabs for each configured sync account, and three
|
||||
others for Activity, General and Network settings. On your account tabs you
|
||||
have the following features:
|
||||
|
||||
* Connection status, showing which ownCloud server you are connected to, and
|
||||
your ownCloud username.
|
||||
* A **Remove Account** button, which deletes your account but does not delete
|
||||
your data files.
|
||||
* An **Account** button, which contains a dropdown menu with **Add New**,
|
||||
**Sign In/Sign 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
|
||||
removed synchronization on an account (see **Remove Sync** below).
|
||||
|
||||
The little button with three dots that sits to the right of the sync status bar
|
||||
offers four additional options:
|
||||
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
|
||||
* Pause Sync / Resume Sync
|
||||
* Remove 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. From there you can
|
||||
choose to sync all or only some of the folders in the folder tree.
|
||||
**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.
|
||||
|
||||
**Pause Sync** pauses sync operations for just this folder sync connection
|
||||
without making any changes to your account.
|
||||
**Resume Sync** resumes sync operations for this folder sync connection.
|
||||
**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**.
|
||||
|
||||
**Remove Sync** removes this folder sync connection without removing the
|
||||
account. 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.
|
||||
**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.
|
||||
|
||||
.. image:: images/client-7.png
|
||||
:alt: Extra options for sync operations
|
||||
|
||||
Adding New Accounts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
any time. Use **Account** > **Remove** to delete accounts.
|
||||
|
||||
Activity Window
|
||||
---------------
|
||||
|
||||
The Activity window contains the log of your recent activities, including files
|
||||
downloaded and deleted, and which local folders your files went into.
|
||||
downloaded and deleted, which local folders your files went into, and files not
|
||||
synced.
|
||||
|
||||
.. image:: images/client-8.png
|
||||
:alt: Activity windows logs all server and client activities.
|
||||
|
||||
General Window
|
||||
--------------
|
||||
|
||||
The General window has configuration options such as **Launch on System
|
||||
Startup**, **Use Monochrome Icons**, and **Show Desktop Notifications**. This
|
||||
is where you will find the **Edit Ignored Files** button, to launch the ignored
|
||||
files editor, and two new features: **Ask confirmation before downloading
|
||||
folders larger than [folder size]**, and **Add an Account**.
|
||||
files editor, and **Ask confirmation before downloading
|
||||
folders larger than [folder size]**.
|
||||
|
||||
Multi-Account Support
|
||||
---------------------
|
||||
|
||||
You may now configure multiple ownCloud accounts in your desktop sync client.
|
||||
Simply click the **Add an Account** button on the General tab, and 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 any time.
|
||||
|
||||
Editing Ignored Files
|
||||
---------------------
|
||||
|
||||
The Ignored Files Editor can be opened by clicking on the button in the General
|
||||
tab of the settings dialog. The settings apply to all configured accounts. The
|
||||
:guilabel:`Ignored Files Editor` provides a list of files that are ignored
|
||||
(that is, not synchronized) by the client and server during synchronizations.
|
||||
You may add additional files or directories that you want to exclude from the
|
||||
synchronization process. In addition to using standard characters, the Ignored
|
||||
Files Editor enables you to use wild cards (for example, using an asterisk ‘*’
|
||||
to indicate multiple characters or a question mark ‘?’ to indicate a single
|
||||
character).
|
||||
|
||||
For additional information see `Using the Ignored Files
|
||||
Editor`_
|
||||
.. image:: images/client-9.png
|
||||
:alt: General window contains configuration options.
|
||||
|
||||
Using the Network Window
|
||||
------------------------
|
||||
@@ -170,7 +167,7 @@ Using the Ignored Files Editor
|
||||
|
||||
You might have some local files or directories that you do not want to backup
|
||||
and store on the server. To identify and exclude these files or directories, you
|
||||
can use the *Ignored Files Editor*.
|
||||
can use the *Ignored Files Editor* (General tab.)
|
||||
|
||||
.. image:: images/ignored_files_editor.png
|
||||
|
||||
|
||||
@@ -50,6 +50,12 @@ OPTIONS
|
||||
``--httpproxy http://[user@pass:]<server>:<port>``
|
||||
Uses ``server`` as HTTP proxy.
|
||||
|
||||
``--nonshib``
|
||||
Uses Non Shibboleth WebDAV Authentication
|
||||
|
||||
``--davpath [path]``
|
||||
Overrides the WebDAV Path with ``path``
|
||||
|
||||
Example
|
||||
=======
|
||||
To synchronize the ownCloud directory ``Music`` to the local directory ``media/music``
|
||||
|
||||
@@ -42,6 +42,12 @@ Other command line switches supported by ``owncloudcmd`` include the following:
|
||||
``--unsyncedfolders [file]``
|
||||
File containing list of folders to not sync
|
||||
|
||||
``--nonshib``
|
||||
Uses Non Shibboleth WebDAV Authentication
|
||||
|
||||
``--davpath [path]``
|
||||
Overrides the WebDAV Path with ``path``
|
||||
|
||||
Credential Handling
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
add_subdirectory(MacOSX)
|
||||
if (APPLE)
|
||||
add_subdirectory(MacOSX)
|
||||
endif()
|
||||
add_subdirectory(icons)
|
||||
|
||||
if( UNIX AND NOT APPLE )
|
||||
add_subdirectory(nautilus)
|
||||
endif()
|
||||
|
||||
find_package(ECM 1.2.0 NO_MODULE QUIET)
|
||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
find_package(KF5 "5.16" COMPONENTS KIO)
|
||||
if(KF5_FOUND)
|
||||
add_subdirectory(dolphin)
|
||||
else()
|
||||
message("Dolphin plugin disabled: KDE Frameworks 5.16 not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
auto act = new QAction(parentWidget);
|
||||
act->setText(helper->shareActionString());
|
||||
connect(act, &QAction::triggered, this, [localFile, helper] {
|
||||
helper->sendCommand("SHARE:"+localFile.toUtf8()+"\n");
|
||||
helper->sendCommand(QByteArray("SHARE:"+localFile.toUtf8()+"\n"));
|
||||
} );
|
||||
return { act };
|
||||
}
|
||||
@@ -62,6 +62,5 @@ public:
|
||||
};
|
||||
|
||||
K_PLUGIN_FACTORY(OwncloudDolphinPluginActionFactory, registerPlugin<OwncloudDolphinPluginAction>();)
|
||||
K_EXPORT_PLUGIN(OwncloudDolphinPluginActionFactory("ownclouddolhpinpluginaction"))
|
||||
|
||||
#include "ownclouddolphinactionplugin.moc"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
class OwncloudDolphinPlugin : public KOverlayIconPlugin
|
||||
{
|
||||
Q_PLUGIN_METADATA(IID "com.owncloud.ovarlayiconplugin" FILE "ownclouddolphinoverlayplugin.json");
|
||||
Q_PLUGIN_METADATA(IID "com.owncloud.ovarlayiconplugin" FILE "ownclouddolphinoverlayplugin.json")
|
||||
Q_OBJECT
|
||||
|
||||
typedef QHash<QByteArray, QByteArray> StatusMap;
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
return QStringList();
|
||||
const QByteArray localFile = url.toLocalFile().toUtf8();
|
||||
|
||||
helper->sendCommand("RETRIEVE_FILE_STATUS:" + localFile + "\n");
|
||||
helper->sendCommand(QByteArray("RETRIEVE_FILE_STATUS:" + localFile + "\n"));
|
||||
|
||||
StatusMap::iterator it = m_status.find(localFile);
|
||||
if (it != m_status.constEnd()) {
|
||||
|
||||
@@ -169,12 +169,12 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||
if os.path.isdir(filename + "/"):
|
||||
filename += "/"
|
||||
# Check if toplevel folder, we need to ignore those as they cannot be shared
|
||||
if filename.count("/") < (reg_path.count("/")+2):
|
||||
if filename == reg_path:
|
||||
topLevelFolder=True
|
||||
# Only show the menu extension if the file is synced and the sync
|
||||
# status is ok. Not for ignored files etc.
|
||||
# ignore top level folders
|
||||
if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'] == 'OK':
|
||||
if filename.startswith(reg_path) and topLevelFolder == False and socketConnect.nautilusVFSFile_table[filename]['state'].startswith('OK'):
|
||||
syncedFile = True
|
||||
|
||||
# If it is neither in a synced folder or is a directory
|
||||
@@ -259,8 +259,8 @@ class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.Info
|
||||
if( not itemStore['state'] or newState != itemStore['state'] ):
|
||||
item = itemStore['item']
|
||||
item.add_emblem(emblem)
|
||||
# print("Setting emblem on " + args[1] + "<>" + emblem + "<>") # For debug only
|
||||
socketConnect.nautilusVFSFile_table[args[1]] = {'item': item, 'state':newState}
|
||||
# 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
|
||||
|
||||
@@ -56,8 +56,10 @@ struct CmdOptions {
|
||||
bool useNetrc;
|
||||
bool interactive;
|
||||
bool ignoreHiddenFiles;
|
||||
bool nonShib;
|
||||
QString exclude;
|
||||
QString unsyncedfolders;
|
||||
QString davPath;
|
||||
int restartTimes;
|
||||
};
|
||||
|
||||
@@ -156,6 +158,8 @@ void help()
|
||||
std::cout << " --password, -p [pass] Use [pass] as password" << std::endl;
|
||||
std::cout << " -n Use netrc (5) for login" << std::endl;
|
||||
std::cout << " --non-interactive Do not block execution with interaction" << std::endl;
|
||||
std::cout << " --nonshib Use Non Shibboleth WebDAV authentication" << std::endl;
|
||||
std::cout << " --davpath [path] Custom themed dav path, overrides --nonshib" << std::endl;
|
||||
std::cout << " --max-sync-retries [n] Retries maximum n times (default to 3)" << std::endl;
|
||||
std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
|
||||
std::cout << " --version, -v Display version and exit" << std::endl;
|
||||
@@ -224,6 +228,10 @@ void parseOptions( const QStringList& app_args, CmdOptions *options )
|
||||
options->exclude = it.next();
|
||||
} else if( option == "--unsyncedfolders" && !it.peekNext().startsWith("-") ) {
|
||||
options->unsyncedfolders = it.next();
|
||||
} else if( option == "--nonshib" ) {
|
||||
options->nonShib = true;
|
||||
} else if( option == "--davpath" && !it.peekNext().startsWith("-") ) {
|
||||
options->davPath = it.next();
|
||||
} else if( option == "--max-sync-retries" && !it.peekNext().startsWith("-") ) {
|
||||
options->restartTimes = it.next().toInt();
|
||||
} else {
|
||||
@@ -272,6 +280,7 @@ int main(int argc, char **argv) {
|
||||
options.useNetrc = false;
|
||||
options.interactive = true;
|
||||
options.ignoreHiddenFiles = true;
|
||||
options.nonShib = false;
|
||||
options.restartTimes = 3;
|
||||
ClientProxy clientProxy;
|
||||
|
||||
@@ -287,6 +296,15 @@ int main(int argc, char **argv) {
|
||||
if(!options.target_url.endsWith("/")) {
|
||||
options.target_url.append("/");
|
||||
}
|
||||
|
||||
if( options.nonShib ) {
|
||||
account->setNonShib(true);
|
||||
}
|
||||
|
||||
if(!options.davPath.isEmpty()) {
|
||||
account->setDavPath( options.davPath );
|
||||
}
|
||||
|
||||
if( !options.target_url.contains( account->davPath() )) {
|
||||
options.target_url.append(account->davPath());
|
||||
}
|
||||
@@ -361,6 +379,9 @@ int main(int argc, char **argv) {
|
||||
account->setCredentials(cred);
|
||||
account->setSslErrorHandler(sslErrorHandler);
|
||||
|
||||
// much lower age than the default since this utility is usually made to be run right after a change in the tests
|
||||
SyncEngine::minimumFileAgeForUpload = 0;
|
||||
|
||||
int restartCount = 0;
|
||||
restart_sync:
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
|
||||
#include "account.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "settingsdialogmac.h"
|
||||
#endif
|
||||
|
||||
namespace OCC {
|
||||
|
||||
static const char progressBarStyleC[] =
|
||||
@@ -151,10 +155,7 @@ void AccountSettings::createAccountToolbox()
|
||||
ui->_accountToolbox->setMenu(menu);
|
||||
ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
|
||||
|
||||
// Expand already on single click
|
||||
ui->_folderList->setExpandsOnDoubleClick(false);
|
||||
QObject::connect(ui->_folderList, SIGNAL(clicked(const QModelIndex &)),
|
||||
this, SLOT(slotFolderListClicked(const QModelIndex&)));
|
||||
slotAccountAdded(_accountState);
|
||||
}
|
||||
|
||||
void AccountSettings::slotOpenAccountWizard()
|
||||
@@ -162,13 +163,27 @@ void AccountSettings::slotOpenAccountWizard()
|
||||
if (QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||
topLevelWidget()->close();
|
||||
}
|
||||
#ifdef Q_OS_MAC
|
||||
qDebug() << parent() << topLevelWidget();
|
||||
SettingsDialogMac *sd = qobject_cast<SettingsDialogMac*>(topLevelWidget());
|
||||
|
||||
if (sd) {
|
||||
sd->showActivityPage();
|
||||
} else {
|
||||
qFatal("nope");
|
||||
}
|
||||
#endif
|
||||
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)), 0);
|
||||
}
|
||||
|
||||
// FIXME: Use same code path as ownCloudGui::slotLogout()
|
||||
void AccountSettings::slotToggleSignInState()
|
||||
{
|
||||
bool signedInState = _accountState->isSignedOut();
|
||||
_accountState->setSignedOut( !signedInState );
|
||||
bool signedOutState = _accountState->isSignedOut();
|
||||
if (!signedOutState) {
|
||||
_accountState->account()->credentials()->invalidateToken();
|
||||
}
|
||||
_accountState->setSignedOut( !signedOutState );
|
||||
}
|
||||
|
||||
void AccountSettings::doExpand()
|
||||
@@ -606,12 +621,6 @@ AccountSettings::~AccountSettings()
|
||||
void AccountSettings::refreshSelectiveSyncStatus()
|
||||
{
|
||||
bool shouldBeVisible = _model->isDirty();
|
||||
for (int i = 0; !shouldBeVisible && i < _model->rowCount(); ++i) {
|
||||
auto index = _model->index(i);
|
||||
if (ui->_folderList->isExpanded(index) && _model->rowCount(index) > 0) {
|
||||
shouldBeVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
QString msg;
|
||||
int cnt = 0;
|
||||
@@ -672,9 +681,11 @@ void AccountSettings::slotAccountAdded(AccountState*)
|
||||
{
|
||||
// if the theme is limited to single account, the button must hide if
|
||||
// there is already one account.
|
||||
if( AccountManager::instance()->accounts().size() > 1 &&
|
||||
!Theme::instance()->multiAccount() ) {
|
||||
int s = AccountManager::instance()->accounts().size();
|
||||
if( s > 0 && !Theme::instance()->multiAccount() ) {
|
||||
_addAccountAction->setVisible(false);
|
||||
} else {
|
||||
_addAccountAction->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,18 +711,13 @@ void AccountSettings::slotDeleteAccount()
|
||||
}
|
||||
}
|
||||
|
||||
_model->setAccountState(0); // Else it might access during destruction. This should be better handled by it having a QSharedPointer
|
||||
auto manager = AccountManager::instance();
|
||||
manager->deleteAccount(_accountState);
|
||||
manager->save();
|
||||
|
||||
// if there is no more account, show the wizard.
|
||||
if( manager->accounts().isEmpty() ) {
|
||||
// allow to add a new account if there is non any more. Always think
|
||||
// about single account theming!
|
||||
_addAccountAction->setVisible(true);
|
||||
OwncloudSetupWizard::runWizard(qApp, SLOT(slotownCloudWizardDone(int)));
|
||||
}
|
||||
|
||||
// IMPORTANT: "this" is deleted from this point on. We should probably remove this synchronous
|
||||
// .exec() QMessageBox magic above as it recurses into the event loop.
|
||||
}
|
||||
|
||||
bool AccountSettings::event(QEvent* e)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "account.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "logger.h"
|
||||
#include "configfile.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QSettings>
|
||||
@@ -39,6 +40,7 @@ AccountState::AccountState(AccountPtr account)
|
||||
SLOT(slotCredentialsFetched(AbstractCredentials*)));
|
||||
connect(account.data(), SIGNAL(credentialsAsked(AbstractCredentials*)),
|
||||
SLOT(slotCredentialsAsked(AbstractCredentials*)));
|
||||
_timeSinceLastETagCheck.invalidate();
|
||||
}
|
||||
|
||||
AccountState::~AccountState()
|
||||
@@ -135,6 +137,11 @@ bool AccountState::isConnectedOrTemporarilyUnavailable() const
|
||||
return isConnected() || _state == ServiceUnavailable;
|
||||
}
|
||||
|
||||
void AccountState::tagLastSuccessfullETagRequest()
|
||||
{
|
||||
_timeSinceLastETagCheck.restart();
|
||||
}
|
||||
|
||||
void AccountState::checkConnectivity(CredentialFetchMode credentialsFetchMode)
|
||||
{
|
||||
if (isSignedOut() || _waitingForNewCredentials) {
|
||||
@@ -145,6 +152,18 @@ void AccountState::checkConnectivity(CredentialFetchMode credentialsFetchMode)
|
||||
qDebug() << "ConnectionValidator already running, ignoring" << account()->displayName();
|
||||
return;
|
||||
}
|
||||
|
||||
// IF the account is connected the connection check can be skipped
|
||||
// if the last successful etag check job is not so long ago.
|
||||
ConfigFile cfg;
|
||||
int polltime = cfg.remotePollInterval();
|
||||
|
||||
if (isConnected() && _timeSinceLastETagCheck.isValid()
|
||||
&& _timeSinceLastETagCheck.elapsed() < polltime) {
|
||||
//qDebug() << account()->displayName() << "The last ETag check succeeded within the last " << polltime/1000 << " secs. No connection check needed!";
|
||||
return;
|
||||
}
|
||||
|
||||
_credentialsFetchMode = credentialsFetchMode;
|
||||
ConnectionValidator * conValidator = new ConnectionValidator(account());
|
||||
_connectionValidator = conValidator;
|
||||
|
||||
@@ -95,6 +95,14 @@ public:
|
||||
*/
|
||||
QString shortDisplayNameForSettings(int width = 0) const;
|
||||
|
||||
/** Mark the timestamp when the last successful ETag check happened for
|
||||
* this account.
|
||||
* The checkConnectivity() method uses the timestamp to save a call to
|
||||
* the server to validate the connection if the last successful etag job
|
||||
* was not so long ago.
|
||||
*/
|
||||
void tagLastSuccessfullETagRequest();
|
||||
|
||||
private:
|
||||
void setState(State state);
|
||||
|
||||
@@ -114,6 +122,7 @@ private:
|
||||
QStringList _connectionErrors;
|
||||
bool _waitingForNewCredentials;
|
||||
CredentialFetchMode _credentialsFetchMode;
|
||||
QElapsedTimer _timeSinceLastETagCheck;
|
||||
QPointer<ConnectionValidator> _connectionValidator;
|
||||
};
|
||||
|
||||
|
||||
@@ -73,7 +73,12 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
switch (role) {
|
||||
case ActivityItemDelegate::PathRole:
|
||||
list = FolderMan::instance()->findFileInLocalFolders(a._file);
|
||||
list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
// File does not exist anymore? Let's try to open its path
|
||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account());
|
||||
if( list.count() > 0 ) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
@@ -142,12 +147,13 @@ void ActivityListModel::startFetchJob(AccountState* s)
|
||||
return;
|
||||
}
|
||||
JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
|
||||
QObject::connect(job, SIGNAL(jsonRecieved(QVariantMap)), this, SLOT(slotActivitiesReceived(QVariantMap)));
|
||||
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)),
|
||||
this, SLOT(slotActivitiesReceived(QVariantMap, int)));
|
||||
job->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(s));
|
||||
|
||||
QList< QPair<QString,QString> > params;
|
||||
params.append(qMakePair(QLatin1String("page"), QLatin1String("0")));
|
||||
params.append(qMakePair(QLatin1String("pagesize"), QLatin1String("100")));
|
||||
params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0")));
|
||||
params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100")));
|
||||
job->addQueryParams(params);
|
||||
|
||||
_currentlyFetching.insert(s);
|
||||
@@ -155,7 +161,7 @@ void ActivityListModel::startFetchJob(AccountState* s)
|
||||
job->start();
|
||||
}
|
||||
|
||||
void ActivityListModel::slotActivitiesReceived(const QVariantMap& json)
|
||||
void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode)
|
||||
{
|
||||
auto activities = json.value("ocs").toMap().value("data").toList();
|
||||
qDebug() << "*** activities" << activities;
|
||||
@@ -164,6 +170,7 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json)
|
||||
AccountState* ai = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr"));
|
||||
_currentlyFetching.remove(ai);
|
||||
list.setAccountName( ai->account()->displayName());
|
||||
|
||||
foreach( auto activ, activities ) {
|
||||
auto json = activ.toMap();
|
||||
|
||||
@@ -180,6 +187,10 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json)
|
||||
|
||||
_activityLists[ai] = list;
|
||||
|
||||
if( statusCode == 999 ) {
|
||||
emit accountWithoutActivityApp(ai);
|
||||
}
|
||||
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
@@ -267,7 +278,10 @@ ActivityWidget::ActivityWidget(QWidget *parent) :
|
||||
_ui->_activityList->setAlternatingRowColors(true);
|
||||
_ui->_activityList->setModel(_model);
|
||||
|
||||
_ui->_headerLabel->setText(tr("Server Activities"));
|
||||
showLabels();
|
||||
|
||||
connect(_model, SIGNAL(accountWithoutActivityApp(AccountState*)),
|
||||
this, SLOT(slotAccountWithoutActivityApp(AccountState*)));
|
||||
|
||||
_copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
|
||||
_copyBtn->setToolTip( tr("Copy the activity list to the clipboard."));
|
||||
@@ -294,6 +308,30 @@ void ActivityWidget::slotRemoveAccount( AccountState *ptr )
|
||||
_model->slotRemoveAccount(ptr);
|
||||
}
|
||||
|
||||
void ActivityWidget::showLabels()
|
||||
{
|
||||
QString t = tr("Server Activities");
|
||||
_ui->_headerLabel->setTextFormat(Qt::RichText);
|
||||
_ui->_headerLabel->setText(t);
|
||||
|
||||
t.clear();
|
||||
QSetIterator<QString> i(_accountsWithoutActivities);
|
||||
while (i.hasNext() ) {
|
||||
t.append( tr("<br/>Account %1 does not have activities enabled.").arg(i.next()));
|
||||
}
|
||||
_ui->_bottomLabel->setTextFormat(Qt::RichText);
|
||||
_ui->_bottomLabel->setText(t);
|
||||
}
|
||||
|
||||
void ActivityWidget::slotAccountWithoutActivityApp(AccountState *ast)
|
||||
{
|
||||
if( ast && ast->account() ) {
|
||||
_accountsWithoutActivities.insert(ast->account()->displayName());
|
||||
}
|
||||
|
||||
showLabels();
|
||||
}
|
||||
|
||||
// FIXME: Reused from protocol widget. Move over to utilities.
|
||||
QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const
|
||||
{
|
||||
@@ -333,6 +371,7 @@ void ActivityWidget::storeActivityList( QTextStream& ts )
|
||||
|
||||
void ActivityWidget::slotOpenFile(QModelIndex indx)
|
||||
{
|
||||
qDebug() << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
|
||||
if( indx.isValid() ) {
|
||||
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
|
||||
|
||||
@@ -364,8 +403,8 @@ ActivitySettings::ActivitySettings(QWidget *parent)
|
||||
|
||||
// Add the not-synced list into the tab
|
||||
QWidget *w = new QWidget;
|
||||
QVBoxLayout *vbox2 = new QVBoxLayout(this);
|
||||
vbox2->addWidget(new QLabel(tr("List of ignored or errornous files"), this));
|
||||
QVBoxLayout *vbox2 = new QVBoxLayout(w);
|
||||
vbox2->addWidget(new QLabel(tr("List of ignored or erroneous files"), this));
|
||||
vbox2->addWidget(_protocolWidget->issueWidget());
|
||||
QDialogButtonBox *dlgButtonBox = new QDialogButtonBox(this);
|
||||
vbox2->addWidget(dlgButtonBox);
|
||||
@@ -418,12 +457,24 @@ void ActivitySettings::slotRemoveAccount( AccountState *ptr )
|
||||
|
||||
void ActivitySettings::slotRefresh( AccountState* ptr )
|
||||
{
|
||||
if( ptr && ptr->isConnected() ) {
|
||||
if( ptr && ptr->isConnected() && isVisible()) {
|
||||
qDebug() << "Refreshing Activity list for " << ptr->account()->displayName();
|
||||
_progressIndicator->startAnimation();
|
||||
_activityWidget->slotRefresh(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ActivitySettings::event(QEvent* e)
|
||||
{
|
||||
if (e->type() == QEvent::Show) {
|
||||
AccountManager *am = AccountManager::instance();
|
||||
foreach (AccountStatePtr a, am->accounts()) {
|
||||
slotRefresh(a.data());
|
||||
}
|
||||
}
|
||||
return QWidget::event(e);
|
||||
}
|
||||
|
||||
ActivitySettings::~ActivitySettings()
|
||||
{
|
||||
|
||||
|
||||
@@ -110,7 +110,10 @@ public slots:
|
||||
void slotRemoveAccount( AccountState *ast );
|
||||
|
||||
private slots:
|
||||
void slotActivitiesReceived(const QVariantMap& json);
|
||||
void slotActivitiesReceived(const QVariantMap& json, int statusCode);
|
||||
|
||||
signals:
|
||||
void accountWithoutActivityApp(AccountState* ast);
|
||||
|
||||
private:
|
||||
void startFetchJob(AccountState* s);
|
||||
@@ -135,13 +138,14 @@ class ActivityWidget : public QWidget
|
||||
public:
|
||||
explicit ActivityWidget(QWidget *parent = 0);
|
||||
~ActivityWidget();
|
||||
QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); }
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE { return ownCloudGui::settingsDialogSize(); }
|
||||
void storeActivityList(QTextStream &ts);
|
||||
|
||||
public slots:
|
||||
void slotOpenFile(QModelIndex indx);
|
||||
void slotRefresh(AccountState* ptr);
|
||||
void slotRemoveAccount( AccountState *ptr );
|
||||
void slotAccountWithoutActivityApp(AccountState *ast);
|
||||
|
||||
signals:
|
||||
void guiLog(const QString&, const QString&);
|
||||
@@ -149,10 +153,13 @@ signals:
|
||||
void rowsInserted();
|
||||
|
||||
private:
|
||||
void showLabels();
|
||||
QString timeString(QDateTime dt, QLocale::FormatType format) const;
|
||||
Ui::ActivityWidget *_ui;
|
||||
QPushButton *_copyBtn;
|
||||
|
||||
QSet<QString> _accountsWithoutActivities;
|
||||
|
||||
ActivityListModel *_model;
|
||||
};
|
||||
|
||||
@@ -170,7 +177,7 @@ class ActivitySettings : public QWidget
|
||||
public:
|
||||
explicit ActivitySettings(QWidget *parent = 0);
|
||||
~ActivitySettings();
|
||||
QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); }
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE { return ownCloudGui::settingsDialogSize(); }
|
||||
|
||||
public slots:
|
||||
void slotRefresh( AccountState* ptr );
|
||||
@@ -182,6 +189,8 @@ signals:
|
||||
void guiLog(const QString&, const QString&);
|
||||
|
||||
private:
|
||||
bool event(QEvent* e) Q_DECL_OVERRIDE;
|
||||
|
||||
QTabWidget *_tab;
|
||||
ActivityWidget *_activityWidget;
|
||||
ProtocolWidget *_protocolWidget;
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
<widget class="QListView" name="_activityList"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="_bottomLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QDialogButtonBox" name="_dialogButtonBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "updater/ocupdater.h"
|
||||
#include "excludedfiles.h"
|
||||
#include "owncloudsetupwizard.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -177,11 +178,15 @@ Application::Application(int &argc, char **argv) :
|
||||
|
||||
// startup procedure.
|
||||
connect(&_checkConnectionTimer, SIGNAL(timeout()), this, SLOT(slotCheckConnection()));
|
||||
_checkConnectionTimer.setInterval(32 * 1000); // check for connection every 32 seconds.
|
||||
_checkConnectionTimer.setInterval(ConnectionValidator::defaultCallingIntervalMsec()); // check for connection every 32 seconds.
|
||||
_checkConnectionTimer.start();
|
||||
// Also check immediately
|
||||
QTimer::singleShot( 0, this, SLOT( slotCheckConnection() ));
|
||||
|
||||
// Can't use onlineStateChanged because it is always true on modern systems because of many interfaces
|
||||
connect(&_networkConfigurationManager, SIGNAL(configurationChanged(QNetworkConfiguration)),
|
||||
this, SLOT(slotSystemOnlineConfigurationChanged(QNetworkConfiguration)));
|
||||
|
||||
// Update checks
|
||||
UpdaterScheduler *updaterScheduler = new UpdaterScheduler(this);
|
||||
connect(updaterScheduler, SIGNAL(updaterAnnouncement(QString, QString)),
|
||||
@@ -215,6 +220,13 @@ void Application::slotAccountStateRemoved(AccountState *accountState)
|
||||
disconnect(accountState, SIGNAL(stateChanged(int)),
|
||||
_folderManager.data(), SLOT(slotAccountStateChanged()));
|
||||
}
|
||||
|
||||
// if there is no more account, show the wizard.
|
||||
if( AccountManager::instance()->accounts().isEmpty() ) {
|
||||
// allow to add a new account if there is non any more. Always think
|
||||
// about single account theming!
|
||||
OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int)));
|
||||
}
|
||||
}
|
||||
|
||||
void Application::slotAccountStateAdded(AccountState *accountState)
|
||||
@@ -234,6 +246,17 @@ void Application::slotCleanup()
|
||||
_gui->deleteLater();
|
||||
}
|
||||
|
||||
// FIXME: This is not ideal yet since a ConnectionValidator might already be running and is in
|
||||
// progress of timing out in some seconds.
|
||||
// Maybe we need 2 validators, one triggered by timer, one by network configuration changes?
|
||||
void Application::slotSystemOnlineConfigurationChanged(QNetworkConfiguration cnf)
|
||||
{
|
||||
if (cnf.state() & QNetworkConfiguration::Active) {
|
||||
//qDebug() << "Trying fast reconnect";
|
||||
QMetaObject::invokeMethod(this, "slotCheckConnection", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::slotCheckConnection()
|
||||
{
|
||||
auto list = AccountManager::instance()->accounts();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QNetworkConfigurationManager>
|
||||
|
||||
#include "qtsingleapplication.h"
|
||||
|
||||
@@ -87,6 +88,7 @@ protected slots:
|
||||
void slotAccountStateAdded(AccountState *accountState);
|
||||
void slotAccountStateRemoved(AccountState *accountState);
|
||||
void slotCrash();
|
||||
void slotSystemOnlineConfigurationChanged(QNetworkConfiguration);
|
||||
|
||||
private:
|
||||
void setHelp();
|
||||
@@ -111,6 +113,7 @@ private:
|
||||
|
||||
ClientProxy _proxy;
|
||||
|
||||
QNetworkConfigurationManager _networkConfigurationManager;
|
||||
QTimer _checkConnectionTimer;
|
||||
|
||||
#if defined(WITH_CRASHREPORTER)
|
||||
|
||||
@@ -22,11 +22,14 @@ ShibbolethUserJob::ShibbolethUserJob(AccountPtr account, QObject* parent)
|
||||
: JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/user"), parent)
|
||||
{
|
||||
setIgnoreCredentialFailure(true);
|
||||
connect(this, SIGNAL(jsonRecieved(QVariantMap)), this, SLOT(slotJsonRecieved(QVariantMap)));
|
||||
connect(this, SIGNAL(jsonReceived(QVariantMap, int)), this, SLOT(slotJsonReceived(QVariantMap, int)));
|
||||
}
|
||||
|
||||
void ShibbolethUserJob::slotJsonRecieved(const QVariantMap &json)
|
||||
void ShibbolethUserJob::slotJsonReceived(const QVariantMap &json, int statusCode)
|
||||
{
|
||||
if( statusCode != 100 ) {
|
||||
qWarning() << "JSON Api call resulted in status code != 100";
|
||||
}
|
||||
QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString();
|
||||
//qDebug() << "cloud/user: " << json << "->" << user;
|
||||
emit userFetched(user);
|
||||
|
||||
@@ -33,7 +33,7 @@ signals:
|
||||
void userFetched(const QString &user);
|
||||
|
||||
private slots:
|
||||
void slotJsonRecieved(const QVariantMap &);
|
||||
void slotJsonReceived(const QVariantMap &, int statusCode);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -365,11 +365,16 @@ void Folder::etagRetreived(const QString& etag)
|
||||
_lastEtag = etag;
|
||||
emit scheduleToSync(this);
|
||||
}
|
||||
|
||||
if( _accountState ) {
|
||||
_accountState->tagLastSuccessfullETagRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::etagRetreivedFromSyncEngine(const QString& etag)
|
||||
{
|
||||
qDebug() << "Root etag from during sync:" << etag;
|
||||
accountState()->tagLastSuccessfullETagRequest();
|
||||
_lastEtag = etag;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "accountstate.h"
|
||||
#include "accountmanager.h"
|
||||
#include "filesystem.h"
|
||||
#include <syncengine.h>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <CoreServices/CoreServices.h>
|
||||
@@ -40,16 +41,6 @@ namespace OCC {
|
||||
|
||||
FolderMan* FolderMan::_instance = 0;
|
||||
|
||||
/**
|
||||
* The minimum time between a sync being requested and it
|
||||
* being executed in milliseconds.
|
||||
*
|
||||
* This delay must be large enough to ensure fileIsStillChanging()
|
||||
* in the upload propagator doesn't decide to skip the file because
|
||||
* the modification was too recent.
|
||||
*/
|
||||
static qint64 msBetweenRequestAndSync = 2000;
|
||||
|
||||
FolderMan::FolderMan(QObject *parent) :
|
||||
QObject(parent),
|
||||
_currentSyncFolder(0),
|
||||
@@ -542,7 +533,7 @@ void FolderMan::slotRunOneEtagJob()
|
||||
}
|
||||
}
|
||||
if (_currentEtagJob.isNull()) {
|
||||
qDebug() << "No more remote ETag check jobs to schedule.";
|
||||
//qDebug() << "No more remote ETag check jobs to schedule.";
|
||||
|
||||
/* now it might be a good time to check for restarting... */
|
||||
if( _currentSyncFolder == NULL && _appRestartRequired ) {
|
||||
@@ -652,7 +643,7 @@ void FolderMan::startScheduledSyncSoon(qint64 msMinimumDelay)
|
||||
|
||||
// A minimum of delay here is essential as the sync will not upload
|
||||
// files that were changed too recently.
|
||||
msDelay = qMax(msBetweenRequestAndSync, msDelay);
|
||||
msDelay = qMax(SyncEngine::minimumFileAgeForUpload, msDelay);
|
||||
|
||||
qDebug() << "Scheduling a sync in" << (msDelay/1000) << "seconds";
|
||||
_startScheduledSyncTimer.start(msDelay);
|
||||
@@ -824,17 +815,20 @@ Folder *FolderMan::folderForPath(const QString &path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
QStringList FolderMan::findFileInLocalFolders( const QString& relPath )
|
||||
QStringList FolderMan::findFileInLocalFolders( const QString& relPath, const AccountPtr acc )
|
||||
{
|
||||
QStringList re;
|
||||
|
||||
foreach(Folder* folder, this->map().values()) {
|
||||
if (acc != 0 && folder->accountState()->account() != acc) {
|
||||
continue;
|
||||
}
|
||||
QString path = folder->cleanPath();
|
||||
QString remRelPath;
|
||||
// cut off the remote path from the server path.
|
||||
remRelPath = relPath.mid(folder->remotePath().length());
|
||||
path += "/";
|
||||
path += remRelPath;
|
||||
|
||||
if( QFile::exists(path) ) {
|
||||
re.append( path );
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
* incoming relative server path. The method checks with all existing sync
|
||||
* folders.
|
||||
*/
|
||||
QStringList findFileInLocalFolders( const QString& relPath );
|
||||
QStringList findFileInLocalFolders( const QString& relPath, const AccountPtr acc );
|
||||
|
||||
/** Returns the folder by alias or NULL if no folder with the alias exists. */
|
||||
Folder *folder( const QString& );
|
||||
|
||||
@@ -53,6 +53,8 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
|
||||
|
||||
auto folders = FolderMan::instance()->map();
|
||||
foreach (auto f, folders) {
|
||||
if (!accountState)
|
||||
break;
|
||||
if (f->accountState() != accountState)
|
||||
continue;
|
||||
SubFolderInfo info;
|
||||
@@ -74,6 +76,9 @@ void FolderStatusModel::setAccountState(const AccountState* accountState)
|
||||
|
||||
Qt::ItemFlags FolderStatusModel::flags ( const QModelIndex &index ) const
|
||||
{
|
||||
if (!_accountState) {
|
||||
return 0;
|
||||
}
|
||||
switch (classify(index)) {
|
||||
case AddButton: {
|
||||
Qt::ItemFlags ret;
|
||||
@@ -139,6 +144,7 @@ QVariant FolderStatusModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case Qt::ToolTipRole:
|
||||
case Qt::DisplayRole:
|
||||
//: Example text: "File.txt (23KB)"
|
||||
return tr("%1 (%2)").arg(x._name, Utility::octetsToString(x._size));
|
||||
case Qt::CheckStateRole:
|
||||
return x._checked;
|
||||
@@ -478,6 +484,9 @@ bool FolderStatusModel::hasChildren(const QModelIndex& parent) const
|
||||
|
||||
bool FolderStatusModel::canFetchMore(const QModelIndex& parent) const
|
||||
{
|
||||
if (!_accountState) {
|
||||
return false;
|
||||
}
|
||||
if (_accountState->state() != AccountState::Connected) {
|
||||
return false;
|
||||
}
|
||||
@@ -624,6 +633,10 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
|
||||
suggestExpand(idx.child(*it, 0));
|
||||
}
|
||||
|
||||
/* We need lambda function for the following code.
|
||||
* It's just a small feature that will be missing if the comiler is too old */
|
||||
#if !(defined(Q_CC_GNU) && !defined(Q_CC_INTEL) && !defined(Q_CC_CLANG)) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 405)
|
||||
|
||||
/* Try to remove the the undecided lists the items that are not on the server. */
|
||||
auto it = std::remove_if(selectiveSyncUndecidedList.begin(), selectiveSyncUndecidedList.end(),
|
||||
[&](const QString &s) { return selectiveSyncUndecidedSet.count(s); } );
|
||||
@@ -633,6 +646,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
|
||||
SyncJournalDb::SelectiveSyncUndecidedList, selectiveSyncUndecidedList);
|
||||
emit dirtyChanged();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FolderStatusModel::slotLscolFinishedWithError(QNetworkReply* r)
|
||||
@@ -823,12 +837,14 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
|
||||
estimatedUpBw += progress.fileProgress(citm._item).estimatedBandwidth;
|
||||
//qDebug() << "UP" << citm._item._file << progress.fileProgress(citm._item).estimatedBandwidth;
|
||||
}
|
||||
auto fileName = QFileInfo(citm._item._file).fileName();
|
||||
if (allFilenames.length() > 0) {
|
||||
allFilenames.append(", ");
|
||||
//: Build a list of file names
|
||||
allFilenames.append(tr(", '%1'").arg(fileName));
|
||||
} else {
|
||||
//: Argument is a file name
|
||||
allFilenames.append(tr("'%1'").arg(fileName));
|
||||
}
|
||||
allFilenames.append('\'');
|
||||
allFilenames.append(QFileInfo(citm._item._file).fileName());
|
||||
allFilenames.append('\'');
|
||||
}
|
||||
//qDebug() << "Syncing bandwidth" << estimatedDownBw << estimatedUpBw;
|
||||
if (curItemProgress == -1) {
|
||||
@@ -851,19 +867,22 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
|
||||
Utility::durationToDescriptiveString(progress.fileProgress(curItem).estimatedEta),
|
||||
Utility::octetsToString(estimatedBw) );
|
||||
*/
|
||||
//: Example text: "Syncing 'foo.txt', 'bar.txt'"
|
||||
fileProgressString = tr("Syncing %1").arg(allFilenames);
|
||||
if (estimatedDownBw > 0) {
|
||||
fileProgressString.append(", ");
|
||||
fileProgressString.append(tr(", "));
|
||||
// ifdefs: https://github.com/owncloud/client/issues/3095#issuecomment-128409294
|
||||
#ifdef Q_OS_WIN
|
||||
//: Example text: "download 24Kb/s" (%1 is replaced by 24Kb (translated))
|
||||
fileProgressString.append(tr("download %1/s").arg(Utility::octetsToString(estimatedDownBw)));
|
||||
#else
|
||||
fileProgressString.append(trUtf8("\u2193" " %1/s").arg(Utility::octetsToString(estimatedDownBw)));
|
||||
#endif
|
||||
}
|
||||
if (estimatedUpBw > 0) {
|
||||
fileProgressString.append(", ");
|
||||
fileProgressString.append(tr(", "));
|
||||
#ifdef Q_OS_WIN
|
||||
//: Example text: "upload 24Kb/s" (%1 is replaced by 24Kb (translated))
|
||||
fileProgressString.append(tr("upload %1/s").arg(Utility::octetsToString(estimatedUpBw)));
|
||||
#else
|
||||
fileProgressString.append(trUtf8("\u2191" " %1/s").arg(Utility::octetsToString(estimatedUpBw)));
|
||||
@@ -891,6 +910,7 @@ void FolderStatusModel::slotSetProgress(const ProgressInfo &progress)
|
||||
if (totalSize > 0) {
|
||||
QString s1 = Utility::octetsToString( completedSize );
|
||||
QString s2 = Utility::octetsToString( totalSize );
|
||||
//: Example text: "12 MB of 345 MB, file 6 of 7\nTotal time left 12 minutes"
|
||||
overallSyncString = tr("%1 of %2, file %3 of %4\nTotal time left %5")
|
||||
.arg(s1, s2)
|
||||
.arg(currentFile).arg(totalFileCount)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>520</width>
|
||||
<height>300</height>
|
||||
<height>350</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -182,19 +182,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
||||
@@ -122,7 +122,9 @@ void OcsShareJob::createShare(const QString& path,
|
||||
addParam(QString::fromLatin1("path"), path);
|
||||
addParam(QString::fromLatin1("shareType"), QString::number(shareType));
|
||||
addParam(QString::fromLatin1("shareWith"), shareWith);
|
||||
addParam(QString::fromLatin1("permissions"), QString::number(permissions));
|
||||
if (!(permissions & Share::PermissionDefault)) {
|
||||
addParam(QString::fromLatin1("permissions"), QString::number(permissions));
|
||||
}
|
||||
|
||||
start();
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
||||
#else
|
||||
QStringList allStatusStrings;
|
||||
foreach(Folder* folder, map.values()) {
|
||||
qDebug() << "Folder in overallStatus Message: " << folder << " with name " << folder->alias();
|
||||
//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);
|
||||
}
|
||||
@@ -335,7 +335,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->path()), this );
|
||||
QAction *action = new QAction( tr("Open folder '%1'").arg(folder->shortGuiPath()), this );
|
||||
connect(action, SIGNAL(triggered()),_folderOpenActionMapper, SLOT(map()));
|
||||
_folderOpenActionMapper->setMapping( action, folder->alias() );
|
||||
menu->addAction(action);
|
||||
@@ -637,6 +637,7 @@ void ownCloudGui::slotLogin()
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Unify codepath with AccountSettings::slotToggleSignInState()
|
||||
void ownCloudGui::slotLogout()
|
||||
{
|
||||
auto list = AccountManager::instance()->accounts();
|
||||
|
||||
@@ -217,9 +217,13 @@ void SettingsDialog::accountRemoved(AccountState *s)
|
||||
if (as->accountsState() == s) {
|
||||
_toolBar->removeAction(it.key());
|
||||
|
||||
delete it.key();
|
||||
delete it.value();
|
||||
it.key()->deleteLater();
|
||||
it.value()->deleteLater();
|
||||
_actionGroupWidgets.erase(it);
|
||||
|
||||
if (_ui->stack->currentWidget() == it.value()) {
|
||||
showFirstPage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -309,7 +313,6 @@ void SettingsDialog::addActionToToolBar(QAction *action) {
|
||||
void SettingsDialog::slotRefreshActivity( AccountState* accountState )
|
||||
{
|
||||
if (accountState) {
|
||||
qDebug() << "Refreshing Activity list for " << accountState->account()->displayName();
|
||||
_activitySettings->slotRefresh(accountState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ void SettingsDialogMac::accountRemoved(AccountState *s)
|
||||
void SettingsDialogMac::slotRefreshActivity( AccountState* accountState )
|
||||
{
|
||||
if (accountState) {
|
||||
qDebug() << "Refreshing Activity list for " << accountState->account()->displayName();
|
||||
_activitySettings->slotRefresh(accountState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,14 +34,14 @@ public:
|
||||
|
||||
/**
|
||||
* Possible share types
|
||||
* Need to be in sync with Sharee::Type
|
||||
*/
|
||||
enum ShareType {
|
||||
TypeUser = 0,
|
||||
TypeGroup = 1,
|
||||
TypeUser = Sharee::User,
|
||||
TypeGroup = Sharee::Group,
|
||||
TypeLink = 3,
|
||||
TypeRemote = 6
|
||||
TypeRemote = Sharee::Federated
|
||||
};
|
||||
Q_DECLARE_FLAGS(ShareTypes, ShareType)
|
||||
|
||||
/**
|
||||
* Possible permissions
|
||||
@@ -51,7 +51,8 @@ public:
|
||||
PermissionUpdate = 2,
|
||||
PermissionCreate = 4,
|
||||
PermissionDelete = 8,
|
||||
PermissionShare = 16
|
||||
PermissionShare = 16,
|
||||
PermissionDefault = 1 << 30
|
||||
};
|
||||
Q_DECLARE_FLAGS(Permissions, Permission)
|
||||
|
||||
@@ -62,7 +63,7 @@ public:
|
||||
const QString& id,
|
||||
const QString& path,
|
||||
const ShareType shareType,
|
||||
const Permissions permissions,
|
||||
const Permissions permissions = PermissionDefault,
|
||||
const QSharedPointer<Sharee> shareWith = QSharedPointer<Sharee>(NULL));
|
||||
|
||||
/*
|
||||
|
||||
@@ -46,7 +46,10 @@ ShareDialog::ShareDialog(AccountPtr account, const QString &sharePath, const QSt
|
||||
|
||||
QPushButton *closeButton = _ui->buttonBox->button(QDialogButtonBox::Close);
|
||||
connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));
|
||||
closeButton->setDefault(false); // Because people press enter in the dialog and we don't want to close for that
|
||||
|
||||
// Because people press enter in the dialog and we don't want to close for that
|
||||
closeButton->setDefault(false);
|
||||
closeButton->setAutoDefault(false);
|
||||
|
||||
// Set icon
|
||||
QFileInfo f_info(_localPath);
|
||||
@@ -68,14 +71,27 @@ ShareDialog::ShareDialog(AccountPtr account, const QString &sharePath, const QSt
|
||||
|
||||
ocDir.replace(QRegExp("^/*"), "");
|
||||
ocDir.replace(QRegExp("/*$"), "");
|
||||
|
||||
// Laying this out is complex because sharePath
|
||||
// may be in use or not.
|
||||
_ui->gridLayout->removeWidget(_ui->label_sharePath);
|
||||
_ui->gridLayout->removeWidget(_ui->label_name);
|
||||
if( ocDir.isEmpty() ) {
|
||||
_ui->gridLayout->addWidget(_ui->label_name, 0, 1, 2, 1);
|
||||
_ui->label_sharePath->setText(QString());
|
||||
} else {
|
||||
_ui->gridLayout->addWidget(_ui->label_name, 0, 1, 1, 1);
|
||||
_ui->gridLayout->addWidget(_ui->label_sharePath, 1, 1, 1, 1);
|
||||
_ui->label_sharePath->setText(tr("Folder: %2").arg(ocDir));
|
||||
}
|
||||
|
||||
this->setWindowTitle(tr("%1 Sharing").arg(Theme::instance()->appNameGUI()));
|
||||
|
||||
if (!account->capabilities().shareAPI()) {
|
||||
_ui->shareWidgetsLayout->addWidget(new QLabel(tr("The server does not allow sharing")));
|
||||
return;
|
||||
}
|
||||
|
||||
bool autoShare = true;
|
||||
|
||||
// We only do user/group sharing from 8.2.0
|
||||
@@ -120,7 +136,9 @@ void ShareDialog::getShares()
|
||||
job->start();
|
||||
}
|
||||
|
||||
_linkWidget->getShares();
|
||||
if (_linkWidget) {
|
||||
_linkWidget->getShares();
|
||||
}
|
||||
if (_userGroupWidget != NULL) {
|
||||
_userGroupWidget->getShares();
|
||||
}
|
||||
|
||||
@@ -24,12 +24,6 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>share label</string>
|
||||
</property>
|
||||
|
||||
@@ -137,7 +137,7 @@ QSharedPointer<Sharee> ShareeModel::parseSharee(const QVariantMap &data)
|
||||
const QString shareWith = data.value("value").toMap().value("shareWith").toString();
|
||||
Sharee::Type type = (Sharee::Type)data.value("value").toMap().value("shareType").toInt();
|
||||
|
||||
return QSharedPointer<Sharee>(new Sharee(shareWith, shareWith, type));
|
||||
return QSharedPointer<Sharee>(new Sharee(shareWith, displayName, type));
|
||||
}
|
||||
|
||||
/* Set the new sharee
|
||||
|
||||
@@ -29,17 +29,17 @@ namespace OCC {
|
||||
class Sharee {
|
||||
public:
|
||||
|
||||
// Keep in sync with Share::ShareType
|
||||
enum Type {
|
||||
User = 0,
|
||||
Group = 1,
|
||||
Federated = 6
|
||||
};
|
||||
Q_DECLARE_FLAGS(Types, Type)
|
||||
|
||||
explicit Sharee(const QString shareWith,
|
||||
const QString displayName,
|
||||
const Type type);
|
||||
|
||||
|
||||
QString format() const;
|
||||
QString shareWith() const;
|
||||
QString displayName() const;
|
||||
|
||||
@@ -41,7 +41,8 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
_manager(NULL),
|
||||
_share(NULL),
|
||||
_resharingAllowed(resharingAllowed),
|
||||
_autoShare(autoShare)
|
||||
_autoShare(autoShare),
|
||||
_passwordRequired(false)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
@@ -81,6 +82,7 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
_ui->lineEdit_password->hide();
|
||||
_ui->pushButton_setPassword->hide();
|
||||
|
||||
_ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
|
||||
_ui->calendar->setEnabled(false);
|
||||
|
||||
_ui->checkBox_password->setText(tr("P&assword protect"));
|
||||
@@ -115,6 +117,7 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
// If password is enforced then don't allow users to disable it
|
||||
if (_account->capabilities().sharePublicLinkEnforcePassword()) {
|
||||
_ui->checkBox_password->setEnabled(false);
|
||||
_passwordRequired = true;
|
||||
}
|
||||
|
||||
// If expiredate is enforced do not allow disable and set max days
|
||||
@@ -220,8 +223,8 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
||||
const QString versionString = _account->serverVersion();
|
||||
qDebug() << Q_FUNC_INFO << versionString << "Fetched" << shares.count() << "shares";
|
||||
|
||||
//Show link checkbox now
|
||||
_ui->checkBox_shareLink->setEnabled(true);
|
||||
//Show link checkbox now if capabilities allow it
|
||||
_ui->checkBox_shareLink->setEnabled(_account->capabilities().sharePublicLink());
|
||||
_pi_link->stopAnimation();
|
||||
|
||||
Q_FOREACH(auto share, shares) {
|
||||
@@ -233,7 +236,8 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
||||
_ui->widget_shareLink->show();
|
||||
_ui->checkBox_shareLink->setChecked(true);
|
||||
|
||||
_ui->checkBox_password->setEnabled(true);
|
||||
_ui->checkBox_password->setEnabled(!_passwordRequired);
|
||||
|
||||
if (_share->isPasswordSet()) {
|
||||
_ui->lineEdit_password->setEnabled(true);
|
||||
_ui->checkBox_password->setChecked(true);
|
||||
@@ -291,7 +295,7 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
||||
if( !_resharingAllowed ) {
|
||||
displayError(tr("The file can not be shared because it was shared without sharing permission."));
|
||||
_ui->checkBox_shareLink->setEnabled(false);
|
||||
} else if (_autoShare) {
|
||||
} else if (_autoShare && _ui->checkBox_shareLink->isEnabled()) {
|
||||
_ui->checkBox_shareLink->setChecked(true);
|
||||
slotCheckBoxShareLinkClicked();
|
||||
}
|
||||
@@ -409,6 +413,7 @@ void ShareLinkWidget::slotCreateShareFetched(const QSharedPointer<LinkShare> sha
|
||||
void ShareLinkWidget::slotCreateShareRequiresPassword()
|
||||
{
|
||||
// there needs to be a password
|
||||
_pi_editing->stopAnimation();
|
||||
_ui->checkBox_password->setChecked(true);
|
||||
_ui->checkBox_password->setEnabled(false);
|
||||
_ui->checkBox_password->setText(tr("Public shå requires a password"));
|
||||
@@ -418,6 +423,8 @@ void ShareLinkWidget::slotCreateShareRequiresPassword()
|
||||
_ui->checkBox_expire->setEnabled(false);
|
||||
_ui->checkBox_editing->setEnabled(false);
|
||||
|
||||
_passwordRequired = true;
|
||||
|
||||
slotCheckBoxPasswordClicked();
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,7 @@ private:
|
||||
bool _resharingAllowed;
|
||||
bool _isFile;
|
||||
bool _autoShare;
|
||||
bool _passwordRequired;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@
|
||||
#include <qscrollarea.h>
|
||||
#include <qlayout.h>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -63,6 +65,9 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, const QString &sh
|
||||
connect(_completerModel, SIGNAL(shareesReady()), this, SLOT(slotShareesReady()));
|
||||
|
||||
_completer->setModel(_completerModel);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
|
||||
_completer->setFilterMode(Qt::MatchContains);
|
||||
#endif
|
||||
_ui->shareeLineEdit->setCompleter(_completer);
|
||||
|
||||
_manager = new ShareManager(_account, this);
|
||||
@@ -140,12 +145,15 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
||||
continue;
|
||||
}
|
||||
|
||||
ShareWidget *s = new ShareWidget(share, _ui->scrollArea);
|
||||
ShareWidget *s = new ShareWidget(share, _isFile, _ui->scrollArea);
|
||||
connect(s, SIGNAL(resizeRequested()), this, SLOT(slotAdjustScrollWidgetSize()));
|
||||
layout->addWidget(s);
|
||||
|
||||
x++;
|
||||
if (x <= 3) {
|
||||
minimumSize = newViewPort->sizeHint();
|
||||
} else {
|
||||
minimumSize.rwidth() = qMax(newViewPort->sizeHint().width(), minimumSize.width());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +164,19 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
||||
scrollArea->setWidget(newViewPort);
|
||||
}
|
||||
|
||||
void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
|
||||
{
|
||||
QScrollArea *scrollArea = _ui->scrollArea;
|
||||
if (scrollArea->findChildren<ShareWidget*>().count() <= 3) {
|
||||
auto minimumSize = scrollArea->widget()->sizeHint();
|
||||
auto spacing = scrollArea->widget()->layout()->spacing();
|
||||
minimumSize.rwidth() += spacing;
|
||||
minimumSize.rheight() += spacing;
|
||||
scrollArea->setMinimumSize(minimumSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ShareUserGroupWidget::slotShareesReady()
|
||||
{
|
||||
_completer->complete();
|
||||
@@ -170,36 +191,53 @@ void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex & index)
|
||||
return;
|
||||
}
|
||||
|
||||
_manager->createShare(_sharePath,
|
||||
(Share::ShareType)sharee->type(),
|
||||
sharee->shareWith(),
|
||||
Share::PermissionRead);
|
||||
_manager->createShare(_sharePath, Share::ShareType(sharee->type()),
|
||||
sharee->shareWith(), Share::PermissionDefault);
|
||||
|
||||
_ui->shareeLineEdit->setText(QString());
|
||||
}
|
||||
|
||||
ShareWidget::ShareWidget(QSharedPointer<Share> share,
|
||||
QWidget *parent) :
|
||||
bool isFile,
|
||||
QWidget *parent) :
|
||||
QWidget(parent),
|
||||
_ui(new Ui::ShareWidget),
|
||||
_share(share),
|
||||
_showDetailedPermissions(false)
|
||||
_isFile(isFile)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
_ui->sharedWith->setText(share->getShareWith()->format());
|
||||
|
||||
// Create detailed permissions menu
|
||||
QMenu *menu = new QMenu(this);
|
||||
_permissionCreate = new QAction(tr("create"), this);
|
||||
_permissionCreate->setCheckable(true);
|
||||
_permissionUpdate = new QAction(tr("change"), this);
|
||||
_permissionUpdate->setCheckable(true);
|
||||
_permissionDelete = new QAction(tr("delete"), this);
|
||||
_permissionDelete->setCheckable(true);
|
||||
|
||||
menu->addAction(_permissionUpdate);
|
||||
/*
|
||||
* Files can't have create or delete permissions
|
||||
*/
|
||||
if (!_isFile) {
|
||||
menu->addAction(_permissionCreate);
|
||||
menu->addAction(_permissionDelete);
|
||||
}
|
||||
_ui->permissionToolButton->setMenu(menu);
|
||||
_ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
|
||||
|
||||
QIcon icon(QLatin1String(":/client/resources/more.png"));
|
||||
_ui->permissionToolButton->setIcon(icon);
|
||||
|
||||
// Set the permissions checkboxes
|
||||
displayPermissions();
|
||||
|
||||
// Hide "detailed permissions" by default
|
||||
_ui->permissionDelete->setHidden(true);
|
||||
_ui->permissionUpdate->setHidden(true);
|
||||
_ui->permissionCreate->setHidden(true);
|
||||
|
||||
connect(_ui->permissionUpdate, SIGNAL(clicked(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_ui->permissionCreate, SIGNAL(clicked(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_ui->permissionDelete, SIGNAL(clicked(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_permissionUpdate, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_permissionCreate, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_permissionDelete, SIGNAL(triggered(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_ui->permissionShare, SIGNAL(clicked(bool)), SLOT(slotPermissionsChanged()));
|
||||
connect(_ui->permissionsEdit, SIGNAL(clicked(bool)), SLOT(slotEditPermissionsChanged()));
|
||||
|
||||
@@ -216,20 +254,6 @@ void ShareWidget::on_deleteShareButton_clicked()
|
||||
_share->deleteShare();
|
||||
}
|
||||
|
||||
void ShareWidget::on_permissionToggleButton_clicked()
|
||||
{
|
||||
_showDetailedPermissions = !_showDetailedPermissions;
|
||||
_ui->permissionDelete->setVisible(_showDetailedPermissions);
|
||||
_ui->permissionUpdate->setVisible(_showDetailedPermissions);
|
||||
_ui->permissionCreate->setVisible(_showDetailedPermissions);
|
||||
|
||||
if (_showDetailedPermissions) {
|
||||
_ui->permissionToggleButton->setText("Hide");
|
||||
} else {
|
||||
_ui->permissionToggleButton->setText("More");
|
||||
}
|
||||
}
|
||||
|
||||
ShareWidget::~ShareWidget()
|
||||
{
|
||||
delete _ui;
|
||||
@@ -242,13 +266,19 @@ void ShareWidget::slotEditPermissionsChanged()
|
||||
Share::Permissions permissions = Share::PermissionRead;
|
||||
|
||||
if (_ui->permissionShare->checkState() == Qt::Checked) {
|
||||
permissions |= Share::PermissionUpdate;
|
||||
permissions |= Share::PermissionShare;
|
||||
}
|
||||
|
||||
if (_ui->permissionsEdit->checkState() == Qt::Checked) {
|
||||
permissions |= Share::PermissionCreate;
|
||||
permissions |= Share::PermissionUpdate;
|
||||
permissions |= Share::PermissionDelete;
|
||||
|
||||
/*
|
||||
* Files can't have create or delete permisisons
|
||||
*/
|
||||
if (!_isFile) {
|
||||
permissions |= Share::PermissionCreate;
|
||||
permissions |= Share::PermissionDelete;
|
||||
}
|
||||
}
|
||||
|
||||
_share->setPermissions(permissions);
|
||||
@@ -260,15 +290,15 @@ void ShareWidget::slotPermissionsChanged()
|
||||
|
||||
Share::Permissions permissions = Share::PermissionRead;
|
||||
|
||||
if (_ui->permissionUpdate->checkState() == Qt::Checked) {
|
||||
if (_permissionUpdate->isChecked()) {
|
||||
permissions |= Share::PermissionUpdate;
|
||||
}
|
||||
|
||||
if (_ui->permissionCreate->checkState() == Qt::Checked) {
|
||||
if (_permissionCreate->isChecked()) {
|
||||
permissions |= Share::PermissionCreate;
|
||||
}
|
||||
|
||||
if (_ui->permissionDelete->checkState() == Qt::Checked) {
|
||||
if (_permissionDelete->isChecked()) {
|
||||
permissions |= Share::PermissionDelete;
|
||||
}
|
||||
|
||||
@@ -281,7 +311,13 @@ void ShareWidget::slotPermissionsChanged()
|
||||
|
||||
void ShareWidget::slotDeleteAnimationFinished()
|
||||
{
|
||||
resizeRequested();
|
||||
deleteLater();
|
||||
|
||||
// There is a painting bug where a small line of this widget isn't
|
||||
// properly cleared. This explicit repaint() call makes sure any trace of
|
||||
// the share widget is removed once it's destroyed. #4189
|
||||
connect(this, SIGNAL(destroyed(QObject*)), parentWidget(), SLOT(repaint()));
|
||||
}
|
||||
|
||||
void ShareWidget::slotShareDeleted()
|
||||
@@ -293,6 +329,7 @@ void ShareWidget::slotShareDeleted()
|
||||
animation->setEndValue(0);
|
||||
|
||||
connect(animation, SIGNAL(finished()), SLOT(slotDeleteAnimationFinished()));
|
||||
connect(animation, SIGNAL(valueChanged(QVariant)), this, SIGNAL(resizeRequested()));
|
||||
|
||||
animation->start();
|
||||
}
|
||||
@@ -310,22 +347,22 @@ QSharedPointer<Share> ShareWidget::share() const
|
||||
|
||||
void ShareWidget::displayPermissions()
|
||||
{
|
||||
_ui->permissionCreate->setCheckState(Qt::Unchecked);
|
||||
_permissionCreate->setChecked(false);
|
||||
_ui->permissionsEdit->setCheckState(Qt::Unchecked);
|
||||
_ui->permissionDelete->setCheckState(Qt::Unchecked);
|
||||
_permissionDelete->setChecked(false);
|
||||
_ui->permissionShare->setCheckState(Qt::Unchecked);
|
||||
_ui->permissionUpdate->setCheckState(Qt::Unchecked);
|
||||
_permissionUpdate->setChecked(false);
|
||||
|
||||
if (_share->getPermissions() & Share::PermissionUpdate) {
|
||||
_ui->permissionUpdate->setCheckState(Qt::Checked);
|
||||
_permissionUpdate->setChecked(true);
|
||||
_ui->permissionsEdit->setCheckState(Qt::Checked);
|
||||
}
|
||||
if (_share->getPermissions() & Share::PermissionCreate) {
|
||||
_ui->permissionCreate->setCheckState(Qt::Checked);
|
||||
if (!_isFile && _share->getPermissions() & Share::PermissionCreate) {
|
||||
_permissionCreate->setChecked(true);
|
||||
_ui->permissionsEdit->setCheckState(Qt::Checked);
|
||||
}
|
||||
if (_share->getPermissions() & Share::PermissionDelete) {
|
||||
_ui->permissionDelete->setCheckState(Qt::Checked);
|
||||
if (!_isFile && _share->getPermissions() & Share::PermissionDelete) {
|
||||
_permissionDelete->setChecked(true);
|
||||
_ui->permissionsEdit->setCheckState(Qt::Checked);
|
||||
}
|
||||
if (_share->getPermissions() & Share::PermissionShare) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QVector>
|
||||
#include <QTimer>
|
||||
|
||||
class QAction;
|
||||
class QCompleter;
|
||||
class QModelIndex;
|
||||
|
||||
@@ -47,30 +48,33 @@ class ShareWidget : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ShareWidget(QSharedPointer<Share> Share, QWidget *parent = 0);
|
||||
explicit ShareWidget(QSharedPointer<Share> Share, bool isFile, QWidget *parent = 0);
|
||||
~ShareWidget();
|
||||
|
||||
QSharedPointer<Share> share() const;
|
||||
|
||||
signals:
|
||||
void shareDeleted(ShareWidget *share);
|
||||
void resizeRequested();
|
||||
|
||||
private slots:
|
||||
void on_deleteShareButton_clicked();
|
||||
void slotPermissionsChanged();
|
||||
void slotEditPermissionsChanged();
|
||||
void on_permissionToggleButton_clicked();
|
||||
void slotDeleteAnimationFinished();
|
||||
|
||||
void slotShareDeleted();
|
||||
void slotPermissionsSet();
|
||||
|
||||
private:
|
||||
void displayPermissions();
|
||||
|
||||
Ui::ShareWidget *_ui;
|
||||
QSharedPointer<Share> _share;
|
||||
bool _showDetailedPermissions;
|
||||
bool _isFile;
|
||||
|
||||
QAction *_permissionCreate;
|
||||
QAction *_permissionUpdate;
|
||||
QAction *_permissionDelete;
|
||||
};
|
||||
|
||||
|
||||
@@ -102,6 +106,7 @@ private slots:
|
||||
|
||||
void slotCompleterActivated(const QModelIndex & index);
|
||||
void slotShareesReady();
|
||||
void slotAdjustScrollWidgetSize();
|
||||
|
||||
private:
|
||||
Ui::ShareUserGroupWidget *_ui;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="shareeLineEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Share with users, groups or remote users ...</string>
|
||||
<string>Share with users or groups ...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -32,9 +32,6 @@
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>474</width>
|
||||
<height>108</height>
|
||||
<width>468</width>
|
||||
<height>64</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -46,56 +46,38 @@
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
<height>15</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Permissions</string>
|
||||
<widget class="QFrame" name="frame">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="permissionShare">
|
||||
<property name="text">
|
||||
<string>Can Share</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="permissionsEdit">
|
||||
<property name="text">
|
||||
<string>Can Edit</string>
|
||||
<string>can edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="permissionCreate">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="permissionShare">
|
||||
<property name="text">
|
||||
<string>Create</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="permissionUpdate">
|
||||
<property name="text">
|
||||
<string>Change</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="permissionDelete">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
<string>can share</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="permissionToggleButton">
|
||||
<widget class="QToolButton" name="permissionToolButton">
|
||||
<property name="text">
|
||||
<string>More</string>
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -378,12 +378,23 @@ void SocketApi::command_SHARE(const QString& localFile, QIODevice* socket)
|
||||
// if the folder isn't connected, don't open the share dialog
|
||||
sendMessage(socket, message);
|
||||
} else {
|
||||
const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length()+1);
|
||||
SyncFileStatus fileStatus = this->fileStatus(shareFolder, file);
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::STATUS_SYNC &&
|
||||
fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) {
|
||||
const QString message = QLatin1String("SHARE:NOTSYNCED:")+QDir::toNativeSeparators(localFile);
|
||||
sendMessage(socket, message);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString folderForPath = shareFolder->path();
|
||||
const QString remotePath = shareFolder->remotePath() + localFile.right(localFile.count()-folderForPath.count()+1);
|
||||
|
||||
// Can't share root folder
|
||||
if (QDir::cleanPath(remotePath) == "/") {
|
||||
const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:")+QDir::toNativeSeparators(localFile);
|
||||
const QString message = QLatin1String("SHARE:CANNOTSHAREROOT:")+QDir::toNativeSeparators(localFile);
|
||||
sendMessage(socket, message);
|
||||
return;
|
||||
}
|
||||
@@ -424,6 +435,17 @@ void SocketApi::command_SHARE_STATUS(const QString &localFile, QIODevice *socket
|
||||
const QString message = QLatin1String("SHARE_STATUS:NOP:")+QDir::toNativeSeparators(localFile);
|
||||
sendMessage(socket, message);
|
||||
} else {
|
||||
const QString file = QDir::cleanPath(localFile).mid(shareFolder->cleanPath().length()+1);
|
||||
SyncFileStatus fileStatus = this->fileStatus(shareFolder, file);
|
||||
|
||||
// Verify the file is on the server (to our knowledge of course)
|
||||
if (fileStatus.tag() != SyncFileStatus::STATUS_SYNC &&
|
||||
fileStatus.tag() != SyncFileStatus::STATUS_UPDATED) {
|
||||
const QString message = QLatin1String("SHARE_STATUS:NOTSYNCED:")+QDir::toNativeSeparators(localFile);
|
||||
sendMessage(socket, message);
|
||||
return;
|
||||
}
|
||||
|
||||
const Capabilities capabilities = shareFolder->accountState()->account()->capabilities();
|
||||
|
||||
if (!capabilities.shareAPI()) {
|
||||
|
||||
@@ -62,7 +62,7 @@ set(libsync_SRCS
|
||||
theme.cpp
|
||||
utility.cpp
|
||||
ownsql.cpp
|
||||
transmissionchecksumvalidator.cpp
|
||||
checksums.cpp
|
||||
excludedfiles.cpp
|
||||
creds/dummycredentials.cpp
|
||||
creds/abstractcredentials.cpp
|
||||
|
||||
@@ -57,6 +57,8 @@ public:
|
||||
QByteArray responseTimestamp();
|
||||
quint64 duration();
|
||||
|
||||
qint64 timeoutMsec() { return _timer.interval(); }
|
||||
|
||||
public slots:
|
||||
void setTimeout(qint64 msec);
|
||||
void resetTimeout();
|
||||
|
||||
@@ -497,6 +497,15 @@ bool Account::rootEtagChangesNotOnlySubFolderEtags()
|
||||
return (serverVersionInt() >= 0x080100);
|
||||
}
|
||||
|
||||
void Account::setNonShib(bool nonShib)
|
||||
{
|
||||
if( nonShib ) {
|
||||
_davPath = Theme::instance()->webDavPathNonShib();
|
||||
} else {
|
||||
_davPath = Theme::instance()->webDavPath();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
*/
|
||||
QString davPath() const;
|
||||
void setDavPath(const QString&s) { _davPath = s; }
|
||||
void setNonShib(bool nonShib);
|
||||
|
||||
static AccountPtr create();
|
||||
~Account();
|
||||
|
||||
@@ -37,7 +37,12 @@ bool Capabilities::shareAPI() const
|
||||
|
||||
bool Capabilities::sharePublicLink() const
|
||||
{
|
||||
return _capabilities["files_sharing"].toMap()["public"].toMap()["enabled"].toBool();
|
||||
if (_capabilities["files_sharing"].toMap().contains("public")) {
|
||||
return shareAPI() && _capabilities["files_sharing"].toMap()["public"].toMap()["enabled"].toBool();
|
||||
} else {
|
||||
// This was later added so if it is not present just assume that link sharing is enabled.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Capabilities::sharePublicLinkAllowUpload() const
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
#include "config.h"
|
||||
#include "filesystem.h"
|
||||
#include "transmissionchecksumvalidator.h"
|
||||
#include "checksums.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "propagatorjobs.h"
|
||||
#include "account.h"
|
||||
@@ -77,36 +77,40 @@ QByteArray ComputeChecksum::checksumType() const
|
||||
|
||||
void ComputeChecksum::start(const QString& filePath)
|
||||
{
|
||||
const QString csType = checksumType();
|
||||
|
||||
// Calculate the checksum in a different thread first.
|
||||
connect( &_watcher, SIGNAL(finished()),
|
||||
this, SLOT(slotCalculationDone()),
|
||||
Qt::UniqueConnection );
|
||||
if( csType == checkSumMD5C ) {
|
||||
_watcher.setFuture(QtConcurrent::run(FileSystem::calcMd5, filePath));
|
||||
_watcher.setFuture(QtConcurrent::run(ComputeChecksum::computeNow, filePath, checksumType()));
|
||||
}
|
||||
|
||||
} else if( csType == checkSumSHA1C ) {
|
||||
_watcher.setFuture(QtConcurrent::run( FileSystem::calcSha1, filePath));
|
||||
QByteArray ComputeChecksum::computeNow(const QString& filePath, const QByteArray& checksumType)
|
||||
{
|
||||
if( checksumType == checkSumMD5C ) {
|
||||
return FileSystem::calcMd5(filePath);
|
||||
} else if( checksumType == checkSumSHA1C ) {
|
||||
return FileSystem::calcSha1(filePath);
|
||||
}
|
||||
#ifdef ZLIB_FOUND
|
||||
else if( csType == checkSumAdlerC) {
|
||||
_watcher.setFuture(QtConcurrent::run(FileSystem::calcAdler32, filePath));
|
||||
else if( checksumType == checkSumAdlerC) {
|
||||
return FileSystem::calcAdler32(filePath);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
// for an unknown checksum or no checksum, we're done right now
|
||||
if( !csType.isEmpty() ) {
|
||||
qDebug() << "Unknown checksum type:" << csType;
|
||||
}
|
||||
emit done(QByteArray(), QByteArray());
|
||||
// for an unknown checksum or no checksum, we're done right now
|
||||
if( !checksumType.isEmpty() ) {
|
||||
qDebug() << "Unknown checksum type:" << checksumType;
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
void ComputeChecksum::slotCalculationDone()
|
||||
{
|
||||
QByteArray checksum = _watcher.future().result();
|
||||
emit done(_checksumType, checksum);
|
||||
if (!checksum.isNull()) {
|
||||
emit done(_checksumType, checksum);
|
||||
} else {
|
||||
emit done(QByteArray(), QByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,4 +155,41 @@ void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray& checksumTy
|
||||
emit validated(checksumType, checksum);
|
||||
}
|
||||
|
||||
CSyncChecksumHook::CSyncChecksumHook(SyncJournalDb *journal)
|
||||
: _journal(journal)
|
||||
{
|
||||
}
|
||||
|
||||
const char* CSyncChecksumHook::hook(const char* path, uint32_t checksumTypeId, void *this_obj)
|
||||
{
|
||||
CSyncChecksumHook* checksumHook = static_cast<CSyncChecksumHook*>(this_obj);
|
||||
QByteArray checksum = checksumHook->compute(QString::fromUtf8(path), checksumTypeId);
|
||||
if (checksum.isNull()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* result = (char*)malloc(checksum.size() + 1);
|
||||
memcpy(result, checksum.constData(), checksum.size());
|
||||
result[checksum.size()] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray CSyncChecksumHook::compute(const QString& path, int checksumTypeId)
|
||||
{
|
||||
QByteArray checksumType = _journal->getChecksumType(checksumTypeId);
|
||||
if (checksumType.isEmpty()) {
|
||||
qDebug() << "Checksum type" << checksumTypeId << "not found";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray checksum = ComputeChecksum::computeNow(path, checksumType);
|
||||
if (checksum.isNull()) {
|
||||
qDebug() << "Failed to compute checksum" << checksumType << "for" << path;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class SyncJournalDb;
|
||||
|
||||
/// Creates a checksum header from type and value.
|
||||
QByteArray makeChecksumHeader(const QByteArray& checksumType, const QByteArray& checksum);
|
||||
|
||||
@@ -59,6 +61,11 @@ public:
|
||||
*/
|
||||
void start(const QString& filePath);
|
||||
|
||||
/**
|
||||
* Computes the checksum synchronously.
|
||||
*/
|
||||
static QByteArray computeNow(const QString& filePath, const QByteArray& checksumType);
|
||||
|
||||
signals:
|
||||
void done(const QByteArray& checksumType, const QByteArray& checksum);
|
||||
|
||||
@@ -103,4 +110,29 @@ private:
|
||||
QByteArray _expectedChecksum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hooks checksum computations into csync.
|
||||
* @ingroup libsync
|
||||
*/
|
||||
class OWNCLOUDSYNC_EXPORT CSyncChecksumHook : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CSyncChecksumHook(SyncJournalDb* journal);
|
||||
|
||||
/**
|
||||
* Returns the checksum value for \a path for the given \a checksumTypeId.
|
||||
*
|
||||
* Called from csync, where a instance of CSyncChecksumHook has
|
||||
* to be set as userdata.
|
||||
* The return value will be owned by csync.
|
||||
*/
|
||||
static const char* hook(const char* path, uint32_t checksumTypeId, void* this_obj);
|
||||
|
||||
QByteArray compute(const QString& path, int checksumTypeId);
|
||||
|
||||
private:
|
||||
SyncJournalDb* _journal;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
namespace OCC {
|
||||
|
||||
// Make sure the timeout for this job is less than how often we get called
|
||||
// This makes sure we get tried often enough without "ConnectionValidator already running"
|
||||
static qint64 timeoutToUseMsec = qMax(qint64(1000), ConnectionValidator::defaultCallingIntervalMsec() - 5*1000);
|
||||
|
||||
ConnectionValidator::ConnectionValidator(AccountPtr account, QObject *parent)
|
||||
: QObject(parent),
|
||||
_account(account),
|
||||
@@ -99,6 +103,7 @@ void ConnectionValidator::systemProxyLookupDone(const QNetworkProxy &proxy) {
|
||||
void ConnectionValidator::slotCheckServerAndAuth()
|
||||
{
|
||||
CheckServerJob *checkJob = new CheckServerJob(_account, this);
|
||||
checkJob->setTimeout(timeoutToUseMsec);
|
||||
checkJob->setIgnoreCredentialFailure(true);
|
||||
connect(checkJob, SIGNAL(instanceFound(QUrl,QVariantMap)), SLOT(slotStatusFound(QUrl,QVariantMap)));
|
||||
connect(checkJob, SIGNAL(networkError(QNetworkReply*)), SLOT(slotNoStatusFound(QNetworkReply*)));
|
||||
@@ -165,6 +170,7 @@ void ConnectionValidator::checkAuthentication()
|
||||
// continue in slotAuthCheck here :-)
|
||||
qDebug() << "# Check whether authenticated propfind works.";
|
||||
PropfindJob *job = new PropfindJob(_account, "/", this);
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
job->setProperties(QList<QByteArray>() << "getlastmodified");
|
||||
connect(job, SIGNAL(result(QVariantMap)), SLOT(slotAuthSuccess()));
|
||||
connect(job, SIGNAL(networkError(QNetworkReply*)), SLOT(slotAuthFailed(QNetworkReply*)));
|
||||
@@ -209,7 +215,7 @@ void ConnectionValidator::slotAuthSuccess()
|
||||
void ConnectionValidator::checkServerCapabilities()
|
||||
{
|
||||
JsonApiJob *job = new JsonApiJob(_account, QLatin1String("ocs/v1.php/cloud/capabilities"), this);
|
||||
QObject::connect(job, SIGNAL(jsonRecieved(QVariantMap)), this, SLOT(slotCapabilitiesRecieved(QVariantMap)));
|
||||
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), this, SLOT(slotCapabilitiesRecieved(QVariantMap)));
|
||||
job->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,9 @@ public:
|
||||
|
||||
static QString statusString( Status );
|
||||
|
||||
// How often should the Application ask this object to check for the connection?
|
||||
static qint64 defaultCallingIntervalMsec() { return 32 * 1000;}
|
||||
|
||||
public slots:
|
||||
/// Checks the server and the authentication.
|
||||
void checkServerAndAuth();
|
||||
|
||||
@@ -160,8 +160,10 @@ int get_errno_from_http_errcode( int err, const QString & reason ) {
|
||||
case 423: /* Locked */
|
||||
new_errno = EACCES;
|
||||
break;
|
||||
case 400: /* Bad Request */
|
||||
case 403: /* Forbidden */
|
||||
new_errno = ERRNO_FORBIDDEN;
|
||||
break;
|
||||
case 400: /* Bad Request */
|
||||
case 409: /* Conflict */
|
||||
case 411: /* Length Required */
|
||||
case 412: /* Precondition Failed */
|
||||
|
||||
@@ -143,6 +143,19 @@ void FileSystem::setFileReadOnly(const QString& filename, bool readonly)
|
||||
file.setPermissions(permissions);
|
||||
}
|
||||
|
||||
|
||||
void FileSystem::setFileReadOnlyWeak(const QString& filename, bool readonly)
|
||||
{
|
||||
QFile file(filename);
|
||||
QFile::Permissions permissions = file.permissions();
|
||||
|
||||
if (!readonly && (permissions & QFile::WriteOwner)) {
|
||||
return; // already writable enough
|
||||
}
|
||||
|
||||
setFileReadOnly(filename, readonly);
|
||||
}
|
||||
|
||||
time_t FileSystem::getModTime(const QString &filename)
|
||||
{
|
||||
csync_vio_file_stat_t* stat = csync_vio_file_stat_new();
|
||||
|
||||
@@ -55,6 +55,17 @@ void OWNCLOUDSYNC_EXPORT setFileHidden(const QString& filename, bool hidden);
|
||||
*/
|
||||
void OWNCLOUDSYNC_EXPORT setFileReadOnly(const QString& filename, bool readonly);
|
||||
|
||||
/**
|
||||
* @brief Marks the file as read-only.
|
||||
*
|
||||
* It's like setFileReadOnly(), but weaker: if readonly is false and the user
|
||||
* already has write permissions, no change to the permissions is made.
|
||||
*
|
||||
* This means that it will preserve explicitly set rw-r--r-- permissions even
|
||||
* when the umask is 0002. (setFileReadOnly() would adjust to rw-rw-r--)
|
||||
*/
|
||||
void OWNCLOUDSYNC_EXPORT setFileReadOnlyWeak(const QString& filename, bool readonly);
|
||||
|
||||
/** convert a "normal" windows path into a path that can be 32k chars long. */
|
||||
QString OWNCLOUDSYNC_EXPORT longWinPath( const QString& inpath );
|
||||
|
||||
|
||||
@@ -565,6 +565,81 @@ bool PropfindJob::finished()
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
ProppatchJob::ProppatchJob(AccountPtr account, const QString &path, QObject *parent)
|
||||
: AbstractNetworkJob(account, path, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ProppatchJob::start()
|
||||
{
|
||||
if (_properties.isEmpty()) {
|
||||
qWarning() << "Proppatch with no properties!";
|
||||
}
|
||||
QNetworkRequest req;
|
||||
|
||||
QByteArray propStr;
|
||||
QMapIterator<QByteArray, QByteArray> it(_properties);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
QByteArray keyName = it.key();
|
||||
QByteArray keyNs;
|
||||
if (keyName.contains(':')) {
|
||||
int colIdx = keyName.lastIndexOf(":");
|
||||
keyNs = keyName.left(colIdx);
|
||||
keyName = keyName.mid(colIdx+1);
|
||||
}
|
||||
|
||||
propStr += " <" + keyName;
|
||||
if (!keyNs.isEmpty()) {
|
||||
propStr += " xmlns=\"" + keyNs + "\" ";
|
||||
}
|
||||
propStr += ">";
|
||||
propStr += it.value();
|
||||
propStr += "</" + keyName + ">\n";
|
||||
}
|
||||
QByteArray xml = "<?xml version=\"1.0\" ?>\n"
|
||||
"<d:propertyupdate xmlns:d=\"DAV:\">\n"
|
||||
" <d:set><d:prop>\n"
|
||||
+ propStr +
|
||||
" </d:prop></d:set>\n"
|
||||
"</d:propertyupdate>\n";
|
||||
|
||||
QBuffer *buf = new QBuffer(this);
|
||||
buf->setData(xml);
|
||||
buf->open(QIODevice::ReadOnly);
|
||||
setReply(davRequest("PROPPATCH", path(), req, buf));
|
||||
buf->setParent(reply());
|
||||
setupConnections(reply());
|
||||
AbstractNetworkJob::start();
|
||||
}
|
||||
|
||||
void ProppatchJob::setProperties(QMap<QByteArray, QByteArray> properties)
|
||||
{
|
||||
_properties = properties;
|
||||
}
|
||||
|
||||
QMap<QByteArray, QByteArray> ProppatchJob::properties() const
|
||||
{
|
||||
return _properties;
|
||||
}
|
||||
|
||||
bool ProppatchJob::finished()
|
||||
{
|
||||
int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (http_result_code == 207) {
|
||||
emit success();
|
||||
} else {
|
||||
qDebug() << "PROPPATCH request *not* successful, http result code is" << http_result_code
|
||||
<< (http_result_code == 302 ? reply()->header(QNetworkRequest::LocationHeader).toString() : QLatin1String(""));
|
||||
emit finishedWithError();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
EntityExistsJob::EntityExistsJob(AccountPtr account, const QString &path, QObject *parent)
|
||||
: AbstractNetworkJob(account, path, parent)
|
||||
{
|
||||
@@ -608,23 +683,40 @@ void JsonApiJob::start()
|
||||
|
||||
bool JsonApiJob::finished()
|
||||
{
|
||||
int statusCode = 0;
|
||||
|
||||
if (reply()->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "Network error: " << path() << reply()->errorString() << reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
emit jsonRecieved(QVariantMap());
|
||||
emit jsonReceived(QVariantMap(), statusCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
QString jsonStr = QString::fromUtf8(reply()->readAll());
|
||||
if( jsonStr.contains( "<?xml version=\"1.0\"?>") ) {
|
||||
QRegExp rex("<statuscode>(\\d+)</statuscode>");
|
||||
if( jsonStr.contains(rex) ) {
|
||||
// this is a error message coming back from ocs.
|
||||
statusCode = rex.cap(1).toInt();
|
||||
}
|
||||
|
||||
} else {
|
||||
QRegExp rex("\"statuscode\":(\\d+),");
|
||||
// example: "{"ocs":{"meta":{"status":"ok","statuscode":100,"message":null},"data":{"version":{"major":8,"minor":"... (504)
|
||||
if( jsonStr.contains(rex) ) {
|
||||
statusCode = rex.cap(1).toInt();
|
||||
}
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
QVariantMap json = QtJson::parse(jsonStr, success).toMap();
|
||||
// empty or invalid response
|
||||
if (!success || json.isEmpty()) {
|
||||
qWarning() << "invalid JSON!" << jsonStr;
|
||||
emit jsonRecieved(QVariantMap());
|
||||
emit jsonReceived(QVariantMap(), statusCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
emit jsonRecieved(json);
|
||||
emit jsonReceived(json, statusCode);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,43 @@ private:
|
||||
QList<QByteArray> _properties;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Send a Proppatch request
|
||||
*
|
||||
* Setting the desired properties with setProperties() is mandatory.
|
||||
*
|
||||
* WARNING: Untested!
|
||||
*
|
||||
* @ingroup libsync
|
||||
*/
|
||||
class OWNCLOUDSYNC_EXPORT ProppatchJob : public AbstractNetworkJob {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProppatchJob(AccountPtr account, const QString &path, QObject *parent = 0);
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
|
||||
/**
|
||||
* Used to specify which properties shall be set.
|
||||
*
|
||||
* The property keys can
|
||||
* - contain no colon: they refer to a property in the DAV: namespace
|
||||
* - contain a colon: and thus specify an explicit namespace,
|
||||
* e.g. "ns:with:colons:bar", which is "bar" in the "ns:with:colons" namespace
|
||||
*/
|
||||
void setProperties(QMap<QByteArray, QByteArray> properties);
|
||||
QMap<QByteArray, QByteArray> properties() const;
|
||||
|
||||
signals:
|
||||
void success();
|
||||
void finishedWithError();
|
||||
|
||||
private slots:
|
||||
virtual bool finished() Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
QMap<QByteArray, QByteArray> _properties;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The MkColJob class
|
||||
* @ingroup libsync
|
||||
@@ -199,7 +236,7 @@ private slots:
|
||||
* To be used like this:
|
||||
* \code
|
||||
* _job = new JsonApiJob(account, QLatin1String("ocs/v1.php/foo/bar"), this);
|
||||
* connect(job, SIGNAL(jsonRecieved(QVariantMap)), ...)
|
||||
* connect(job, SIGNAL(jsonReceived(QVariantMap)), ...)
|
||||
* The received QVariantMap is empty in case of error or otherwise is a map as parsed by QtJson
|
||||
* \encode
|
||||
*
|
||||
@@ -221,12 +258,20 @@ public:
|
||||
* This function needs to be called before start() obviously.
|
||||
*/
|
||||
void addQueryParams(QList< QPair<QString,QString> > params);
|
||||
|
||||
public slots:
|
||||
void start() Q_DECL_OVERRIDE;
|
||||
protected:
|
||||
bool finished() Q_DECL_OVERRIDE;
|
||||
signals:
|
||||
void jsonRecieved(const QVariantMap &json);
|
||||
|
||||
/**
|
||||
* @brief jsonReceived - signal to report the json answer from ocs
|
||||
* @param json - the raw json string
|
||||
* @param statusCode - the OCS status code: 100 (!) for success
|
||||
*/
|
||||
void jsonReceived(const QVariantMap &json, int statusCode);
|
||||
|
||||
private:
|
||||
QList< QPair<QString,QString> > _additionalParams;
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include "utility.h"
|
||||
#include "filesystem.h"
|
||||
#include "propagatorjobs.h"
|
||||
#include "transmissionchecksumvalidator.h"
|
||||
#include "checksums.h"
|
||||
|
||||
#include <json.h>
|
||||
#include <QNetworkAccessManager>
|
||||
@@ -351,10 +351,7 @@ void PropagateDownloadFileQNAM::start()
|
||||
if (_resumeStart == _item->_size) {
|
||||
qDebug() << "File is already complete, no need to download";
|
||||
_tmpFile.close();
|
||||
|
||||
// Unfortunately we lost the checksum header, if any...
|
||||
QByteArray noChecksumData;
|
||||
downloadFinished(noChecksumData, noChecksumData);
|
||||
downloadFinished();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -537,7 +534,7 @@ void PropagateDownloadFileQNAM::slotGetFinished()
|
||||
// as this is (still) also correct.
|
||||
ValidateChecksumHeader *validator = new ValidateChecksumHeader(this);
|
||||
connect(validator, SIGNAL(validated(QByteArray,QByteArray)),
|
||||
SLOT(downloadFinished(QByteArray,QByteArray)));
|
||||
SLOT(downloadFinished()));
|
||||
connect(validator, SIGNAL(validationFailed(QString)),
|
||||
SLOT(slotChecksumFail(QString)));
|
||||
auto checksumHeader = job->reply()->rawHeader(checkSumHeaderC);
|
||||
@@ -621,13 +618,8 @@ static void handleRecallFile(const QString &fn)
|
||||
}
|
||||
} // end namespace
|
||||
|
||||
void PropagateDownloadFileQNAM::downloadFinished(const QByteArray& checksumType, const QByteArray& checksum)
|
||||
void PropagateDownloadFileQNAM::downloadFinished()
|
||||
{
|
||||
if (!checksumType.isEmpty()) {
|
||||
_item->_transmissionChecksum = checksum;
|
||||
_item->_transmissionChecksumType = checksumType;
|
||||
}
|
||||
|
||||
QString fn = _propagator->getFilePath(_item->_file);
|
||||
|
||||
// In case of file name clash, report an error
|
||||
@@ -679,8 +671,8 @@ void PropagateDownloadFileQNAM::downloadFinished(const QByteArray& checksumType,
|
||||
}
|
||||
|
||||
// Apply the remote permissions
|
||||
FileSystem::setFileReadOnly(_tmpFile.fileName(),
|
||||
!_item->_remotePerm.contains('W'));
|
||||
FileSystem::setFileReadOnlyWeak(_tmpFile.fileName(),
|
||||
!_item->_remotePerm.contains('W'));
|
||||
|
||||
QString error;
|
||||
_propagator->addTouchedFile(fn);
|
||||
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
private slots:
|
||||
void slotGetFinished();
|
||||
void abort() Q_DECL_OVERRIDE;
|
||||
void downloadFinished(const QByteArray& checksumType, const QByteArray& checksum);
|
||||
void downloadFinished();
|
||||
void slotDownloadProgress(qint64,qint64);
|
||||
void slotChecksumFail( const QString& errMsg );
|
||||
|
||||
|
||||