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

Compare commits

...

117 Commits

Author SHA1 Message Date
Markus Goetz
820899d071 VERSION.cmake: 2.3.2 2017-05-08 15:53:10 +02:00
Markus Goetz
ea691aa2a0 ChangeLog: 2.3.2 2017-05-08 15:50:09 +02:00
Jenkins for ownCloud
9597c2b808 [tx-robot] updated from transifex 2017-05-08 02:18:29 +02:00
Jenkins for ownCloud
3817202b9c [tx-robot] updated from transifex 2017-05-07 02:18:30 +02:00
Jenkins for ownCloud
ed960e5a71 [tx-robot] updated from transifex 2017-05-06 02:18:30 +02:00
Jenkins for ownCloud
8b08ca2d00 [tx-robot] updated from transifex 2017-05-05 02:18:28 +02:00
Jenkins for ownCloud
b20adf2281 [tx-robot] updated from transifex 2017-05-04 02:18:38 +02:00
Jenkins for ownCloud
b02f724e7e [tx-robot] updated from transifex 2017-05-03 02:18:29 +02:00
Jenkins for ownCloud
6d2f77851e [tx-robot] updated from transifex 2017-05-02 02:18:27 +02:00
Jenkins for ownCloud
131055aefc [tx-robot] updated from transifex 2017-05-01 02:18:29 +02:00
Jenkins for ownCloud
0682f8793c [tx-robot] updated from transifex 2017-04-30 02:18:28 +02:00
Jenkins for ownCloud
fdf4a53283 [tx-robot] updated from transifex 2017-04-29 02:18:29 +02:00
Jenkins for ownCloud
ccc1db02a0 [tx-robot] updated from transifex 2017-04-28 02:18:30 +02:00
Jenkins for ownCloud
4e15e2055c [tx-robot] updated from transifex 2017-04-27 02:18:30 +02:00
Christian Kamm
f8c0e796df SqlDatabase: Ask freeSpace for directory, fix for Windows
See owncloud/enterprise#1955
2017-04-26 12:38:04 +02:00
Jenkins for ownCloud
fd40113e48 [tx-robot] updated from transifex 2017-04-26 02:18:39 +02:00
Jenkins for ownCloud
ca8b961e60 [tx-robot] updated from transifex 2017-04-25 02:18:37 +02:00
Markus Goetz
b6e3709e74 ChangeLog for 2.3.2 2017-04-24 12:08:36 +02:00
Jenkins for ownCloud
fd97a09e64 [tx-robot] updated from transifex 2017-04-24 02:18:29 +02:00
Jenkins for ownCloud
42d2594071 [tx-robot] updated from transifex 2017-04-23 02:18:28 +02:00
Jenkins for ownCloud
ff70cf9b5f [tx-robot] updated from transifex 2017-04-22 02:18:28 +02:00
Jenkins for ownCloud
9d06e99b58 [tx-robot] updated from transifex 2017-04-21 02:18:30 +02:00
ckamm
543e8a224c Fix a crash in ProxyAuthHandler (#5711)
See
https://sentry.io/owncloud/desktop-win-and-mac/issues/243433178/activity/
https://sentry.io/owncloud/desktop-win-and-mac/issues/234182688/activity/

The problem was that an account's QNetworkAccessManager can be deleted
when reentering the event loop.
2017-04-20 09:21:33 +02:00
ckamm
ea378fe837 FolderStatusModel: Fix crash for empty relativePath (#5713)
The problem was that split("", SkipEmptyParts) is the empty list.

See
https://sentry.io/owncloud/desktop-win-and-mac/issues/251167186/
2017-04-20 09:16:55 +02:00
Markus Goetz
de9ee295be Some Dialogs: Bring to top on tray click #5515 #5566 (#5664)
On my OS X, it might get hidden under other apps while I opened it and then want to quickly
verify something in another app.
2017-04-20 08:55:44 +02:00
Jenkins for ownCloud
3a8d6a6f16 [tx-robot] updated from transifex 2017-04-20 02:18:30 +02:00
Markus Goetz
5ac58d3b83 Server: Parse version from capabilities too #5691 (#5698)
Newer servers will have the option of hiding version, versionstring, edition
and productname. They will always send the full information in the capabilities.
2017-04-19 11:02:03 +02:00
Jenkins for ownCloud
866991df4c [tx-robot] updated from transifex 2017-04-19 02:18:38 +02:00
Jenkins for ownCloud
7613938181 [tx-robot] updated from transifex 2017-04-18 02:18:27 +02:00
Jenkins for ownCloud
4eb4069c15 [tx-robot] updated from transifex 2017-04-17 02:18:28 +02:00
Jenkins for ownCloud
f097e472f4 [tx-robot] updated from transifex 2017-04-16 02:18:27 +02:00
Jenkins for ownCloud
c46b97ec37 [tx-robot] updated from transifex 2017-04-15 02:18:27 +02:00
Jenkins for ownCloud
1994cd7ba2 [tx-robot] updated from transifex 2017-04-14 02:18:37 +02:00
Olivier Goffart
4af45394f9 owncloudcmd: return code depend on sync result
Issue #3936
2017-04-13 10:10:47 +02:00
Jenkins for ownCloud
c1c5a488d0 [tx-robot] updated from transifex 2017-04-13 02:18:32 +02:00
Jenkins for ownCloud
5e18d5cf4e [tx-robot] updated from transifex 2017-04-12 02:18:30 +02:00
Christian Kamm
8a639d39fc ShareDialog: Reenable user input textedit on error #5694 2017-04-11 15:50:32 +02:00
Jenkins for ownCloud
e43425cf16 [tx-robot] updated from transifex 2017-04-11 02:18:28 +02:00
Markus Goetz
26af3ce525 Wizard: Improve folder creation (2) 2017-04-10 16:15:28 +02:00
Jenkins for ownCloud
5f316f8b96 [tx-robot] updated from transifex 2017-04-10 02:18:28 +02:00
Jenkins for ownCloud
35060f4954 [tx-robot] updated from transifex 2017-04-09 02:18:27 +02:00
Jenkins for ownCloud
0b2f1dda48 [tx-robot] updated from transifex 2017-04-08 02:18:38 +02:00
Jenkins for ownCloud
1a555449ec [tx-robot] updated from transifex 2017-04-08 01:15:19 +02:00
Jenkins for ownCloud
5ba61e5399 [tx-robot] updated from transifex 2017-04-07 02:18:29 +02:00
Jenkins for ownCloud
0809703d2b [tx-robot] updated from transifex 2017-04-06 02:18:28 +02:00
Jenkins for ownCloud
55b423fbb5 [tx-robot] updated from transifex 2017-04-06 01:15:15 +02:00
Markus Goetz
e08ec11fd4 Wizard: Improve folder creation 2017-04-05 16:55:52 +02:00
Jenkins for ownCloud
0c2f65a939 [tx-robot] updated from transifex 2017-04-05 02:18:28 +02:00
Christian Kamm
29e34bd2c0 Doc: Update to new sync journal db name #5662 2017-04-04 10:49:26 +02:00
Christian Kamm
6abc2bf72d Transfers: Show a decimal for single digit MB rates #4428 2017-04-04 09:54:06 +02:00
Christian Kamm
a10fe84a83 ShareDialog: Hide the detailed permissions if there is only one #5655 2017-04-04 09:40:48 +02:00
Christian Kamm
7d9c4d052c ShareDialog: Margin and spacing refinements #5627 2017-04-04 09:40:48 +02:00
Christian Kamm
f8d69dfe8e ShareDialog: Fix thumbnail size #5654 2017-04-04 09:40:48 +02:00
Jenkins for ownCloud
ce735d8d68 [tx-robot] updated from transifex 2017-04-04 02:18:31 +02:00
Jenkins for ownCloud
484c8397e3 [tx-robot] updated from transifex 2017-04-03 02:18:28 +02:00
Jenkins for ownCloud
b8de36c585 [tx-robot] updated from transifex 2017-04-02 02:18:27 +02:00
Jenkins for ownCloud
59ba292edd [tx-robot] updated from transifex 2017-04-01 02:18:36 +02:00
Jenkins for ownCloud
bc9c4fe17b [tx-robot] updated from transifex 2017-03-31 02:18:27 +02:00
Jenkins for ownCloud
7253f64d26 [tx-robot] updated from transifex 2017-03-30 02:18:30 +02:00
Markus Goetz
28e9c56a4b Chunking NG: Also send OC-Total-Length on MOVE
For https://github.com/owncloud/core/issues/26988
2017-03-29 19:51:02 +02:00
Jenkins for ownCloud
f1a35527ee [tx-robot] updated from transifex 2017-03-29 02:18:28 +02:00
Olivier Goffart
039ff0ca8e PropagateDownload: fix possible crash
Backtrace looks like this:
Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0x0
  File "propagatedownload.cpp", line 234, in OCC::GETFileJob::slotReadyRead
  File "moc_propagatedownl_CA5CFSHZDTX34X.cpp", line 86, in OCC::GETFileJob::qt_static_metacall
  File "qobject.cpp", line 495, in QMetaCallEvent::placeMetaCall
  File "qobject.cpp", line 1256, in QObject::event
  File "qapplication.cpp", line 3804, in QApplicationPrivate::notify_helper

GETFileJob::slotReadyRead can be called with a QueuedConnection when the
bendwith manager is involved. In that case, if the QNAM was reset
in between, the reply might have been destroyed.
(This is only speculation based on the backtrace)
2017-03-28 18:04:19 +02:00
Olivier Goffart
85afa4788b AccountState: Attempt to fix a crash
Backtrace from the crash reporter:

Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0x21
  File "qcoreapplication.cpp", line 1281, in QCoreApplication::postEvent
  File "qobject.cpp", line 2125, in QObject::deleteLater
  File "connectionvalidator.cpp", line 240, in OCC::ConnectionValidator::reportResult
  File "connectionvalidator.cpp", line 206, in OCC::ConnectionValidator::slotAuthFailed
  File "moc_connectionvalidator.cpp", line 127, in OCC::ConnectionValidator::qt_static_metacall
  File "qobject.cpp", line 3716, in QMetaObject::activate
  File "moc_networkjobs.cpp", line 653, in OCC::PropfindJob::finishedWithError
  File "networkjobs.cpp", line 570, in OCC::PropfindJob::finished

I believe the problem is caused because 'this' was deleted in ConnectionValidator::reportResult
as the signal connectionResult gets emited. The AccountState::slotConnectionValidatorResult
slot does indeed call slotInvalidCredentials which might call {Shibboleth,Http}Credentials::fetchFromKeychain
which might emit fetched directly, which will call AccountState::slotCredentialsFetched
which deletes the _connectionValidator

So use deleteLater when deleting the _connectionValidator, hoping this helps
2017-03-28 18:04:19 +02:00
Jenkins for ownCloud
3ca071c612 [tx-robot] updated from transifex 2017-03-28 02:18:27 +02:00
Jenkins for ownCloud
d78b84a12f [tx-robot] updated from transifex 2017-03-27 02:18:28 +02:00
Olivier Goffart
5c2b185374 VERSION: 2.3.1 was release, this branch is now for 2.3.2 2017-03-25 09:06:59 +01:00
Jenkins for ownCloud
8339ff3c1c [tx-robot] updated from transifex 2017-03-25 02:18:28 +01:00
Jenkins for ownCloud
73e58fa090 [tx-robot] updated from transifex 2017-03-25 01:15:15 +01:00
Jenkins for ownCloud
4e70a14c29 [tx-robot] updated from transifex 2017-03-24 02:18:28 +01:00
Jenkins for ownCloud
14131cf020 [tx-robot] updated from transifex 2017-03-23 02:18:37 +01:00
Jenkins for ownCloud
dd5b36779d [tx-robot] updated from transifex 2017-03-22 02:18:28 +01:00
Jenkins for ownCloud
34bb950889 [tx-robot] updated from transifex 2017-03-21 02:18:29 +01:00
Samuel Alfageme
489efa24f7 Fixing a typo on an issue reference
(cherry picked from commit ed03cdf45a)
2017-03-20 17:29:36 +01:00
Matthew Setter
9840ae9e96 Fix incorrect documentation for the ownCloud command-line client
This fixes #5609.
2017-03-20 17:29:16 +01:00
Samuel Alfageme
d375e9b86c Including some info on the proxy config. in the docs.
This was a request of @michaelstingl to document how to configure the proxy manually via the .cfg file for config provisioning purposes.
- Restructured the bullet points into 3 different tables; I believe it's a cleaner approach; maybe this is also applicable to other sections.
- Would be logical to include also settings on proxy auth?
2017-03-20 11:55:02 +01:00
Olivier Goffart
722918abd6 Folder::showSyncResultPopup: Fix undefined behavior when there is no errors
When there is no errors  _syncResult.firstItemError() is NULL, and accessing
it's _file member is an undefined behavior. (Thankfully, createGuiLog did not
use the string when the count was 0, but we are not supposed to create
null references.

Found with the UB sanitizer:

src/gui/folder.cpp:348:49: runtime error: member access within null pointer of type 'OCC::SyncFileItem'
src/gui/folder.cpp:348:19: runtime error: reference binding to null pointer of type 'const QString'
2017-03-20 11:53:13 +01:00
Jenkins for ownCloud
48fe8d1cb8 [tx-robot] updated from transifex 2017-03-20 02:18:28 +01:00
Jenkins for ownCloud
1000cdda09 [tx-robot] updated from transifex 2017-03-19 02:18:28 +01:00
Jenkins for ownCloud
d71b59b817 [tx-robot] updated from transifex 2017-03-18 02:18:35 +01:00
Jenkins for ownCloud
95b92c12ac [tx-robot] updated from transifex 2017-03-17 02:18:36 +01:00
Olivier Goffart
a60370255e ActivityListModel: attempt to fix a crash.
The backtrace seems to indicate that the account is invalid.
I don't know how this can happen, maybe the account's display
name was changed while the app is running?

Backtrace:
Crash: EXC_BAD_ACCESS / KERN_INVALID_ADDRESS at 0x18
  Module "owncloud", in OCC::AccountState::account
  Module "owncloud", in OCC::ActivityListModel::data
  Module "owncloud", in OCC::ActivityItemDelegate::paint
  Module "QtWidgets", in QListView::paintEvent
  Module "QtWidgets", in QWidget::event
  Module "QtWidgets", in QFrame::event
  Module "QtWidgets", in QAbstractScrollArea::viewportEvent
  Module "QtWidgets", in QAbstractItemView::viewportEvent
  Module "QtWidgets", in QAbstractScrollAreaFilter::eventFilter
  Module "QtCore", in QCoreApplicationPrivate::sendThroughObjectEventFilters
2017-03-16 14:31:15 +01:00
Olivier Goffart
21909cae04 Propagator: change order of destruction
The destructor of the PropagateItemJob will access the propagator's
_activeJobList. So the _rootJob needs to be destroyed before it.
Order of destruction is the reverse of the order of the members in
the class. So put it at the end so it can be destroyed first.

(This made TestSyncEngine::testDirDownloadWithError crash sometimes
in the master branch)
2017-03-16 14:24:25 +01:00
Markus Goetz
176ea20fef ChangeLog for 2.3.1 2017-03-16 14:19:23 +01:00
Christian Kamm
1a279ca158 About: Show the runtime versions of Qt and OpenSSL #5592
I didn't want to touch this visible translated string in a
patch release so I just switched the data.

For Qt versions before 5, we continue to show the build-time version
of OpenSSL but it's clearly marked as such.
2017-03-16 12:40:42 +01:00
Olivier Goffart
0ed929f24b SyncEngine: fix test regression in TestChunkingNG::testCreateConflictWhileSyncing
In 8ef11a38c9, we started blacklisting
SoftError for 0 seconds.  But if the two sync happen with less than
1s interval, we would still prevent them to happen.
So make sure we expire if 0 seconds have expired
2017-03-16 10:17:08 +01:00
Jenkins for ownCloud
41eeed981f [tx-robot] updated from transifex 2017-03-16 02:18:29 +01:00
Olivier Goffart
8e68e0321c Attempt to fix a crash in FolderStatusModel::slotUpdateDirectories
The backtrace looks like:

  File "atomic_base.h", line 396, in QString::~QString
  File "qlist.h", line 442, in OCC::FolderStatusModel::slotUpdateDirectories

This is the only QList operation, and it may crash if the list is empty.
It can be empty if the propfind returned empty results.
I'm not sure how this can be possible to have an empty list there since
the server is always supposed to return at least one entry, for the directory
itself. But it can happen if a directory was transformed in a file, or
if there is a bug on the server.
2017-03-15 17:23:39 +01:00
Christian Kamm
881b32521b HttpCreds: Update app passwords url #5605
See also owncloud/core#27360
2017-03-15 16:30:08 +01:00
Christian Kamm
b98876e265 Account server version: Helper to create versions
Hex literals don't work well with version 10: 0x100000 doesn't do
the right thing.
2017-03-15 16:30:08 +01:00
Olivier Goffart
dbb5bcb784 Ignore list editor: adjust the size
The previous patch on this file made it a bit too big, so make it a bit smaller
For issue #5600
2017-03-15 16:00:48 +01:00
Jenkins for ownCloud
d95b43f347 [tx-robot] updated from transifex 2017-03-15 02:18:36 +01:00
Christian Kamm
9ca9773e9d Add more versions to --version #5592
* Add git revision, if available
* Add SSL runtime version
* Add compile-time Qt version
2017-03-14 16:13:52 +01:00
Christian Kamm
13b2568358 Windows/NTFS: Do not attempt to upload inaccessible files #5544
It is possible to create files with filenames that differ
only by case in NTFS, but most operations such as stat and
open only target one of these by default.

When that happens, we want to avoid uploading incorrect data
and give up on the file.

Typically this situation should never occurr during normal use
of Windows. It can happen, however, when a NTFS partition is
mounted in another OS.
2017-03-14 16:03:08 +01:00
Jenkins for ownCloud
7a2d12bb47 [tx-robot] updated from transifex 2017-03-14 02:18:30 +01:00
Olivier Goffart
2152bc5fca Ignore list editor: adjust the size of the columns in the table
So the title of the column is fully visible.
Adjust the default size and the size policy of the label so it behave
correctly as the window get resized

Issue #5600
2017-03-13 17:05:24 +01:00
Jenkins for ownCloud
11aecf1af2 [tx-robot] updated from transifex 2017-03-13 02:18:30 +01:00
Jenkins for ownCloud
903d6c80fe [tx-robot] updated from transifex 2017-03-12 02:18:30 +01:00
Jenkins for ownCloud
585221c58c [tx-robot] updated from transifex 2017-03-11 02:18:38 +01:00
Christian Kamm
8ef11a38c9 Blacklist: Escalate repeated SoftError to NormalError #5500 2017-03-10 15:51:55 +01:00
Jenkins for ownCloud
f94a15b0fa [tx-robot] updated from transifex 2017-03-10 02:18:30 +01:00
Olivier Goffart
9c83c57f1c Propagator: Attempt to fix a crash in OwncloudPropagator::scheduleNextJob
The crash reporter shows many crashes in OwncloudPropagator::scheduleNextJob.
We don't really know what could be the cause, but it's probably because
the _activeJobList contains dangling pointer.
So this patch makes sure to remove all the jobs from this list as they get
destroyed.
2017-03-09 15:48:52 +01:00
Jenkins for ownCloud
1d0ef83078 [tx-robot] updated from transifex 2017-03-09 02:18:30 +01:00
Jenkins for ownCloud
4ab217ba04 [tx-robot] updated from transifex 2017-03-08 02:18:33 +01:00
Christian Kamm
4a1a5fa076 AbstractNetworkJob: Improve redirect handling #5555
* For requests:
  - reuse the original QNetworkRequest, so headers and attributes
    are the same as in the original request
  - determine the original http method from the reply and the request
    attributes
  - keep the original request body around such that it can be sent
    again in case the request is redirected

* Simplify the interface that is used for creating new requests in
  AbstractNetworkJob.
2017-03-07 13:18:01 +01:00
Jenkins for ownCloud
298684aaa0 [tx-robot] updated from transifex 2017-03-07 02:18:31 +01:00
Jenkins for ownCloud
005c027274 [tx-robot] updated from transifex 2017-03-07 01:15:15 +01:00
Olivier Goffart
f4495c5c80 ActivityListModel: fix possible crash in slotActivitiesReceived
Backtrace from the crash reporter:

Crash: EXCEPTION_ACCESS_VIOLATION_READ at 0x401
  File "moc_activitylistmo_M5OEXJ7XGJYTWT.cpp", line 92, in OCC::ActivityListModel::qt_static_metacall
  File "qobject.cpp", line 3730, in QMetaObject::activate
  File "moc_networkjobs_7AMNCW4BBANVRK.cpp", line 1342, in OCC::JsonApiJob::jsonReceived
  File "networkjobs.cpp", line 714, in OCC::JsonApiJob::finished
  File "abstractnetworkjob.cpp", line 207, in OCC::AbstractNetworkJob::slotFinished
  File "moc_abstractnetwor_PFI2TXGQHRE33H.cpp", line 98, in OCC::AbstractNetworkJob::qt_static_metacall
  File "qobject.cpp", line 3730, in QMetaObject::activate
  File "moc_qnetworkreply.cpp", line 367, in QNetworkReply::finished
  File "qnetworkreplyhttpimpl.cpp", line 2100, in QNetworkReplyHttpImplPrivate::finished
  File "qnetworkreplyhttpimpl.cpp", line 279, in QNetworkReplyHttpImpl::abort

My theory is that the AccountState stored in a property of the job was destroyed.
before the job timed out.
Therefore, the qobject_cast within the qvariant_cast would call the metaObject()
virtual function on a dangling pointer.

Fix it by storing a QPointer instead which will track the deletion.
2017-03-06 17:25:22 +01:00
Olivier Goffart
f862c626a1 Propagator: Fix finished signal of directory being emited twice and causing crash
When there is a FatalError, we ended up emiting the finished signal for
the directory job several times, which would lead to crashes

Issue #5578
2017-03-06 16:14:04 +01:00
Markus Goetz
6487bb071b NSIS: Don't display link on finish page 2017-03-06 15:40:10 +01:00
Jenkins for ownCloud
e7296d03d3 [tx-robot] updated from transifex 2017-03-05 02:18:29 +01:00
Jenkins for ownCloud
139bb8f094 [tx-robot] updated from transifex 2017-03-04 02:18:29 +01:00
Olivier Goffart
84ecbc4c89 2.3.0 was released: Update version 2017-03-03 10:48:47 +01:00
Olivier Goffart
4a010ce7cd SslButton: Remove additional ')'
Regression since 7bfe061382
Issue: #5573
2017-03-03 10:43:47 +01:00
Jenkins for ownCloud
40698c8fd0 [tx-robot] updated from transifex 2017-03-03 02:18:27 +01:00
Jenkins for ownCloud
3adaf44f7b [tx-robot] updated from transifex 2017-03-02 02:18:30 +01:00
Jenkins for ownCloud
3983fecaf1 [tx-robot] updated from transifex 2017-03-01 02:18:28 +01:00
Jenkins for ownCloud
7effcff245 [tx-robot] updated from transifex 2017-02-28 02:18:28 +01:00
92 changed files with 9293 additions and 8338 deletions

View File

@@ -1,7 +1,21 @@
ChangeLog ChangeLog
========= =========
version 2.3.0 (2017-02-xx) version 2.3.2 (2017-05-08)
* Fix more crashes (thanks to everyone submitting to our crash reporter!)
* Improve compatibility with server 10.0 (#5691, X-OC-Total-Size)
* Share dialog: UI improvements, Bring to front on tray click
* owncloudcmd: Align process return value with sync return value (#3936)
* Fix disk free check on Windows when opening the local DB
version 2.3.1 (2017-03-21)
* Fix several crashes (thanks to everyone submitting to our crash reporter!)
* Improve HTTP redirect handling (#5555)
* Blacklist: Escalate repeated soft error to normal error (#5500)
* NTFS: Do not attempt to upload two existing files with similar casing (#5544)
* Fix URL for linking to application password generation for ownCloud 10.0 (#5605)
version 2.3.0 (2017-03-03)
* Decreased memory usage during sync * Decreased memory usage during sync
* Overlay icons: Lower CPU usage * Overlay icons: Lower CPU usage
* Allow to not sync the server's external storages by default * Allow to not sync the server's external storages by default

View File

@@ -1,11 +1,11 @@
set( MIRALL_VERSION_MAJOR 2 ) set( MIRALL_VERSION_MAJOR 2 )
set( MIRALL_VERSION_MINOR 3 ) set( MIRALL_VERSION_MINOR 3 )
set( MIRALL_VERSION_PATCH 0 ) set( MIRALL_VERSION_PATCH 2 )
set( MIRALL_VERSION_YEAR 2016 ) set( MIRALL_VERSION_YEAR 2017 )
set( MIRALL_SOVERSION 0 ) set( MIRALL_SOVERSION 0 )
if ( NOT DEFINED MIRALL_VERSION_SUFFIX ) if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
set( MIRALL_VERSION_SUFFIX "git") #e.g. beta1, beta2, rc1 set( MIRALL_VERSION_SUFFIX "") #e.g. beta1, beta2, rc1
endif( NOT DEFINED MIRALL_VERSION_SUFFIX ) endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
if( NOT DEFINED MIRALL_VERSION_BUILD ) if( NOT DEFINED MIRALL_VERSION_BUILD )

View File

@@ -9,6 +9,7 @@ StrCpy $PageReinstall_NEW_Field_3 "Ez desinstalatu"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Dagoeneko Instalatuta" StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Dagoeneko Instalatuta"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Hautatu nola nahi duzun ${APPLICATION_NAME} instalatzea." StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Hautatu nola nahi duzun ${APPLICATION_NAME} instalatzea."
StrCpy $PageReinstall_OLD_Field_1 "${APPLICATION_NAME}ren bertsio berriago bat instalatuta dago! Ez da aholkatzen bertsio zaharrago bat instalatzea. Benetan bertsio zaharrago hau instalatu nahi baduzu, hobe da lehenengo bertsio berria desinstalatzea. Hautatu nahi duzun aukera eta sakatu Hurrengoa jarraitzeko." StrCpy $PageReinstall_OLD_Field_1 "${APPLICATION_NAME}ren bertsio berriago bat instalatuta dago! Ez da aholkatzen bertsio zaharrago bat instalatzea. Benetan bertsio zaharrago hau instalatu nahi baduzu, hobe da lehenengo bertsio berria desinstalatzea. Hautatu nahi duzun aukera eta sakatu Hurrengoa jarraitzeko."
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} dagoeneko instalatuta dago.$\nHautatu zer operazio egin nahi duzu eta klikatu Hurrengoa jarraitzeko."
StrCpy $PageReinstall_SAME_Field_2 "Gehitu/Berrinstalatu osagaiak" StrCpy $PageReinstall_SAME_Field_2 "Gehitu/Berrinstalatu osagaiak"
StrCpy $PageReinstall_SAME_Field_3 "Desinstalatu ${APPLICATION_NAME}" StrCpy $PageReinstall_SAME_Field_3 "Desinstalatu ${APPLICATION_NAME}"
StrCpy $UNINSTALLER_APPDATA_TITLE "Desinstalatu ${APPLICATION_NAME}" StrCpy $UNINSTALLER_APPDATA_TITLE "Desinstalatu ${APPLICATION_NAME}"
@@ -40,4 +41,3 @@ StrCpy $UAC_UNINSTALLER_REQUIRE_ADMIN "Desinstalatzaile honek administratzaile b
StrCpy $UAC_ERROR_LOGON_SERVICE "Saioa hasteko zerbitzua ez dago martxan, bertan behera uzten!" StrCpy $UAC_ERROR_LOGON_SERVICE "Saioa hasteko zerbitzua ez dago martxan, bertan behera uzten!"
StrCpy $INIT_UNINSTALLER_RUNNING "Desinstalatzailea dagoeneko martxan da." StrCpy $INIT_UNINSTALLER_RUNNING "Desinstalatzailea dagoeneko martxan da."
StrCpy $SectionGroup_Shortcuts "Lasterbideak" StrCpy $SectionGroup_Shortcuts "Lasterbideak"
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} is already installed.$\r$\nSelect the operation you want to perform and click Next to continue."

View File

@@ -25,7 +25,7 @@ StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Raccourci de lancement rapide"
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Création d'un raccourci de lancement rapide" StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Création d'un raccourci de lancement rapide"
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "Essentiels de ${APPLICATION_NAME}." StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "Essentiels de ${APPLICATION_NAME}."
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Raccourci de ${APPLICATION_NAME}" StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Raccourci de ${APPLICATION_NAME}"
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Raccourci Bureau de ${APPLICATION_NAME}." StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Raccourci de bureau pour ${APPLICATION_NAME}."
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Raccourci de lancement rapide de ${APPLICATION_NAME}." StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Raccourci de lancement rapide de ${APPLICATION_NAME}."
StrCpy $UNINSTALLER_FILE_Detail "Écriture du désinstallateur" StrCpy $UNINSTALLER_FILE_Detail "Écriture du désinstallateur"
StrCpy $UNINSTALLER_REGISTRY_Detail "Écriture des clefs de registre du désinstallateur" StrCpy $UNINSTALLER_REGISTRY_Detail "Écriture des clefs de registre du désinstallateur"

View File

@@ -8,7 +8,7 @@ StrCpy $PageReinstall_NEW_Field_2 "Desinstalar antes de instalar"
StrCpy $PageReinstall_NEW_Field_3 "Não desinstale" StrCpy $PageReinstall_NEW_Field_3 "Não desinstale"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Já instalado" StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Já instalado"
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Escolha como pretende instalar ${APPLICATION_NAME}." StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_SUBTITLE "Escolha como pretende instalar ${APPLICATION_NAME}."
StrCpy $PageReinstall_OLD_Field_1 "Uma versão mais recente da aplicação ${APPLICATION_NAME} já está instalada! Não é recomendada a instalação de uma versão mais antiga. Se realmente deseja instalar esta versão, aconselha-se a desinstalação da versão atual primeiro. Selecione a operação que deseja executar e clique em Avançar para continuar." StrCpy $PageReinstall_OLD_Field_1 "Já está instalada uma versão mais recente de ${APPLICATION_NAME}! Não é recomendada a instalação de uma versão mais antiga. Se realmente desejar instalar esta versão antiga, aconselha-se que desinstale primeiro a versão atual. Selecione a operação que deseja executar e clique em $\"Seguinte$\" para continuar."
StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} já está instalada.$\nSelecione a operação que deseja realizar e clique em 'Seguinte' para continuar." StrCpy $PageReinstall_SAME_Field_1 "${APPLICATION_NAME} ${VERSION} já está instalada.$\nSelecione a operação que deseja realizar e clique em 'Seguinte' para continuar."
StrCpy $PageReinstall_SAME_Field_2 "Adicionar/Reinstalar Componentes" StrCpy $PageReinstall_SAME_Field_2 "Adicionar/Reinstalar Componentes"
StrCpy $PageReinstall_SAME_Field_3 "Desinstalar ${APPLICATION_NAME}" StrCpy $PageReinstall_SAME_Field_3 "Desinstalar ${APPLICATION_NAME}"
@@ -17,7 +17,7 @@ StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Escolha a opção de manute
StrCpy $SEC_APPLICATION_DETAILS "A instalar o essencial de ${APPLICATION_NAME}." StrCpy $SEC_APPLICATION_DETAILS "A instalar o essencial de ${APPLICATION_NAME}."
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integração para Windows Explorer" StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integração para Windows Explorer"
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "A instalar integração para Windows Explorer" StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "A instalar integração para Windows Explorer"
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Atalho do progama no Menu Inicial" StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Atalho do progama no Menu Iniciar"
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "A adicionar o atalho de ${APPLICATION_NAME} ao Menu Inicial." StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "A adicionar o atalho de ${APPLICATION_NAME} ao Menu Inicial."
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Atalho da área de trabalho" StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Atalho da área de trabalho"
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "A criar atalhos na área de trabalho" StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "A criar atalhos na área de trabalho"

View File

@@ -116,8 +116,9 @@ ReserveFile "${NSISDIR}\Plugins\InstallOptions.dll"
!define MUI_HEADERIMAGE !define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP ${WIN_SETUP_BITMAP_PATH}/page_header.bmp !define MUI_HEADERIMAGE_BITMAP ${WIN_SETUP_BITMAP_PATH}/page_header.bmp
!define MUI_COMPONENTSPAGE_SMALLDESC !define MUI_COMPONENTSPAGE_SMALLDESC
!define MUI_FINISHPAGE_LINK "${APPLICATION_DOMAIN}" ; We removed this, h1 issue 191687
!define MUI_FINISHPAGE_LINK_LOCATION "http://${APPLICATION_DOMAIN}" ;!define MUI_FINISHPAGE_LINK "${APPLICATION_DOMAIN}"
;!define MUI_FINISHPAGE_LINK_LOCATION "http://${APPLICATION_DOMAIN}"
!define MUI_FINISHPAGE_NOREBOOTSUPPORT !define MUI_FINISHPAGE_NOREBOOTSUPPORT
!ifdef OPTION_FINISHPAGE_RELEASE_NOTES !ifdef OPTION_FINISHPAGE_RELEASE_NOTES
!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED

View File

@@ -16,20 +16,50 @@ format. You can overwrite changes using the ownCloud configuration dialog.
.. note:: Use caution when making changes to the ownCloud Client configuration .. note:: Use caution when making changes to the ownCloud Client configuration
file. Incorrect settings can produce unintended results. file. Incorrect settings can produce unintended results.
You can change the following configuration settings in the ``[ownCloud]`` section: Some interesting values that can be set on the configuration file are:
- ``remotePollInterval`` (default: ``30000``) -- Specifies the poll time for the remote repository in milliseconds. +----------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``[ownCloud]`` section |
+=================================+===============+========================================================================================================+
| Variable | Default | Meaning |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``remotePollInterval`` | ``30000`` | Specifies the poll time for the remote repository in milliseconds. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``forceSyncInterval`` | ``7200000`` | The duration of no activity after which a synchronization run shall be triggered automatically. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``notificationRefreshInterval`` | ``300000`` | Specifies the default interval of checking for new server notifications in milliseconds. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
- ``forceSyncInterval`` (default: ``7200000``) -- The duration of no activity after which a synchronization run shall be triggered automatically.
- ``notificationRefreshInterval`` (default: ``300000``) -- Specifies the default interval of checking for new server notifications in milliseconds. +----------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``[General]`` section |
+=================================+===============+========================================================================================================+
| Variable | Default | Meaning |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``chunkSize`` | ``5242880`` | Specifies the chunk size of uploaded files in bytes. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``promptDeleteAllFiles`` | ``true`` | If a UI prompt should ask for confirmation if it was detected that all files and folders were deleted. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``maxLogLines`` | ``20000`` | Specifies the maximum number of log lines displayed in the log window. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``timeout`` | ``300`` | The timeout for network connections in seconds. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
You can change the following configuration settings in the ``[General]`` section:
- ``chunkSize`` (default: ``5242880``) -- Specifies the chunk size of uploaded files in bytes. +----------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``[Proxy]`` section |
- ``promptDeleteAllFiles`` (default: ``true``) -- If a UI prompt should ask for confirmation if it was detected that all files and folders were deleted. +=================================+===============+========================================================================================================+
| Variable | Default | Meaning |
- ``maxLogLines`` (default: ``20000``) -- Specifies the maximum number of log lines displayed in the log window. +---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``host`` | ``127.0.0.1`` | The address of the proxy server. |
- ``timeout`` (default: ``300``) -- The timeout for network connections in seconds. +---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``port`` | ``8080`` | The port were the proxy is listening. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
| ``type`` | ``2`` | ``0`` for System Proxy. |
+ + +--------------------------------------------------------------------------------------------------------+
| | | ``1`` for SOCKS5 Proxy. |
+ + +--------------------------------------------------------------------------------------------------------+
| | | ``2`` for No Proxy. |
+ + +--------------------------------------------------------------------------------------------------------+
| | | ``3`` for HTTP(S) Proxy. |
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+

View File

@@ -60,10 +60,9 @@ Other command line switches supported by ``owncloudcmd`` include the following:
Credential Handling Credential Handling
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
``owncloudcmd`` uses the credentials of the GUI synchronization client. ``owncloudcmd`` requires the user to specify the username and password using the standard URL pattern, e.g.,
If no client is configured, or if you choose to use a different user to synchronize,
you can specify the user ::
password setting with the usual URL pattern. For example::
$ owncloudcmd /home/user/my_sync_folder https://carla:secret@server/owncloud/remote.php/webdav/ $ owncloudcmd /home/user/my_sync_folder https://carla:secret@server/owncloud/remote.php/webdav/

View File

@@ -55,8 +55,8 @@ Identifying Basic Functionality Problems
--------------------- ---------------------
If you see this error message stop your client, delete the If you see this error message stop your client, delete the
``.csync_journal.db`` file, and then restart your client. ``._sync_xxxxxxx.db`` file, and then restart your client.
There is a ``.csync_journal.db`` file inside the folder of every account There is a hidden ``._sync_xxxxxxx.db`` file inside the folder of every account
configured on your client. configured on your client.
.. NOTE:: .. NOTE::

View File

@@ -754,6 +754,210 @@ X-GNOME-Autostart-Delay=3
# Translations # Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations
# Translations # Translations
Comment[oc]=@APPLICATION_NAME@ sincronizacion del client Comment[oc]=@APPLICATION_NAME@ sincronizacion del client
GenericName[oc]=Dorsièr de Sincronizacion GenericName[oc]=Dorsièr de Sincronizacion
@@ -763,6 +967,10 @@ Comment[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي
GenericName[ar]=مزامنة المجلد GenericName[ar]=مزامنة المجلد
Name[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي Name[ar]=@APPLICATION_NAME@ زبون مزامنة مكتبي
Icon[ar]=@APPLICATION_EXECUTABLE@ Icon[ar]=@APPLICATION_EXECUTABLE@
Comment[bg_BG]=@APPLICATION_NAME@ клиент за десктоп синхронизация
GenericName[bg_BG]=Синхронизиране на папката
Name[bg_BG]=@APPLICATION_NAME@ клиент десктоп синхронизация
Icon[bg_BG]=@APPLICATION_EXECUTABLE@
Comment[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ Comment[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@
GenericName[ca]=Sincronització de carpetes GenericName[ca]=Sincronització de carpetes
Name[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@ Name[ca]=Client de sincronització d'escriptori @APPLICATION_NAME@
@@ -795,16 +1003,16 @@ Comment[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient
GenericName[de_DE]=Ordner-Synchronisation GenericName[de_DE]=Ordner-Synchronisation
Name[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient Name[de_DE]=@APPLICATION_NAME@ Desktop-Synchronisationsclient
Icon[de_DE]=@APPLICATION_EXECUTABLE@ Icon[de_DE]=@APPLICATION_EXECUTABLE@
Comment[bg_BG]=@APPLICATION_NAME@ клиент за десктоп синхронизация Comment[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa
GenericName[bg_BG]=Синхронизиране на папката GenericName[eu]=Karpetaren sinkronizazioa
Name[bg_BG]=@APPLICATION_NAME@ клиент десктоп синхронизация Name[eu]=@APPLICATION_NAME@ mahaigaineko sinkronizazio bezeroa
Icon[bg_BG]=@APPLICATION_EXECUTABLE@ Icon[eu]=@APPLICATION_EXECUTABLE@
GenericName[fa]=همسان سازی پوشه‌ها GenericName[fa]=همسان سازی پوشه‌ها
Name[fa]=@APPLICATION_EXECUTABLE@ نسخه‌ی همسان سازی مشتری Name[fa]=@APPLICATION_EXECUTABLE@ نسخه‌ی همسان سازی مشتری
Icon[fa]=@APPLICATION_EXECUTABLE@ Icon[fa]=@APPLICATION_EXECUTABLE@
Comment[fr]=@APPLICATION_NAME@ synchronisation du client Comment[fr]=Synchronisez vos dossiers avec un serveur @APPLICATION_NAME@
GenericName[fr]=Dossier de Synchronisation GenericName[fr]=Synchronisation de dossier
Name[fr]=@APPLICATION_NAME@ synchronisation du client Name[fr]=Client de synchronisation @APPLICATION_NAME@
Icon[fr]=@APPLICATION_EXECUTABLE@ Icon[fr]=@APPLICATION_EXECUTABLE@
Comment[he]=@APPLICATION_NAME@ לקוח סנכון שולחן עבודה Comment[he]=@APPLICATION_NAME@ לקוח סנכון שולחן עבודה
GenericName[he]=סנכון תיקייה GenericName[he]=סנכון תיקייה

View File

@@ -478,7 +478,12 @@ restart_sync:
SyncEngine engine(account, options.source_dir, folder, &db); SyncEngine engine(account, options.source_dir, folder, &db);
engine.setIgnoreHiddenFiles(options.ignoreHiddenFiles); engine.setIgnoreHiddenFiles(options.ignoreHiddenFiles);
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
QObject::connect(&engine, &SyncEngine::finished,
[&app](bool result) { app.exit(result ? EXIT_SUCCESS : EXIT_FAILURE); });
#else
QObject::connect(&engine, SIGNAL(finished(bool)), &app, SLOT(quit())); QObject::connect(&engine, SIGNAL(finished(bool)), &app, SLOT(quit()));
#endif
QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot())); QObject::connect(&engine, SIGNAL(transmissionProgress(ProgressInfo)), &cmd, SLOT(transmissionProgressSlot()));
@@ -505,7 +510,7 @@ restart_sync:
// Have to be done async, else, an error before exec() does not terminate the event loop. // Have to be done async, else, an error before exec() does not terminate the event loop.
QMetaObject::invokeMethod(&engine, "startSync", Qt::QueuedConnection); QMetaObject::invokeMethod(&engine, "startSync", Qt::QueuedConnection);
app.exec(); int resultCode = app.exec();
if (engine.isAnotherSyncNeeded() != NoFollowUpSync) { if (engine.isAnotherSyncNeeded() != NoFollowUpSync) {
if (restartCount < options.restartTimes) { if (restartCount < options.restartTimes) {
@@ -516,6 +521,6 @@ restart_sync:
qWarning() << "Another sync is needed, but not done because restart count is exceeded" << restartCount; qWarning() << "Another sync is needed, but not done because restart count is exceeded" << restartCount;
} }
return 0; return resultCode;
} }

View File

@@ -313,10 +313,9 @@ void AccountSettings::slotFolderWizardAccepted()
tr("<p>Could not create local folder <i>%1</i>.") tr("<p>Could not create local folder <i>%1</i>.")
.arg(QDir::toNativeSeparators(definition.localPath))); .arg(QDir::toNativeSeparators(definition.localPath)));
return; return;
} else {
FileSystem::setFolderMinimumPermissions(definition.localPath);
} }
} }
FileSystem::setFolderMinimumPermissions(definition.localPath);
} }
/* take the value from the definition of already existing folders. All folders have /* take the value from the definition of already existing folders. All folders have

View File

@@ -281,9 +281,12 @@ void AccountState::slotCredentialsFetched(AbstractCredentials* credentials)
_waitingForNewCredentials = false; _waitingForNewCredentials = false;
// When new credentials become available we always want to restart the if (_connectionValidator) {
// connection validation, even if it's currently running. // When new credentials become available we always want to restart the
delete _connectionValidator; // connection validation, even if it's currently running.
_connectionValidator->deleteLater();
_connectionValidator = 0;
}
checkConnectivity(); checkConnectivity();
} }
@@ -298,9 +301,12 @@ void AccountState::slotCredentialsAsked(AbstractCredentials* credentials)
return; return;
} }
// When new credentials become available we always want to restart the if (_connectionValidator) {
// connection validation, even if it's currently running. // When new credentials become available we always want to restart the
delete _connectionValidator; // connection validation, even if it's currently running.
_connectionValidator->deleteLater();
_connectionValidator = 0;
}
checkConnectivity(); checkConnectivity();
} }

View File

@@ -27,6 +27,10 @@
#include "activitydata.h" #include "activitydata.h"
#include "activitylistmodel.h" #include "activitylistmodel.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
Q_DECLARE_METATYPE(QPointer<OCC::AccountState>)
#endif
namespace OCC { namespace OCC {
ActivityListModel::ActivityListModel(QWidget *parent) ActivityListModel::ActivityListModel(QWidget *parent)
@@ -43,6 +47,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
a = _finalList.at(index.row()); a = _finalList.at(index.row());
AccountStatePtr ast = AccountManager::instance()->account(a._accName); AccountStatePtr ast = AccountManager::instance()->account(a._accName);
if (!ast)
return QVariant();
QStringList list; QStringList list;
switch (role) { switch (role) {
@@ -122,7 +128,7 @@ void ActivityListModel::startFetchJob(AccountState* s)
JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this); JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)),
this, SLOT(slotActivitiesReceived(QVariantMap, int))); this, SLOT(slotActivitiesReceived(QVariantMap, int)));
job->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(s)); job->setProperty("AccountStatePtr", QVariant::fromValue<QPointer<AccountState>>(s));
QList< QPair<QString,QString> > params; QList< QPair<QString,QString> > params;
params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0"))); params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0")));
@@ -139,7 +145,10 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int stat
auto activities = json.value("ocs").toMap().value("data").toList(); auto activities = json.value("ocs").toMap().value("data").toList();
ActivityList list; ActivityList list;
AccountState* ast = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr")); auto ast = qvariant_cast<QPointer<AccountState>>(sender()->property("AccountStatePtr"));
if (!ast)
return;
_currentlyFetching.remove(ast); _currentlyFetching.remove(ast);
foreach( auto activ, activities ) { foreach( auto activ, activities ) {

View File

@@ -37,6 +37,7 @@
#include "updater/ocupdater.h" #include "updater/ocupdater.h"
#include "excludedfiles.h" #include "excludedfiles.h"
#include "owncloudsetupwizard.h" #include "owncloudsetupwizard.h"
#include "version.h"
#include "config.h" #include "config.h"
@@ -52,6 +53,8 @@
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <openssl/crypto.h>
class QSocket; class QSocket;
namespace OCC { namespace OCC {
@@ -508,7 +511,13 @@ void Application::showVersion()
stream << _theme->appName().toLatin1().constData() stream << _theme->appName().toLatin1().constData()
<< QLatin1String(" version ") << QLatin1String(" version ")
<< _theme->version().toLatin1().constData() << endl; << _theme->version().toLatin1().constData() << endl;
stream << "Using Qt " << qVersion() << endl; #ifdef GIT_SHA1
stream << "Git revision " << GIT_SHA1 << endl;
#endif
stream << "Using Qt " << qVersion() << ", built against Qt " << QT_VERSION_STR << endl;
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
stream << "Using '" << QSslSocket::sslLibraryVersionString() << "'" << endl;
#endif
displayHelpText(helpText); displayHelpText(helpText);
} }

View File

@@ -70,13 +70,21 @@ void HttpCredentialsGui::askFromUserAsync()
QString HttpCredentialsGui::requestAppPasswordText(const Account* account) QString HttpCredentialsGui::requestAppPasswordText(const Account* account)
{ {
if (account->serverVersionInt() < 0x090100) { int version = account->serverVersionInt();
// Older server than 9.1 does not have trhe feature to request App Password QString path;
// Version may not be available before login on new servers!
if (!version || version >= Account::makeServerVersion(10, 0, 0)) {
path = QLatin1String("/index.php/settings/personal?sectionid=security#apppasswords");
} else if (version >= Account::makeServerVersion(9, 1, 0)) {
path = QLatin1String("/index.php/settings/personal?section=apppasswords");
} else {
// Older server than 9.1 does not have the feature to request App Password
return QString(); return QString();
} }
return tr("<a href=\"%1\">Click here</a> to request an app password from the web interface.") return tr("<a href=\"%1\">Click here</a> to request an app password from the web interface.")
.arg(account->url().toString() + QLatin1String("/index.php/settings/personal?section=apppasswords")); .arg(account->url().toString() + path);
} }

View File

@@ -345,7 +345,9 @@ void Folder::showSyncResultPopup()
if( _syncResult.firstConflictItem() ) { if( _syncResult.firstConflictItem() ) {
createGuiLog( _syncResult.firstConflictItem()->_file, LogStatusConflict, _syncResult.numConflictItems() ); createGuiLog( _syncResult.firstConflictItem()->_file, LogStatusConflict, _syncResult.numConflictItems() );
} }
createGuiLog( _syncResult.firstItemError()->_file, LogStatusError, _syncResult.numErrorItems() ); if (int errorCount = _syncResult.numErrorItems()) {
createGuiLog( _syncResult.firstItemError()->_file, LogStatusError, errorCount );
}
qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status()); qDebug() << "OO folder slotSyncFinished: result: " << int(_syncResult.status());
} }
@@ -909,6 +911,7 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, bool *cancel
} }
*cancel = msgBox.clickedButton() == keepBtn; *cancel = msgBox.clickedButton() == keepBtn;
if (*cancel) { if (*cancel) {
FileSystem::setFolderMinimumPermissions(path());
journalDb()->clearFileTable(); journalDb()->clearFileTable();
_lastEtag.clear(); _lastEtag.clear();
slotScheduleThisFolder(); slotScheduleThisFolder();

View File

@@ -631,8 +631,8 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
const auto permissionMap = job->property(propertyPermissionMap).toMap(); const auto permissionMap = job->property(propertyPermissionMap).toMap();
QStringList sortedSubfolders = list; QStringList sortedSubfolders = list;
// skip the parent item (first in the list) if (!sortedSubfolders.isEmpty())
sortedSubfolders.erase(sortedSubfolders.begin()); sortedSubfolders.removeFirst(); // skip the parent item (first in the list)
Utility::sortFilenames(sortedSubfolders); Utility::sortFilenames(sortedSubfolders);
QVarLengthArray<int, 10> undecidedIndexes; QVarLengthArray<int, 10> undecidedIndexes;
@@ -652,7 +652,7 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
newInfo._size = job->_sizes.value(path); newInfo._size = job->_sizes.value(path);
newInfo._isExternal = permissionMap.value(removeTrailingSlash(path)).toString().contains("M"); newInfo._isExternal = permissionMap.value(removeTrailingSlash(path)).toString().contains("M");
newInfo._path = relativePath; newInfo._path = relativePath;
newInfo._name = relativePath.split('/', QString::SkipEmptyParts).last(); newInfo._name = removeTrailingSlash(relativePath).split('/').last();
if (relativePath.isEmpty()) if (relativePath.isEmpty())
continue; continue;

View File

@@ -57,6 +57,7 @@ IgnoreListEditor::IgnoreListEditor(QWidget *parent) :
connect(ui->removePushButton, SIGNAL(clicked()), SLOT(slotRemoveCurrentItem())); connect(ui->removePushButton, SIGNAL(clicked()), SLOT(slotRemoveCurrentItem()));
connect(ui->addPushButton, SIGNAL(clicked()), SLOT(slotAddPattern())); connect(ui->addPushButton, SIGNAL(clicked()), SLOT(slotAddPattern()));
ui->tableWidget->resizeColumnsToContents();
ui->tableWidget->horizontalHeader()->setResizeMode(patternCol, QHeaderView::Stretch); ui->tableWidget->horizontalHeader()->setResizeMode(patternCol, QHeaderView::Stretch);
ui->tableWidget->verticalHeader()->setVisible(false); ui->tableWidget->verticalHeader()->setVisible(false);

View File

@@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>438</width> <width>516</width>
<height>463</height> <height>546</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -103,9 +103,18 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>

View File

@@ -54,9 +54,7 @@ void NotificationConfirmJob::start()
req.setRawHeader("Ocs-APIREQUEST", "true"); req.setRawHeader("Ocs-APIREQUEST", "true");
req.setRawHeader("Content-Type", "application/x-www-form-urlencoded"); req.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
QIODevice *iodevice = 0; sendRequest(_verb, _link, req);
setReply(davRequest(_verb, _link, req, iodevice));
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }

View File

@@ -93,9 +93,7 @@ void OcsJob::start()
queryItems.append(qMakePair(QByteArray("format"), QByteArray("json"))); queryItems.append(qMakePair(QByteArray("format"), QByteArray("json")));
url.setEncodedQueryItems(queryItems); url.setEncodedQueryItems(queryItems);
setReply(davRequest(_verb, url, req, buffer)); sendRequest(_verb, url, req, buffer);
setupConnections(reply());
buffer->setParent(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }

View File

@@ -174,22 +174,31 @@ void ownCloudGui::slotTrayClicked( QSystemTrayIcon::ActivationReason reason )
last_click.start(); last_click.start();
} }
// A click on the tray icon should only open the status window on Win and // Left click
// Linux, not on Mac. They want a menu entry.
#if !defined Q_OS_MAC
if( reason == QSystemTrayIcon::Trigger ) { if( reason == QSystemTrayIcon::Trigger ) {
// Start settings if config is existing. if (OwncloudSetupWizard::bringWizardToFrontIfVisible()) {
slotOpenSettingsDialog(); // brought wizard to front
} } else if (_shareDialogs.size() > 0) {
// Share dialog(s) be hidden by other apps, bring them back
Q_FOREACH(const QPointer<ShareDialog> &shareDialog, _shareDialogs) {
Q_ASSERT(shareDialog.data());
raiseDialog(shareDialog);
}
} else {
#ifdef Q_OS_MAC
// on macOS, a left click always opens menu.
// However if the settings dialog is already visible but hidden
// by other applications, this will bring it to the front.
if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
raiseDialog(_settingsDialog.data());
}
#else #else
// On Mac, if the settings dialog is already visible but hidden slotOpenSettingsDialog();
// by other applications, this will bring it to the front. #endif
if( reason == QSystemTrayIcon::Trigger ) {
if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
slotShowSettings();
} }
} }
#endif // FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613
// or SSL error dialog also comes to front.
} }
void ownCloudGui::slotSyncStateChange( Folder* folder ) void ownCloudGui::slotSyncStateChange( Folder* folder )

View File

@@ -32,6 +32,7 @@
#include "accountmanager.h" #include "accountmanager.h"
#include "clientproxy.h" #include "clientproxy.h"
#include "filesystem.h" #include "filesystem.h"
#include "owncloudgui.h"
#include "creds/credentialsfactory.h" #include "creds/credentialsfactory.h"
#include "creds/abstractcredentials.h" #include "creds/abstractcredentials.h"
@@ -64,10 +65,10 @@ OwncloudSetupWizard::~OwncloudSetupWizard()
_ocWizard->deleteLater(); _ocWizard->deleteLater();
} }
static QPointer<OwncloudSetupWizard> wiz = 0;
void OwncloudSetupWizard::runWizard(QObject* obj, const char* amember, QWidget *parent) void OwncloudSetupWizard::runWizard(QObject* obj, const char* amember, QWidget *parent)
{ {
static QPointer<OwncloudSetupWizard> wiz;
if (!wiz.isNull()) { if (!wiz.isNull()) {
return; return;
} }
@@ -78,6 +79,16 @@ void OwncloudSetupWizard::runWizard(QObject* obj, const char* amember, QWidget *
wiz->startWizard(); wiz->startWizard();
} }
bool OwncloudSetupWizard::bringWizardToFrontIfVisible()
{
if (wiz.isNull()) {
return false;
}
ownCloudGui::raiseDialog(wiz->_ocWizard);
return true;
}
void OwncloudSetupWizard::startWizard() void OwncloudSetupWizard::startWizard()
{ {
AccountPtr account = AccountManager::createAccount(); AccountPtr account = AccountManager::createAccount();
@@ -183,6 +194,8 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl& url, const QVariantM
Utility::escape(CheckServerJob::versionString(info)), Utility::escape(CheckServerJob::versionString(info)),
Utility::escape(serverVersion))); Utility::escape(serverVersion)));
// Note with newer servers we get the version actually only later in capabilities
// https://github.com/owncloud/core/pull/27473/files
_ocWizard->account()->setServerVersion(serverVersion); _ocWizard->account()->setServerVersion(serverVersion);
QString p = url.path(); QString p = url.path();
@@ -368,6 +381,7 @@ void OwncloudSetupWizard::slotCreateLocalAndRemoteFolders(const QString& localFo
bool nextStep = true; bool nextStep = true;
if( fi.exists() ) { if( fi.exists() ) {
FileSystem::setFolderMinimumPermissions(localFolder);
// there is an existing local folder. If its non empty, it can only be synced if the // there is an existing local folder. If its non empty, it can only be synced if the
// ownCloud is newly created. // ownCloud is newly created.
_ocWizard->appendToConfigurationLog( _ocWizard->appendToConfigurationLog(
@@ -594,9 +608,7 @@ DetermineAuthTypeJob::DetermineAuthTypeJob(AccountPtr account, QObject *parent)
void DetermineAuthTypeJob::start() void DetermineAuthTypeJob::start()
{ {
QNetworkReply *reply = getRequest(account()->davPath()); sendRequest("GET", account()->davUrl());
setReply(reply);
setupConnections(reply);
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -613,8 +625,7 @@ bool DetermineAuthTypeJob::finished()
// do a new run // do a new run
_redirects++; _redirects++;
resetTimeout(); resetTimeout();
setReply(getRequest(redirection)); sendRequest("GET", redirection);
setupConnections(reply());
return false; // don't discard return false; // don't discard
} else { } else {
#ifndef NO_SHIBBOLETH #ifndef NO_SHIBBOLETH

View File

@@ -60,6 +60,7 @@ class OwncloudSetupWizard : public QObject
public: public:
/** Run the wizard */ /** Run the wizard */
static void runWizard(QObject *obj, const char* amember, QWidget *parent = 0 ); static void runWizard(QObject *obj, const char* amember, QWidget *parent = 0 );
static bool bringWizardToFrontIfVisible();
signals: signals:
// overall dialog close signal. // overall dialog close signal.
void ownCloudWizardDone( int ); void ownCloudWizardDone( int );

View File

@@ -81,9 +81,14 @@ void ProxyAuthHandler::handleProxyAuthenticationRequired(
} }
// Find the responsible QNAM if possible. // Find the responsible QNAM if possible.
QNetworkAccessManager* sending_qnam = qobject_cast<QNetworkAccessManager*>(sender()); QNetworkAccessManager* sending_qnam = 0;
QWeakPointer<QNetworkAccessManager> qnam_alive;
if (Account* account = qobject_cast<Account*>(sender())) { if (Account* account = qobject_cast<Account*>(sender())) {
sending_qnam = account->networkAccessManager(); // Since we go into an event loop, it's possible for the account's qnam
// to be destroyed before we get back. We can use this to check for its
// liveness.
qnam_alive = account->sharedNetworkAccessManager();
sending_qnam = qnam_alive.data();
} }
if (!sending_qnam) { if (!sending_qnam) {
qDebug() << "Could not get the sending QNAM for" << sender(); qDebug() << "Could not get the sending QNAM for" << sender();
@@ -122,6 +127,7 @@ void ProxyAuthHandler::handleProxyAuthenticationRequired(
qDebug() << "got creds for" << _proxy; qDebug() << "got creds for" << _proxy;
authenticator->setUser(_username); authenticator->setUser(_username);
authenticator->setPassword(_password); authenticator->setPassword(_password);
sending_qnam = qnam_alive.data();
if (sending_qnam) { if (sending_qnam) {
_gaveCredentialsTo.insert(sending_qnam); _gaveCredentialsTo.insert(sending_qnam);
connect(sending_qnam, SIGNAL(destroyed(QObject*)), connect(sending_qnam, SIGNAL(destroyed(QObject*)),

View File

@@ -32,6 +32,8 @@
namespace OCC { namespace OCC {
static const int thumbnailSize = 40;
ShareDialog::ShareDialog(QPointer<AccountState> accountState, ShareDialog::ShareDialog(QPointer<AccountState> accountState,
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
@@ -67,7 +69,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
QFileInfo f_info(_localPath); QFileInfo f_info(_localPath);
QFileIconProvider icon_provider; QFileIconProvider icon_provider;
QIcon icon = icon_provider.icon(f_info); QIcon icon = icon_provider.icon(f_info);
_ui->label_icon->setPixmap(icon.pixmap(40,40)); _ui->label_icon->setPixmap(icon.pixmap(thumbnailSize, thumbnailSize));
// Set filename // Set filename
QFileInfo lPath(_localPath); QFileInfo lPath(_localPath);
@@ -176,7 +178,7 @@ void ShareDialog::showSharingUi()
// We only do user/group sharing from 8.2.0 // We only do user/group sharing from 8.2.0
bool userGroupSharing = bool userGroupSharing =
theme->userGroupSharing() theme->userGroupSharing()
&& _accountState->account()->serverVersionInt() >= ((8 << 16) + (2 << 8)); && _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0);
bool autoShare = !userGroupSharing; bool autoShare = !userGroupSharing;
@@ -213,6 +215,7 @@ void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &
QPixmap p; QPixmap p;
p.loadFromData(reply, "PNG"); p.loadFromData(reply, "PNG");
p = p.scaledToHeight(thumbnailSize, Qt::SmoothTransformation);
_ui->label_icon->setPixmap(p); _ui->label_icon->setPixmap(p);
} }

View File

@@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>408</width>
<height>300</height> <height>281</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -64,7 +64,24 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="shareWidgetsLayout"/> <layout class="QVBoxLayout" name="shareWidgetsLayout">
<property name="spacing">
<number>10</number>
</property>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">

View File

@@ -14,11 +14,20 @@
<string>Share NewDocument.odt</string> <string>Share NewDocument.odt</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0"> <item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_shareLink"> <layout class="QHBoxLayout" name="horizontalLayout_shareLink">
<property name="topMargin">
<number>10</number>
</property>
<item> <item>
<widget class="QCheckBox" name="checkBox_shareLink"> <widget class="QCheckBox" name="checkBox_shareLink">
<property name="text"> <property name="text">
@@ -88,7 +97,7 @@
<number>20</number> <number>20</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>1</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>0</number> <number>0</number>

View File

@@ -343,7 +343,7 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QVariantMap &data)
// From ownCloud server 8.2 the url field is always set for public shares // From ownCloud server 8.2 the url field is always set for public shares
if (data.contains("url")) { if (data.contains("url")) {
url = QUrl(data.value("url").toString()); url = QUrl(data.value("url").toString());
} else if (_account->serverVersionInt() >= (8 << 16)) { } else if (_account->serverVersionInt() >= Account::makeServerVersion(8, 0, 0)) {
// From ownCloud server version 8 on, a different share link scheme is used. // From ownCloud server version 8 on, a different share link scheme is used.
url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("index.php/s/") + data.value("token").toString())).toString(); url = QUrl(Utility::concatUrlPath(_account->url(), QLatin1String("index.php/s/") + data.value("token").toString())).toString();
} else { } else {

View File

@@ -254,7 +254,7 @@ void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex & index)
* https://github.com/owncloud/client/issues/4996 * https://github.com/owncloud/client/issues/4996
*/ */
if (sharee->type() == Sharee::Federated if (sharee->type() == Sharee::Federated
&& _account->serverVersionInt() < 0x090100) { && _account->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
int permissions = SharePermissionRead | SharePermissionUpdate; int permissions = SharePermissionRead | SharePermissionUpdate;
if (!_isFile) { if (!_isFile) {
permissions |= SharePermissionCreate | SharePermissionDelete; permissions |= SharePermissionCreate | SharePermissionDelete;
@@ -280,9 +280,16 @@ void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex & index)
void ShareUserGroupWidget::displayError(int code, const QString& message) void ShareUserGroupWidget::displayError(int code, const QString& message)
{ {
_pi_sharee.stopAnimation(); _pi_sharee.stopAnimation();
// Also remove the spinner in the widget list, if any
foreach (auto pi, _ui->scrollArea->findChildren<QProgressIndicator*>()) {
delete pi;
}
qDebug() << "Error from server" << code << message; qDebug() << "Error from server" << code << message;
_ui->errorLabel->setText(message); _ui->errorLabel->setText(message);
_ui->errorLabel->show(); _ui->errorLabel->show();
_ui->shareeLineEdit->setEnabled(true);
} }
ShareWidget::ShareWidget(QSharedPointer<Share> share, ShareWidget::ShareWidget(QSharedPointer<Share> share,
@@ -324,6 +331,11 @@ ShareWidget::ShareWidget(QSharedPointer<Share> share,
QIcon icon(QLatin1String(":/client/resources/more.png")); QIcon icon(QLatin1String(":/client/resources/more.png"));
_ui->permissionToolButton->setIcon(icon); _ui->permissionToolButton->setIcon(icon);
// If there's only a single entry in the detailed permission menu, hide it
if (menu->actions().size() == 1) {
_ui->permissionToolButton->hide();
}
// Set the permissions checkboxes // Set the permissions checkboxes
displayPermissions(); displayPermissions();
@@ -343,7 +355,7 @@ ShareWidget::ShareWidget(QSharedPointer<Share> share,
* https://github.com/owncloud/client/issues/4996 * https://github.com/owncloud/client/issues/4996
*/ */
if (share->getShareType() == Share::TypeRemote if (share->getShareType() == Share::TypeRemote
&& share->account()->serverVersionInt() < 0x090100) { && share->account()->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
_ui->permissionShare->setVisible(false); _ui->permissionShare->setVisible(false);
_ui->permissionToolButton->setVisible(false); _ui->permissionToolButton->setVisible(false);
} }

View File

@@ -6,14 +6,26 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>457</width> <width>397</width>
<height>164</height> <height>273</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Share NewDocument.odt</string> <string>Share NewDocument.odt</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<layout class="QHBoxLayout" name="shareeHorizontalLayout"> <layout class="QHBoxLayout" name="shareeHorizontalLayout">
<item> <item>
@@ -94,8 +106,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>437</width> <width>395</width>
<height>94</height> <height>221</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"/> <layout class="QVBoxLayout" name="verticalLayout_3"/>

View File

@@ -404,7 +404,7 @@ void SocketApi::command_SHARE(const QString& localFile, SocketListener* listener
listener->sendMessage(message); listener->sendMessage(message);
} else if (!theme->linkSharing() && ( } else if (!theme->linkSharing() && (
!theme->userGroupSharing() || !theme->userGroupSharing() ||
shareFolder->accountState()->account()->serverVersionInt() < ((8 << 16) + (2 << 8)))) { shareFolder->accountState()->account()->serverVersionInt() < Account::makeServerVersion(8, 2, 0))) {
const QString message = QLatin1String("SHARE:NOP:")+QDir::toNativeSeparators(localFile); const QString message = QLatin1String("SHARE:NOP:")+QDir::toNativeSeparators(localFile);
listener->sendMessage(message); listener->sendMessage(message);
} else { } else {

View File

@@ -59,7 +59,7 @@ static QString addCertDetailsField(const QString &key, const QString &value)
return QString(); return QString();
return QLatin1String("<tr><td style=\"vertical-align: top;\"><b>") + key return QLatin1String("<tr><td style=\"vertical-align: top;\"><b>") + key
+ QLatin1String("</b></td><td style=\"vertical-align: bottom;\">)") + value + QLatin1String("</b></td><td style=\"vertical-align: bottom;\">") + value
+ QLatin1String("</td></tr>"); + QLatin1String("</td></tr>");
} }

View File

@@ -27,8 +27,7 @@ ThumbnailJob::ThumbnailJob(const QString &path, AccountPtr account, QObject* par
void ThumbnailJob::start() void ThumbnailJob::start()
{ {
setReply(getRequest(path())); sendRequest("GET", makeAccountUrl(path()));
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }

View File

@@ -122,40 +122,49 @@ QNetworkReply* AbstractNetworkJob::addTimer(QNetworkReply *reply)
return reply; return reply;
} }
QNetworkReply* AbstractNetworkJob::davRequest(const QByteArray &verb, const QString &relPath, QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUrl &url,
QNetworkRequest req, QIODevice *data) QNetworkRequest req, QIODevice *requestBody)
{ {
return addTimer(_account->davRequest(verb, relPath, req, data)); auto reply = _account->sendRequest(verb, url, req, requestBody);
_requestBody = requestBody;
if (_requestBody) {
_requestBody->setParent(reply);
}
addTimer(reply);
setReply(reply);
setupConnections(reply);
return reply;
} }
QNetworkReply *AbstractNetworkJob::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data) QUrl AbstractNetworkJob::makeAccountUrl(const QString& relativePath) const
{ {
return addTimer(_account->davRequest(verb, url, req, data)); return Utility::concatUrlPath(_account->url(), relativePath);
} }
QNetworkReply* AbstractNetworkJob::getRequest(const QString &relPath) QUrl AbstractNetworkJob::makeDavUrl(const QString& relativePath) const
{ {
return addTimer(_account->getRequest(relPath)); return Utility::concatUrlPath(_account->davUrl(), relativePath);
} }
QNetworkReply *AbstractNetworkJob::getRequest(const QUrl &url) QByteArray AbstractNetworkJob::requestVerb(QNetworkReply* reply)
{ {
return addTimer(_account->getRequest(url)); switch (reply->operation()) {
} case QNetworkAccessManager::HeadOperation:
return "HEAD";
QNetworkReply *AbstractNetworkJob::headRequest(const QString &relPath) case QNetworkAccessManager::GetOperation:
{ return "GET";
return addTimer(_account->headRequest(relPath)); case QNetworkAccessManager::PutOperation:
} return "PUT";
case QNetworkAccessManager::PostOperation:
QNetworkReply *AbstractNetworkJob::headRequest(const QUrl &url) return "POST";
{ case QNetworkAccessManager::DeleteOperation:
return addTimer(_account->headRequest(url)); return "DELETE";
} case QNetworkAccessManager::CustomOperation:
return reply->request().attribute(QNetworkRequest::CustomVerbAttribute).toByteArray();
QNetworkReply *AbstractNetworkJob::deleteRequest(const QUrl &url) case QNetworkAccessManager::UnknownOperation:
{ break;
return addTimer(_account->deleteRequest(url)); }
return QByteArray();
} }
void AbstractNetworkJob::slotFinished() void AbstractNetworkJob::slotFinished()
@@ -178,24 +187,36 @@ void AbstractNetworkJob::slotFinished()
// get the Date timestamp from reply // get the Date timestamp from reply
_responseTimestamp = _reply->rawHeader("Date"); _responseTimestamp = _reply->rawHeader("Date");
if (_followRedirects) { QUrl requestedUrl = reply()->request().url();
// ### the qWarnings here should be exported via displayErrors() so they QUrl redirectUrl = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (_followRedirects && !redirectUrl.isEmpty()) {
_redirectCount++;
// ### some of the qWarnings here should be exported via displayErrors() so they
// ### can be presented to the user if the job executor has a GUI // ### can be presented to the user if the job executor has a GUI
QUrl requestedUrl = reply()->request().url(); QByteArray verb = requestVerb(reply());
QUrl redirectUrl = reply()->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (requestedUrl.scheme() == QLatin1String("https") &&
if (!redirectUrl.isEmpty()) { redirectUrl.scheme() == QLatin1String("http")) {
_redirectCount++; qWarning() << this << "HTTPS->HTTP downgrade detected!";
if (requestedUrl.scheme() == QLatin1String("https") && } else if (requestedUrl == redirectUrl || _redirectCount >= maxRedirects()) {
redirectUrl.scheme() == QLatin1String("http")) { qWarning() << this << "Redirect loop detected!";
qWarning() << this << "HTTPS->HTTP downgrade detected!"; } else if (_requestBody && _requestBody->isSequential()) {
} else if (requestedUrl == redirectUrl || _redirectCount >= maxRedirects()) { qWarning() << this << "cannot redirect request with sequential body";
qWarning() << this << "Redirect loop detected!"; } else if (verb.isEmpty()) {
} else { qWarning() << this << "cannot redirect request: could not detect original verb";
resetTimeout(); } else {
setReply(getRequest(redirectUrl)); // Create the redirected request and send it
setupConnections(reply()); qDebug() << "Redirecting" << verb << requestedUrl << redirectUrl;
return; resetTimeout();
if (_requestBody) {
_requestBody->seek(0);
} }
sendRequest(
verb,
redirectUrl,
reply()->request(),
_requestBody);
return;
} }
} }

View File

@@ -78,17 +78,29 @@ signals:
void networkActivity(); void networkActivity();
protected: protected:
void setupConnections(QNetworkReply *reply); void setupConnections(QNetworkReply *reply);
QNetworkReply* davRequest(const QByteArray& verb, const QString &relPath,
QNetworkRequest req = QNetworkRequest(), /** Initiate a network request, returning a QNetworkReply.
QIODevice *data = 0); *
QNetworkReply* davRequest(const QByteArray& verb, const QUrl &url, * Calls setReply() and setupConnections() on it.
QNetworkRequest req = QNetworkRequest(), *
QIODevice *data = 0); * Takes ownership of the requestBody (to allow redirects).
QNetworkReply* getRequest(const QString &relPath); */
QNetworkReply* getRequest(const QUrl &url); QNetworkReply* sendRequest(const QByteArray& verb, const QUrl &url,
QNetworkReply* headRequest(const QString &relPath); QNetworkRequest req = QNetworkRequest(),
QNetworkReply* headRequest(const QUrl &url); QIODevice *requestBody = 0);
QNetworkReply* deleteRequest(const QUrl &url);
// sendRequest does not take a relative path instead of an url,
// but the old API allowed that. We have this undefined overload
// to help catch usage errors
QNetworkReply* sendRequest(const QByteArray& verb, const QString &relativePath,
QNetworkRequest req = QNetworkRequest(),
QIODevice *requestBody = 0);
/// Creates a url for the account from a relative path
QUrl makeAccountUrl(const QString& relativePath) const;
/// Like makeAccountUrl() but uses the account's dav base path
QUrl makeDavUrl(const QString& relativePath) const;
int maxRedirects() const { return 10; } int maxRedirects() const { return 10; }
virtual bool finished() = 0; virtual bool finished() = 0;
@@ -99,12 +111,19 @@ protected:
// GET requests that don't set up any HTTP body or other flags. // GET requests that don't set up any HTTP body or other flags.
bool _followRedirects; bool _followRedirects;
/** Helper to construct the HTTP verb used in the request
*
* Returns an empty QByteArray for UnknownOperation.
*/
static QByteArray requestVerb(QNetworkReply* reply);
private slots: private slots:
void slotFinished(); void slotFinished();
virtual void slotTimeout(); virtual void slotTimeout();
protected: protected:
AccountPtr _account; AccountPtr _account;
private: private:
QNetworkReply* addTimer(QNetworkReply *reply); QNetworkReply* addTimer(QNetworkReply *reply);
bool _ignoreCredentialFailure; bool _ignoreCredentialFailure;
@@ -112,6 +131,12 @@ private:
QString _path; QString _path;
QTimer _timer; QTimer _timer;
int _redirectCount; int _redirectCount;
// Set by the xyzRequest() functions and needed to be able to redirect
// requests, should it be required.
//
// Reparented to the currently running QNetworkReply.
QPointer<QIODevice> _requestBody;
}; };
/** /**

View File

@@ -202,54 +202,28 @@ QNetworkAccessManager *Account::networkAccessManager()
return _am.data(); return _am.data();
} }
QNetworkReply *Account::headRequest(const QString &relPath) QSharedPointer<QNetworkAccessManager> Account::sharedNetworkAccessManager()
{ {
return headRequest(Utility::concatUrlPath(url(), relPath)); return _am;
} }
QNetworkReply *Account::headRequest(const QUrl &url) QNetworkReply *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
{
QNetworkRequest request(url);
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
request.setSslConfiguration(this->getOrCreateSslConfig());
#endif
return _am->head(request);
}
QNetworkReply *Account::getRequest(const QString &relPath)
{
return getRequest(Utility::concatUrlPath(url(), relPath));
}
QNetworkReply *Account::getRequest(const QUrl &url)
{
QNetworkRequest request(url);
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
request.setSslConfiguration(this->getOrCreateSslConfig());
#endif
return _am->get(request);
}
QNetworkReply *Account::deleteRequest( const QUrl &url)
{
QNetworkRequest request(url);
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
request.setSslConfiguration(this->getOrCreateSslConfig());
#endif
return _am->deleteResource(request);
}
QNetworkReply *Account::davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data)
{
return davRequest(verb, Utility::concatUrlPath(davUrl(), relPath), req, data);
}
QNetworkReply *Account::davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
{ {
req.setUrl(url); req.setUrl(url);
#if QT_VERSION > QT_VERSION_CHECK(4, 8, 4) #if QT_VERSION > QT_VERSION_CHECK(4, 8, 4)
req.setSslConfiguration(this->getOrCreateSslConfig()); req.setSslConfiguration(this->getOrCreateSslConfig());
#endif #endif
if (verb == "HEAD" && !data) {
return _am->head(req);
} else if (verb == "GET" && !data) {
return _am->get(req);
} else if (verb == "POST") {
return _am->post(req, data);
} else if (verb == "PUT") {
return _am->put(req, data);
} else if (verb == "DELETE" && !data) {
return _am->deleteResource(req);
}
return _am->sendCustomRequest(req, verb, data); return _am->sendCustomRequest(req, verb, data);
} }
@@ -427,9 +401,14 @@ int Account::serverVersionInt() const
{ {
// FIXME: Use Qt 5.5 QVersionNumber // FIXME: Use Qt 5.5 QVersionNumber
auto components = serverVersion().split('.'); auto components = serverVersion().split('.');
return (components.value(0).toInt() << 16) return makeServerVersion(components.value(0).toInt(),
+ (components.value(1).toInt() << 8) components.value(1).toInt(),
+ components.value(2).toInt(); components.value(2).toInt());
}
int Account::makeServerVersion(int majorVersion, int minorVersion, int patchVersion)
{
return (majorVersion << 16) + (minorVersion << 8) + patchVersion;
} }
bool Account::serverVersionUnsupported() const bool Account::serverVersionUnsupported() const
@@ -438,7 +417,7 @@ bool Account::serverVersionUnsupported() const
// not detected yet, assume it is fine. // not detected yet, assume it is fine.
return false; return false;
} }
return serverVersionInt() < 0x070000; return serverVersionInt() < makeServerVersion(7, 0, 0);
} }
void Account::setServerVersion(const QString& version) void Account::setServerVersion(const QString& version)
@@ -454,7 +433,7 @@ void Account::setServerVersion(const QString& version)
bool Account::rootEtagChangesNotOnlySubFolderEtags() bool Account::rootEtagChangesNotOnlySubFolderEtags()
{ {
return (serverVersionInt() >= 0x080100); return (serverVersionInt() >= makeServerVersion(8, 1, 0));
} }
void Account::setNonShib(bool nonShib) void Account::setNonShib(bool nonShib)

View File

@@ -106,14 +106,10 @@ public:
// For creating various network requests // For creating various network requests
QNetworkReply* headRequest(const QString &relPath); QNetworkReply* sendRequest(const QByteArray &verb,
QNetworkReply* headRequest(const QUrl &url); const QUrl &url,
QNetworkReply* getRequest(const QString &relPath); QNetworkRequest req = QNetworkRequest(),
QNetworkReply* getRequest(const QUrl &url); QIODevice *data = 0);
QNetworkReply* deleteRequest( const QUrl &url);
QNetworkReply* davRequest(const QByteArray &verb, const QString &relPath, QNetworkRequest req, QIODevice *data = 0);
QNetworkReply* davRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data = 0);
/** The ssl configuration during the first connection */ /** The ssl configuration during the first connection */
QSslConfiguration getOrCreateSslConfig(); QSslConfiguration getOrCreateSslConfig();
@@ -149,9 +145,22 @@ public:
const Capabilities &capabilities() const; const Capabilities &capabilities() const;
void setCapabilities(const QVariantMap &caps); void setCapabilities(const QVariantMap &caps);
/** Access the server version */ /** Access the server version
*
* For servers >= 10.0.0, this can be the empty string until capabilities
* have been received.
*/
QString serverVersion() const; QString serverVersion() const;
/** Server version for easy comparison.
*
* Example: serverVersionInt() >= makeServerVersion(11, 2, 3)
*
* Will be 0 if the version is not available yet.
*/
int serverVersionInt() const; int serverVersionInt() const;
static int makeServerVersion(int majorVersion, int minorVersion, int patchVersion);
void setServerVersion(const QString &version); void setServerVersion(const QString &version);
/** Whether the server is too old. /** Whether the server is too old.
@@ -176,6 +185,7 @@ public:
void resetNetworkAccessManager(); void resetNetworkAccessManager();
QNetworkAccessManager* networkAccessManager(); QNetworkAccessManager* networkAccessManager();
QSharedPointer<QNetworkAccessManager> sharedNetworkAccessManager();
/// Called by network jobs on credential errors, emits invalidCredentials() /// Called by network jobs on credential errors, emits invalidCredentials()
void handleInvalidCredentials(); void handleInvalidCredentials();

View File

@@ -114,26 +114,21 @@ void ConnectionValidator::slotCheckServerAndAuth()
void ConnectionValidator::slotStatusFound(const QUrl&url, const QVariantMap &info) void ConnectionValidator::slotStatusFound(const QUrl&url, const QVariantMap &info)
{ {
// Newer servers don't disclose any version in status.php anymore
// https://github.com/owncloud/core/pull/27473/files
// so this string can be empty.
QString serverVersion = CheckServerJob::version(info);
// status.php was found. // status.php was found.
qDebug() << "** Application: ownCloud found: " qDebug() << "** Application: ownCloud found: "
<< url << " with version " << url << " with version "
<< CheckServerJob::versionString(info) << CheckServerJob::versionString(info)
<< "(" << CheckServerJob::version(info) << ")"; << "(" << serverVersion << ")";
QString version = CheckServerJob::version(info); if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) {
_account->setServerVersion(version);
// We cannot deal with servers < 5.0.0
if (version.contains('.') && version.split('.')[0].toInt() < 5) {
_errors.append( tr("The configured server for this client is too old") );
_errors.append( tr("Please update to the latest server and restart the client.") );
reportResult( ServerVersionMismatch );
return; return;
} }
// We attempt to work with servers >= 5.0.0 but warn users.
// Check usages of Account::serverVersionUnsupported() for details.
// now check the authentication // now check the authentication
if (_account->credentials()->ready()) if (_account->credentials()->ready())
QTimer::singleShot( 0, this, SLOT( checkAuthentication() )); QTimer::singleShot( 0, this, SLOT( checkAuthentication() ));
@@ -235,6 +230,13 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QVariantMap &json)
auto caps = json.value("ocs").toMap().value("data").toMap().value("capabilities"); auto caps = json.value("ocs").toMap().value("data").toMap().value("capabilities");
qDebug() << "Server capabilities" << caps; qDebug() << "Server capabilities" << caps;
_account->setCapabilities(caps.toMap()); _account->setCapabilities(caps.toMap());
// New servers also report the version in the capabilities
QString serverVersion = caps.toMap()["core"].toMap()["status"].toMap()["version"].toString();
if (!serverVersion.isEmpty() && !setAndCheckServerVersion(serverVersion)) {
return;
}
fetchUser(); fetchUser();
} }
@@ -247,6 +249,26 @@ void ConnectionValidator::fetchUser()
job->start(); job->start();
} }
bool ConnectionValidator::setAndCheckServerVersion(const QString& version)
{
qDebug() << _account->url() << "has server version" << version;
_account->setServerVersion(version);
// We cannot deal with servers < 5.0.0
if (_account->serverVersionInt()
&& _account->serverVersionInt() < Account::makeServerVersion(5, 0, 0)) {
_errors.append( tr("The configured server for this client is too old") );
_errors.append( tr("Please update to the latest server and restart the client.") );
reportResult( ServerVersionMismatch );
return false;
}
// We attempt to work with servers >= 5.0.0 but warn users.
// Check usages of Account::serverVersionUnsupported() for details.
return true;
}
void ConnectionValidator::slotUserFetched(const QVariantMap &json) void ConnectionValidator::slotUserFetched(const QVariantMap &json)
{ {
QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString(); QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString();

View File

@@ -125,6 +125,12 @@ private:
void checkServerCapabilities(); void checkServerCapabilities();
void fetchUser(); void fetchUser();
/** Sets the account's server version
*
* Returns false and reports ServerVersionMismatch for very old servers.
*/
bool setAndCheckServerVersion(const QString& version);
QStringList _errors; QStringList _errors;
AccountPtr _account; AccountPtr _account;
bool _isCheckingServerAndAuth; bool _isCheckingServerAndAuth;

View File

@@ -67,9 +67,7 @@ void RequestEtagJob::start()
buf->setData(xml); buf->setData(xml);
buf->open(QIODevice::ReadOnly); buf->open(QIODevice::ReadOnly);
// assumes ownership // assumes ownership
setReply(davRequest("PROPFIND", path(), req, buf)); sendRequest("PROPFIND", makeDavUrl(path()), req, buf);
buf->setParent(reply());
setupConnections(reply());
if( reply()->error() != QNetworkReply::NoError ) { if( reply()->error() != QNetworkReply::NoError ) {
qDebug() << "getting etag: request network error: " << reply()->errorString(); qDebug() << "getting etag: request network error: " << reply()->errorString();
@@ -122,10 +120,11 @@ void MkColJob::start()
} }
// assumes ownership // assumes ownership
QNetworkReply *reply = _url.isValid() ? davRequest("MKCOL", _url, req) if (_url.isValid()) {
: davRequest("MKCOL", path(), req); sendRequest("MKCOL", _url, req);
setReply(reply); } else {
setupConnections(reply); sendRequest("MKCOL", makeDavUrl(path()), req);
}
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -322,11 +321,11 @@ void LsColJob::start()
QBuffer *buf = new QBuffer(this); QBuffer *buf = new QBuffer(this);
buf->setData(xml); buf->setData(xml);
buf->open(QIODevice::ReadOnly); buf->open(QIODevice::ReadOnly);
QNetworkReply *reply = _url.isValid() ? davRequest("PROPFIND", _url, req, buf) if (_url.isValid()) {
: davRequest("PROPFIND", path(), req, buf); sendRequest("PROPFIND", _url, req, buf);
buf->setParent(reply); } else {
setReply(reply); sendRequest("PROPFIND", makeDavUrl(path()), req, buf);
setupConnections(reply); }
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -380,8 +379,7 @@ CheckServerJob::CheckServerJob(AccountPtr account, QObject *parent)
void CheckServerJob::start() void CheckServerJob::start()
{ {
setReply(getRequest(path())); sendRequest("GET", makeAccountUrl(path()));
setupConnections(reply());
connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(metaDataChangedSlot())); connect(reply(), SIGNAL(metaDataChanged()), this, SLOT(metaDataChangedSlot()));
connect(reply(), SIGNAL(encrypted()), this, SLOT(encryptedSlot())); connect(reply(), SIGNAL(encrypted()), this, SLOT(encryptedSlot()));
AbstractNetworkJob::start(); AbstractNetworkJob::start();
@@ -476,10 +474,7 @@ bool CheckServerJob::finished()
} }
qDebug() << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply(); qDebug() << "status.php returns: " << status << " " << reply()->error() << " Reply: " << reply();
if( status.contains("installed") if( status.contains("installed") ) {
&& status.contains("version")
&& status.contains("versionstring") ) {
emit instanceFound(reply()->url(), status); emit instanceFound(reply()->url(), status);
} else { } else {
qDebug() << "No proper answer on " << reply()->url(); qDebug() << "No proper answer on " << reply()->url();
@@ -529,9 +524,7 @@ void PropfindJob::start()
QBuffer *buf = new QBuffer(this); QBuffer *buf = new QBuffer(this);
buf->setData(xml); buf->setData(xml);
buf->open(QIODevice::ReadOnly); buf->open(QIODevice::ReadOnly);
setReply(davRequest("PROPFIND", path(), req, buf)); sendRequest("PROPFIND", makeDavUrl(path()), req, buf);
buf->setParent(reply());
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -632,9 +625,7 @@ void ProppatchJob::start()
QBuffer *buf = new QBuffer(this); QBuffer *buf = new QBuffer(this);
buf->setData(xml); buf->setData(xml);
buf->open(QIODevice::ReadOnly); buf->open(QIODevice::ReadOnly);
setReply(davRequest("PROPPATCH", path(), req, buf)); sendRequest("PROPPATCH", makeDavUrl(path()), req, buf);
buf->setParent(reply());
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -671,8 +662,7 @@ EntityExistsJob::EntityExistsJob(AccountPtr account, const QString &path, QObjec
void EntityExistsJob::start() void EntityExistsJob::start()
{ {
setReply(headRequest(path())); sendRequest("HEAD", makeAccountUrl(path()));
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -700,8 +690,7 @@ void JsonApiJob::start()
QList<QPair<QString, QString> > params = _additionalParams; QList<QPair<QString, QString> > params = _additionalParams;
params << qMakePair(QString::fromLatin1("format"), QString::fromLatin1("json")); params << qMakePair(QString::fromLatin1("format"), QString::fromLatin1("json"));
url.setQueryItems(params); url.setQueryItems(params);
setReply(davRequest("GET", url, req)); sendRequest("GET", url, req);
setupConnections(reply());
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }

View File

@@ -94,32 +94,132 @@ int OwncloudPropagator::hardMaximumActiveJob()
return max; return max;
} }
/** Updates, creates or removes a blacklist entry for the given item. PropagateItemJob::~PropagateItemJob()
*
* Returns whether the error should be suppressed.
*/
static bool blacklistCheck(SyncJournalDb* journal, const SyncFileItem& item)
{ {
SyncJournalErrorBlacklistRecord oldEntry = journal->errorBlacklistEntry(item._file); if (auto p = propagator()) {
SyncJournalErrorBlacklistRecord newEntry = SyncJournalErrorBlacklistRecord::update(oldEntry, item); // Normally, every job should clean itself from the _activeJobList. So this should not be
// needed. But if a job has a bug or is deleted before the network jobs signal get received,
if (newEntry.isValid()) { // we might risk end up with dangling pointer in the list which may cause crashes.
journal->updateErrorBlacklistEntry(newEntry); p->_activeJobList.removeAll(this);
} else if (oldEntry.isValid()) {
journal->wipeErrorBlacklistEntry(item._file);
} }
// In some cases we add errors to the blacklist for tracking, but don't
// want to actively suppress them.
return newEntry.isValid() && newEntry._ignoreDuration > 0;
} }
void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorString) static time_t getMinBlacklistTime()
{ {
return qMax(qgetenv("OWNCLOUD_BLACKLIST_TIME_MIN").toInt(),
25); // 25 seconds
}
static time_t getMaxBlacklistTime()
{
int v = qgetenv("OWNCLOUD_BLACKLIST_TIME_MAX").toInt();
if (v > 0)
return v;
return 24*60*60; // 1 day
}
/** Creates a blacklist entry, possibly taking into account an old one.
*
* The old entry may be invalid, then a fresh entry is created.
*/
static SyncJournalErrorBlacklistRecord createBlacklistEntry(
const SyncJournalErrorBlacklistRecord& old, const SyncFileItem& item)
{
SyncJournalErrorBlacklistRecord entry;
entry._errorString = item._errorString;
entry._lastTryModtime = item._modtime;
entry._lastTryEtag = item._etag;
entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime());
entry._file = item._file;
entry._renameTarget = item._renameTarget;
entry._retryCount = old._retryCount + 1;
static time_t minBlacklistTime(getMinBlacklistTime());
static time_t maxBlacklistTime(qMax(getMaxBlacklistTime(), minBlacklistTime));
// The factor of 5 feels natural: 25s, 2 min, 10 min, ~1h, ~5h, ~24h
entry._ignoreDuration = old._ignoreDuration * 5;
if( item._httpErrorCode == 403 ) {
qDebug() << "Probably firewall error: " << item._httpErrorCode << ", blacklisting up to 1h only";
entry._ignoreDuration = qMin(entry._ignoreDuration, time_t(60*60));
} else if( item._httpErrorCode == 413 || item._httpErrorCode == 415 ) {
qDebug() << "Fatal Error condition" << item._httpErrorCode << ", maximum blacklist ignore time!";
entry._ignoreDuration = maxBlacklistTime;
}
entry._ignoreDuration = qBound(minBlacklistTime, entry._ignoreDuration, maxBlacklistTime);
if( item._status == SyncFileItem::SoftError ) {
// Track these errors, but don't actively suppress them.
entry._ignoreDuration = 0;
}
return entry;
}
/** Updates, creates or removes a blacklist entry for the given item.
*
* May adjust the status or item._errorString.
*/
static void blacklistUpdate(SyncJournalDb* journal, SyncFileItem& item)
{
SyncJournalErrorBlacklistRecord oldEntry = journal->errorBlacklistEntry(item._file);
bool mayBlacklist =
item._errorMayBeBlacklisted // explicitly flagged for blacklisting
|| ((item._status == SyncFileItem::NormalError
|| item._status == SyncFileItem::SoftError)
&& item._httpErrorCode != 0 // or non-local error
);
// No new entry? Possibly remove the old one, then done.
if (!mayBlacklist) {
if (oldEntry.isValid()) {
journal->wipeErrorBlacklistEntry(item._file);
}
return;
}
auto newEntry = createBlacklistEntry(oldEntry, item);
journal->updateErrorBlacklistEntry(newEntry);
// Suppress the error if it was and continues to be blacklisted.
// An ignoreDuration of 0 mean we're tracking the error, but not actively
// suppressing it.
if (item._hasBlacklistEntry && newEntry._ignoreDuration > 0) {
item._status = SyncFileItem::FileIgnored;
item._errorString.prepend(PropagateItemJob::tr("Continue blacklisting:") + " ");
qDebug() << "blacklisting " << item._file
<< " for " << newEntry._ignoreDuration
<< ", retry count " << newEntry._retryCount;
return;
}
// Some soft errors might become louder on repeat occurrence
if (item._status == SyncFileItem::SoftError
&& newEntry._retryCount > 1) {
qDebug() << "escalating soft error on " << item._file
<< " to normal error, " << item._httpErrorCode;
item._status = SyncFileItem::NormalError;
return;
}
}
void PropagateItemJob::done(SyncFileItem::Status statusArg, const QString &errorString)
{
_item->_status = statusArg;
_state = Finished; _state = Finished;
if (_item->_isRestoration) { if (_item->_isRestoration) {
if( status == SyncFileItem::Success || status == SyncFileItem::Conflict) { if( _item->_status == SyncFileItem::Success
status = SyncFileItem::Restoration; || _item->_status == SyncFileItem::Conflict) {
_item->_status = SyncFileItem::Restoration;
} else { } else {
_item->_errorString += tr("; Restoration Failed: %1").arg(errorString); _item->_errorString += tr("; Restoration Failed: %1").arg(errorString);
} }
@@ -130,24 +230,18 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
} }
if( propagator()->_abortRequested.fetchAndAddRelaxed(0) && if( propagator()->_abortRequested.fetchAndAddRelaxed(0) &&
(status == SyncFileItem::NormalError || status == SyncFileItem::FatalError)) { (_item->_status == SyncFileItem::NormalError
|| _item->_status == SyncFileItem::FatalError)) {
// an abort request is ongoing. Change the status to Soft-Error // an abort request is ongoing. Change the status to Soft-Error
status = SyncFileItem::SoftError; _item->_status = SyncFileItem::SoftError;
} }
switch( status ) { switch( _item->_status ) {
case SyncFileItem::SoftError: case SyncFileItem::SoftError:
case SyncFileItem::FatalError: case SyncFileItem::FatalError:
case SyncFileItem::NormalError: case SyncFileItem::NormalError:
// For normal errors, we blacklist aggressively, otherwise only on // Check the blacklist, possibly adjusting the item (including its status)
// explicit request. blacklistUpdate(propagator()->_journal, *_item);
if ((status == SyncFileItem::NormalError || _item->_errorMayBeBlacklisted)
&& blacklistCheck(propagator()->_journal, *_item)
&& _item->_hasBlacklistEntry) {
// do not error if the item was, and continues to be, blacklisted
status = SyncFileItem::FileIgnored;
_item->_errorString.prepend(tr("Continue blacklisting:") + " ");
}
break; break;
case SyncFileItem::Success: case SyncFileItem::Success:
case SyncFileItem::Restoration: case SyncFileItem::Restoration:
@@ -167,10 +261,8 @@ void PropagateItemJob::done(SyncFileItem::Status status, const QString &errorStr
break; break;
} }
_item->_status = status;
emit itemCompleted(_item); emit itemCompleted(_item);
emit finished(status); emit finished(_item->_status);
} }
/** /**
@@ -509,6 +601,36 @@ bool OwncloudPropagator::localFileNameClash( const QString& relFile )
return re; return re;
} }
bool OwncloudPropagator::hasCaseClashAccessibilityProblem(const QString &relfile)
{
#ifdef Q_OS_WIN
bool result = false;
const QString file( _localDir + relfile );
WIN32_FIND_DATA FindFileData;
HANDLE hFind;
hFind = FindFirstFileW(reinterpret_cast<const wchar_t*>(file.utf16()), &FindFileData);
if (hFind != INVALID_HANDLE_VALUE) {
QString firstFile = QString::fromWCharArray( FindFileData.cFileName );
if (FindNextFile(hFind, &FindFileData)) {
QString secondFile = QString::fromWCharArray( FindFileData.cFileName );
// This extra check shouldn't be necessary, but ensures that there
// are two different filenames that are identical when case is ignored.
if (firstFile != secondFile
&& QString::compare(firstFile, secondFile, Qt::CaseInsensitive) == 0) {
result = true;
qDebug() << "Found two filepaths that only differ in case: " << firstFile << secondFile;
}
}
FindClose(hFind);
}
return result;
#else
Q_UNUSED(relfile);
return false;
#endif
}
QString OwncloudPropagator::getFilePath(const QString& tmp_file_name) const QString OwncloudPropagator::getFilePath(const QString& tmp_file_name) const
{ {
return _localDir + tmp_file_name; return _localDir + tmp_file_name;
@@ -669,9 +791,11 @@ void PropagateDirectory::slotSubJobFinished(SyncFileItem::Status status)
if (status == SyncFileItem::FatalError || if (status == SyncFileItem::FatalError ||
(wasFirstJob && status != SyncFileItem::Success && status != SyncFileItem::Restoration)) { (wasFirstJob && status != SyncFileItem::Success && status != SyncFileItem::Restoration)) {
abort(); if (_state != Finished) {
_state = Finished; abort();
emit finished(status); _state = Finished;
emit finished(status);
}
return; return;
} else if (status == SyncFileItem::NormalError || status == SyncFileItem::SoftError) { } else if (status == SyncFileItem::NormalError || status == SyncFileItem::SoftError) {
_hasError = status; _hasError = status;
@@ -688,6 +812,9 @@ void PropagateDirectory::slotSubJobFinished(SyncFileItem::Status status)
void PropagateDirectory::finalize() void PropagateDirectory::finalize()
{ {
if (_state == Finished)
return;
bool ok = true; bool ok = true;
if (!_item->isEmpty() && _hasError == SyncFileItem::NoStatus) { if (!_item->isEmpty() && _hasError == SyncFileItem::NoStatus) {
if( !_item->_renameTarget.isEmpty() ) { if( !_item->_renameTarget.isEmpty() ) {

View File

@@ -160,6 +160,7 @@ private:
public: public:
PropagateItemJob(OwncloudPropagator* propagator, const SyncFileItemPtr &item) PropagateItemJob(OwncloudPropagator* propagator, const SyncFileItemPtr &item)
: PropagatorJob(propagator), _item(item) {} : PropagatorJob(propagator), _item(item) {}
~PropagateItemJob();
bool scheduleNextJob() Q_DECL_OVERRIDE { bool scheduleNextJob() Q_DECL_OVERRIDE {
if (_state != NotYetStarted) { if (_state != NotYetStarted) {
@@ -256,10 +257,7 @@ public:
class OwncloudPropagator : public QObject { class OwncloudPropagator : public QObject {
Q_OBJECT Q_OBJECT
PropagateItemJob *createJob(const SyncFileItemPtr& item); PropagateItemJob *createJob(const SyncFileItemPtr& item);
QScopedPointer<PropagateDirectory> _rootJob;
public: public:
const QString _localDir; // absolute path to the local directory. ends with '/' const QString _localDir; // absolute path to the local directory. ends with '/'
const QString _remoteFolder; // remote folder, ends with '/' const QString _remoteFolder; // remote folder, ends with '/'
@@ -307,7 +305,23 @@ public:
int hardMaximumActiveJob(); int hardMaximumActiveJob();
bool isInSharedDirectory(const QString& file); bool isInSharedDirectory(const QString& file);
/** Check whether a download would clash with an existing file
* in filesystems that are only case-preserving.
*/
bool localFileNameClash(const QString& relfile); bool localFileNameClash(const QString& relfile);
/** Check whether a file is properly accessible for upload.
*
* It is possible to create files with filenames that differ
* only by case in NTFS, but most operations such as stat and
* open only target one of these by default.
*
* When that happens, we want to avoid uploading incorrect data
* and give up on the file.
*/
bool hasCaseClashAccessibilityProblem(const QString& relfile);
QString getFilePath(const QString& tmp_file_name) const; QString getFilePath(const QString& tmp_file_name) const;
void abort() { void abort() {
@@ -369,6 +383,7 @@ signals:
private: private:
AccountPtr _account; AccountPtr _account;
QScopedPointer<PropagateDirectory> _rootJob;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
// access to signals which are protected in Qt4 // access to signals which are protected in Qt4

View File

@@ -17,6 +17,8 @@
#include <QString> #include <QString>
#include <QDebug> #include <QDebug>
#include <QFile> #include <QFile>
#include <QFileInfo>
#include <QDir>
#include "ownsql.h" #include "ownsql.h"
#include "utility.h" #include "utility.h"
@@ -101,8 +103,8 @@ bool SqlDatabase::openOrCreateReadWrite( const QString& filename )
if( !checkDb() ) { if( !checkDb() ) {
// When disk space is low, checking the db may fail even though it's fine. // When disk space is low, checking the db may fail even though it's fine.
qint64 freeSpace = Utility::freeDiskSpace(filename); qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath());
if (freeSpace < 1000000) { if (freeSpace != -1 && freeSpace < 1000000) {
qDebug() << "Consistency check failed, disk space is low, aborting" << freeSpace; qDebug() << "Consistency check failed, disk space is low, aborting" << freeSpace;
close(); close();
return false; return false;

View File

@@ -100,12 +100,11 @@ void GETFileJob::start() {
} }
if (_directDownloadUrl.isEmpty()) { if (_directDownloadUrl.isEmpty()) {
setReply(davRequest("GET", path(), req)); sendRequest("GET", makeDavUrl(path()), req);
} else { } else {
// Use direct URL // Use direct URL
setReply(davRequest("GET", _directDownloadUrl, req)); sendRequest("GET", _directDownloadUrl, req);
} }
setupConnections(reply());
reply()->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth reply()->setReadBufferSize(16 * 1024); // keep low so we can easier limit the bandwidth
qDebug() << Q_FUNC_INFO << _bandwidthManager << _bandwidthChoked << _bandwidthLimited; qDebug() << Q_FUNC_INFO << _bandwidthManager << _bandwidthChoked << _bandwidthLimited;
@@ -232,6 +231,8 @@ qint64 GETFileJob::currentDownloadPosition()
void GETFileJob::slotReadyRead() void GETFileJob::slotReadyRead()
{ {
if (!reply())
return;
int bufferSize = qMin(1024*8ll , reply()->bytesAvailable()); int bufferSize = qMin(1024*8ll , reply()->bytesAvailable());
QByteArray buffer(bufferSize, Qt::Uninitialized); QByteArray buffer(bufferSize, Qt::Uninitialized);
@@ -768,7 +769,7 @@ void PropagateDownloadFile::downloadFinished()
// Apply the remote permissions // Apply the remote permissions
// Older server versions sometimes provide empty remote permissions // Older server versions sometimes provide empty remote permissions
// see #4450 - don't adjust the write permissions there. // see #4450 - don't adjust the write permissions there.
const int serverVersionGoodRemotePerm = 0x070000; // 7.0.0 const int serverVersionGoodRemotePerm = Account::makeServerVersion(7, 0, 0);
if (propagator()->account()->serverVersionInt() >= serverVersionGoodRemotePerm) { if (propagator()->account()->serverVersionInt() >= serverVersionGoodRemotePerm) {
FileSystem::setFileReadOnlyWeak(_tmpFile.fileName(), FileSystem::setFileReadOnlyWeak(_tmpFile.fileName(),
!_item->_remotePerm.contains('W')); !_item->_remotePerm.contains('W'));

View File

@@ -30,8 +30,11 @@ DeleteJob::DeleteJob(AccountPtr account, const QUrl& url, QObject* parent)
void DeleteJob::start() void DeleteJob::start()
{ {
QNetworkRequest req; QNetworkRequest req;
setReply(_url.isValid() ? davRequest("DELETE", _url, req) : davRequest("DELETE", path(), req)); if (_url.isValid()) {
setupConnections(reply()); sendRequest("DELETE", _url, req);
} else {
sendRequest("DELETE", makeDavUrl(path()), req);
}
if( reply()->error() != QNetworkReply::NoError ) { if( reply()->error() != QNetworkReply::NoError ) {
qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString(); qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString();

View File

@@ -43,8 +43,11 @@ void MoveJob::start()
for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) { for(auto it = _extraHeaders.constBegin(); it != _extraHeaders.constEnd(); ++it) {
req.setRawHeader(it.key(), it.value()); req.setRawHeader(it.key(), it.value());
} }
setReply(_url.isValid() ? davRequest("MOVE", _url, req) : davRequest("MOVE", path(), req)); if (_url.isValid()) {
setupConnections(reply()); sendRequest("MOVE", _url, req);
} else {
sendRequest("MOVE", makeDavUrl(path()), req);
}
if( reply()->error() != QNetworkReply::NoError ) { if( reply()->error() != QNetworkReply::NoError ) {
qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString(); qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString();

View File

@@ -73,9 +73,11 @@ void PUTFileJob::start() {
req.setRawHeader(it.key(), it.value()); req.setRawHeader(it.key(), it.value());
} }
setReply(_url.isValid() ? davRequest("PUT", _url, req, _device.data()) if (_url.isValid()) {
: davRequest("PUT", path(), req, _device.data())); sendRequest("PUT", _url, req, _device);
setupConnections(reply()); } else {
sendRequest("PUT", makeDavUrl(path()), req, _device);
}
if( reply()->error() != QNetworkReply::NoError ) { if( reply()->error() != QNetworkReply::NoError ) {
qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString(); qWarning() << Q_FUNC_INFO << " Network error: " << reply()->errorString();
@@ -89,10 +91,10 @@ void PUTFileJob::start() {
// (workaround disabled on windows and mac because the binaries we ship have patched qt) // (workaround disabled on windows and mac because the binaries we ship have patched qt)
#if QT_VERSION < QT_VERSION_CHECK(4, 8, 7) #if QT_VERSION < QT_VERSION_CHECK(4, 8, 7)
if (QLatin1String(qVersion()) < QLatin1String("4.8.7")) if (QLatin1String(qVersion()) < QLatin1String("4.8.7"))
connect(_device.data(), SIGNAL(wasReset()), this, SLOT(slotSoftAbort())); connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort()));
#elif QT_VERSION > QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(5, 4, 2) && !defined Q_OS_WIN && !defined Q_OS_MAC #elif QT_VERSION > QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(5, 4, 2) && !defined Q_OS_WIN && !defined Q_OS_MAC
if (QLatin1String(qVersion()) < QLatin1String("5.4.2")) if (QLatin1String(qVersion()) < QLatin1String("5.4.2"))
connect(_device.data(), SIGNAL(wasReset()), this, SLOT(slotSoftAbort())); connect(_device, SIGNAL(wasReset()), this, SLOT(slotSoftAbort()));
#endif #endif
AbstractNetworkJob::start(); AbstractNetworkJob::start();
@@ -119,8 +121,7 @@ void PollJob::start()
QUrl accountUrl = account()->url(); QUrl accountUrl = account()->url();
QUrl finalUrl = QUrl::fromUserInput(accountUrl.scheme() + QLatin1String("://") + accountUrl.authority() QUrl finalUrl = QUrl::fromUserInput(accountUrl.scheme() + QLatin1String("://") + accountUrl.authority()
+ (path().startsWith('/') ? QLatin1String("") : QLatin1String("/")) + path()); + (path().startsWith('/') ? QLatin1String("") : QLatin1String("/")) + path());
setReply(getRequest(finalUrl)); sendRequest("GET", finalUrl);
setupConnections(reply());
connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(resetTimeout())); connect(reply(), SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(resetTimeout()));
AbstractNetworkJob::start(); AbstractNetworkJob::start();
} }
@@ -198,6 +199,13 @@ void PropagateUploadFileCommon::start()
return; return;
} }
// Check if the specific file can be accessed
if( propagator()->hasCaseClashAccessibilityProblem(_item->_file) ) {
done( SyncFileItem::NormalError, tr("File %1 cannot be uploaded because another file with the same name, differing only in case, exists")
.arg(QDir::toNativeSeparators(_item->_file)) );
return;
}
propagator()->_activeJobList.append(this); propagator()->_activeJobList.append(this);
if (!_deleteExisting) { if (!_deleteExisting) {

View File

@@ -86,7 +86,7 @@ class PUTFileJob : public AbstractNetworkJob {
Q_OBJECT Q_OBJECT
private: private:
QScopedPointer<QIODevice> _device; QIODevice* _device;
QMap<QByteArray, QByteArray> _headers; QMap<QByteArray, QByteArray> _headers;
QString _errorString; QString _errorString;
QUrl _url; QUrl _url;
@@ -95,11 +95,17 @@ public:
// Takes ownership of the device // Takes ownership of the device
explicit PUTFileJob(AccountPtr account, const QString& path, QIODevice *device, explicit PUTFileJob(AccountPtr account, const QString& path, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0) const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, path, parent), _device(device), _headers(headers), _chunk(chunk) {} : AbstractNetworkJob(account, path, parent), _device(device), _headers(headers), _chunk(chunk)
{
_device->setParent(this);
}
explicit PUTFileJob(AccountPtr account, const QUrl& url, QIODevice *device, explicit PUTFileJob(AccountPtr account, const QUrl& url, QIODevice *device,
const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0) const QMap<QByteArray, QByteArray> &headers, int chunk, QObject* parent = 0)
: AbstractNetworkJob(account, QString(), parent), _device(device), _headers(headers) : AbstractNetworkJob(account, QString(), parent), _device(device), _headers(headers)
, _url(url), _chunk(chunk) {} , _url(url), _chunk(chunk)
{
_device->setParent(this);
}
~PUTFileJob(); ~PUTFileJob();
int _chunk; int _chunk;

View File

@@ -292,6 +292,8 @@ void PropagateUploadFileNG::startNextChunk()
_transmissionChecksumType, _transmissionChecksum); _transmissionChecksumType, _transmissionChecksum);
} }
headers["OC-Total-Length"] = QByteArray::number(fileSize);
auto job = new MoveJob(propagator()->account(), Utility::concatUrlPath(chunkUrl(), "/.file"), auto job = new MoveJob(propagator()->account(), Utility::concatUrlPath(chunkUrl(), "/.file"),
destination, headers, this); destination, headers, this);
_jobs.append(job); _jobs.append(job);

View File

@@ -145,7 +145,7 @@ void PropagateUploadFileV1::startNextChunk()
parallelChunkUpload = env != "false" && env != "0"; parallelChunkUpload = env != "false" && env != "0";
} else { } else {
int versionNum = propagator()->account()->serverVersionInt(); int versionNum = propagator()->account()->serverVersionInt();
if (versionNum < 0x080003) { if (versionNum < Account::makeServerVersion(8, 0, 3)) {
// Disable parallel chunk upload severs older than 8.0.3 to avoid too many // Disable parallel chunk upload severs older than 8.0.3 to avoid too many
// internal sever errors (#2743, #2938) // internal sever errors (#2743, #2938)
parallelChunkUpload = false; parallelChunkUpload = false;

View File

@@ -230,7 +230,7 @@ bool SyncEngine::checkErrorBlacklisting( SyncFileItem &item )
// If duration has expired, it's not blacklisted anymore // If duration has expired, it's not blacklisted anymore
time_t now = Utility::qDateTimeToTime_t(QDateTime::currentDateTime()); time_t now = Utility::qDateTimeToTime_t(QDateTime::currentDateTime());
if( now > entry._lastTryTime + entry._ignoreDuration ) { if( now >= entry._lastTryTime + entry._ignoreDuration ) {
qDebug() << "blacklist entry for " << item._file << " has expired!"; qDebug() << "blacklist entry for " << item._file << " has expired!";
return false; return false;
} }
@@ -930,7 +930,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
} }
// Check for invalid character in old server version // Check for invalid character in old server version
if (_account->serverVersionInt() < 0x080100) { if (_account->serverVersionInt() < Account::makeServerVersion(8, 1, 0)) {
// Server version older than 8.1 don't support these character in filename. // Server version older than 8.1 don't support these character in filename.
static const QRegExp invalidCharRx("[\\\\:?*\"<>|]"); static const QRegExp invalidCharRx("[\\\\:?*\"<>|]");
for (auto it = syncItems.begin(); it != syncItems.end(); ++it) { for (auto it = syncItems.begin(); it != syncItems.end(); ++it) {
@@ -961,7 +961,8 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult)
&& _discoveryMainThread->_dataFingerprint != databaseFingerprint) { && _discoveryMainThread->_dataFingerprint != databaseFingerprint) {
qDebug() << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint; qDebug() << "data fingerprint changed, assume restore from backup" << databaseFingerprint << _discoveryMainThread->_dataFingerprint;
restoreOldFiles(syncItems); restoreOldFiles(syncItems);
} else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2 && _account->serverVersionInt() < 0x090100) { } else if (!_hasForwardInTimeFiles && _backInTimeFiles >= 2
&& _account->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
// The server before ownCloud 9.1 did not have the data-fingerprint property. So in that // The server before ownCloud 9.1 did not have the data-fingerprint property. So in that
// case we use heuristics to detect restored backup. This is disabled with newer version // case we use heuristics to detect restored backup. This is disabled with newer version
// because this causes troubles to the user and is not as reliable as the data-fingerprint. // because this causes troubles to the user and is not as reliable as the data-fingerprint.

View File

@@ -103,20 +103,6 @@ SyncFileItem SyncJournalFileRecord::toSyncFileItem()
return item; return item;
} }
static time_t getMinBlacklistTime()
{
return qMax(qgetenv("OWNCLOUD_BLACKLIST_TIME_MIN").toInt(),
25); // 25 seconds
}
static time_t getMaxBlacklistTime()
{
int v = qgetenv("OWNCLOUD_BLACKLIST_TIME_MAX").toInt();
if (v > 0)
return v;
return 24*60*60; // 1 day
}
bool SyncJournalErrorBlacklistRecord::isValid() const bool SyncJournalErrorBlacklistRecord::isValid() const
{ {
return ! _file.isEmpty() return ! _file.isEmpty()
@@ -124,54 +110,6 @@ bool SyncJournalErrorBlacklistRecord::isValid() const
&& _lastTryTime > 0; && _lastTryTime > 0;
} }
SyncJournalErrorBlacklistRecord SyncJournalErrorBlacklistRecord::update(
const SyncJournalErrorBlacklistRecord& old, const SyncFileItem& item)
{
SyncJournalErrorBlacklistRecord entry;
bool mayBlacklist =
item._errorMayBeBlacklisted // explicitly flagged for blacklisting
|| (item._httpErrorCode != 0 // or non-local error
#ifdef OWNCLOUD_5XX_NO_BLACKLIST
&& item._httpErrorCode / 100 != 5 // In this configuration, never blacklist error 5xx
#endif
);
if (!mayBlacklist) {
qDebug() << "This error is not blacklisted " << item._httpErrorCode << item._errorMayBeBlacklisted;
return entry;
}
static time_t minBlacklistTime(getMinBlacklistTime());
static time_t maxBlacklistTime(qMax(getMaxBlacklistTime(), minBlacklistTime));
entry._retryCount = old._retryCount + 1;
entry._errorString = item._errorString;
entry._lastTryModtime = item._modtime;
entry._lastTryEtag = item._etag;
entry._lastTryTime = Utility::qDateTimeToTime_t(QDateTime::currentDateTime());
// The factor of 5 feels natural: 25s, 2 min, 10 min, ~1h, ~5h, ~24h
entry._ignoreDuration = old._ignoreDuration * 5;
entry._file = item._file;
entry._renameTarget = item._renameTarget;
if( item._httpErrorCode == 403 ) {
qDebug() << "Probably firewall error: " << item._httpErrorCode << ", blacklisting up to 1h only";
entry._ignoreDuration = qMin(entry._ignoreDuration, time_t(60*60));
} else if( item._httpErrorCode == 413 || item._httpErrorCode == 415 ) {
qDebug() << "Fatal Error condition" << item._httpErrorCode << ", maximum blacklist ignore time!";
entry._ignoreDuration = maxBlacklistTime;
}
entry._ignoreDuration = qBound(minBlacklistTime, entry._ignoreDuration, maxBlacklistTime);
qDebug() << "blacklisting " << item._file
<< " for " << entry._ignoreDuration
<< ", retry count " << entry._retryCount;
return entry;
}
bool operator==(const SyncJournalFileRecord & lhs, bool operator==(const SyncJournalFileRecord & lhs,
const SyncJournalFileRecord & rhs) const SyncJournalFileRecord & rhs)
{ {

View File

@@ -93,15 +93,6 @@ public:
QString _renameTarget; QString _renameTarget;
bool isValid() const; bool isValid() const;
/** Takes an old blacklist entry and updates it for a new sync result.
*
* The old entry may be invalid, then a fresh entry is created.
* If the returned record is invalid, the file shall not be
* blacklisted.
*/
static SyncJournalErrorBlacklistRecord update(
const SyncJournalErrorBlacklistRecord& old, const SyncFileItem& item);
}; };
} }

View File

@@ -23,6 +23,7 @@
#ifndef TOKEN_AUTH_ONLY #ifndef TOKEN_AUTH_ONLY
#include <QtGui> #include <QtGui>
#endif #endif
#include <QSslSocket>
#include "owncloudtheme.h" #include "owncloudtheme.h"
@@ -297,8 +298,13 @@ QString Theme::gitSHA1() const
" on %3, %4 using Qt %5, %6</small></p>") " on %3, %4 using Qt %5, %6</small></p>")
.arg(githubPrefix+gitSha1).arg(gitSha1.left(6)) .arg(githubPrefix+gitSha1).arg(gitSha1.left(6))
.arg(__DATE__).arg(__TIME__) .arg(__DATE__).arg(__TIME__)
.arg(QT_VERSION_STR) .arg(QString::fromAscii(qVersion()))
.arg(QString::fromAscii(OPENSSL_VERSION_TEXT)); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
.arg(QSslSocket::sslLibraryVersionString());
#else
.arg(QCoreApplication::translate("ownCloudTheme::about()", "built with %1").arg(
QString::fromAscii(OPENSSL_VERSION_TEXT)));
#endif
#endif #endif
return devString; return devString;
} }

View File

@@ -122,8 +122,8 @@ QString Utility::octetsToString( qint64 octets )
QString s; QString s;
qreal value = octets; qreal value = octets;
// Whether we care about decimals: only for GB and only // Whether we care about decimals: only for GB/MB and only
// if it's less than 10 GB. // if it's less than 10 units.
bool round = true; bool round = true;
// do not display terra byte with the current units, as when // do not display terra byte with the current units, as when
@@ -137,6 +137,7 @@ QString Utility::octetsToString( qint64 octets )
} else if (octets >= mb) { } else if (octets >= mb) {
s = QCoreApplication::translate("Utility", "%L1 MB"); s = QCoreApplication::translate("Utility", "%L1 MB");
value /= mb; value /= mb;
round = false;
} else if (octets >= kb) { } else if (octets >= kb) {
s = QCoreApplication::translate("Utility", "%L1 KB"); s = QCoreApplication::translate("Utility", "%L1 KB");
value /= kb; value /= kb;

View File

@@ -43,6 +43,12 @@ namespace Utility
OWNCLOUDSYNC_EXPORT QByteArray userAgentString(); OWNCLOUDSYNC_EXPORT QByteArray userAgentString();
OWNCLOUDSYNC_EXPORT bool hasLaunchOnStartup(const QString &appName); OWNCLOUDSYNC_EXPORT bool hasLaunchOnStartup(const QString &appName);
OWNCLOUDSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString& guiName, bool launch); OWNCLOUDSYNC_EXPORT void setLaunchOnStartup(const QString &appName, const QString& guiName, bool launch);
/**
* Return the amount of free space available.
*
* \a path must point to a directory
*/
OWNCLOUDSYNC_EXPORT qint64 freeDiskSpace(const QString &path); OWNCLOUDSYNC_EXPORT qint64 freeDiskSpace(const QString &path);
/** /**

View File

@@ -536,6 +536,7 @@ public:
const FileInfo *fileInfo; const FileInfo *fileInfo;
char payload; char payload;
int size; int size;
bool aborted = false;
FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) FakeGetReply(FileInfo &remoteRootFileInfo, QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent)
: QNetworkReply{parent} { : QNetworkReply{parent} {
@@ -551,6 +552,12 @@ public:
} }
Q_INVOKABLE void respond() { Q_INVOKABLE void respond() {
if (aborted) {
setError(OperationCanceledError, "Operation Canceled");
emit metaDataChanged();
emit finished();
return;
}
payload = fileInfo->contentChar; payload = fileInfo->contentChar;
size = fileInfo->size; size = fileInfo->size;
setHeader(QNetworkRequest::ContentLengthHeader, size); setHeader(QNetworkRequest::ContentLengthHeader, size);
@@ -564,8 +571,14 @@ public:
emit finished(); emit finished();
} }
void abort() override { } void abort() override {
qint64 bytesAvailable() const override { return size + QIODevice::bytesAvailable(); } aborted = true;
}
qint64 bytesAvailable() const override {
if (aborted)
return 0;
return size + QIODevice::bytesAvailable();
}
qint64 readData(char *data, qint64 maxlen) override { qint64 readData(char *data, qint64 maxlen) override {
qint64 len = std::min(qint64{size}, maxlen); qint64 len = std::min(qint64{size}, maxlen);
@@ -666,8 +679,9 @@ class FakeErrorReply : public QNetworkReply
{ {
Q_OBJECT Q_OBJECT
public: public:
FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QObject *parent) FakeErrorReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request,
: QNetworkReply{parent} { QObject *parent, int httpErrorCode)
: QNetworkReply{parent}, _httpErrorCode(httpErrorCode) {
setRequest(request); setRequest(request);
setUrl(request.url()); setUrl(request.url());
setOperation(op); setOperation(op);
@@ -676,7 +690,7 @@ public:
} }
Q_INVOKABLE void respond() { Q_INVOKABLE void respond() {
setAttribute(QNetworkRequest::HttpStatusCodeAttribute, 500); setAttribute(QNetworkRequest::HttpStatusCodeAttribute, _httpErrorCode);
setError(InternalServerError, "Internal Server Fake Error"); setError(InternalServerError, "Internal Server Fake Error");
emit metaDataChanged(); emit metaDataChanged();
emit finished(); emit finished();
@@ -684,18 +698,22 @@ public:
void abort() override { } void abort() override { }
qint64 readData(char *, qint64) override { return 0; } qint64 readData(char *, qint64) override { return 0; }
int _httpErrorCode;
}; };
class FakeQNAM : public QNetworkAccessManager class FakeQNAM : public QNetworkAccessManager
{ {
FileInfo _remoteRootFileInfo; FileInfo _remoteRootFileInfo;
FileInfo _uploadFileInfo; FileInfo _uploadFileInfo;
QStringList _errorPaths; // maps a path to an HTTP error
QHash<QString, int> _errorPaths;
public: public:
FakeQNAM(FileInfo initialRoot) : _remoteRootFileInfo{std::move(initialRoot)} { } FakeQNAM(FileInfo initialRoot) : _remoteRootFileInfo{std::move(initialRoot)} { }
FileInfo &currentRemoteState() { return _remoteRootFileInfo; } FileInfo &currentRemoteState() { return _remoteRootFileInfo; }
FileInfo &uploadState() { return _uploadFileInfo; } FileInfo &uploadState() { return _uploadFileInfo; }
QStringList &errorPaths() { return _errorPaths; }
QHash<QString, int> &errorPaths() { return _errorPaths; }
protected: protected:
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QNetworkReply *createRequest(Operation op, const QNetworkRequest &request,
@@ -703,7 +721,7 @@ protected:
const QString fileName = getFilePathFromUrl(request.url()); const QString fileName = getFilePathFromUrl(request.url());
Q_ASSERT(!fileName.isNull()); Q_ASSERT(!fileName.isNull());
if (_errorPaths.contains(fileName)) if (_errorPaths.contains(fileName))
return new FakeErrorReply{op, request, this}; return new FakeErrorReply{op, request, this, _errorPaths[fileName]};
bool isUpload = request.url().path().startsWith(sUploadUrl.path()); bool isUpload = request.url().path().startsWith(sUploadUrl.path());
FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo; FileInfo &info = isUpload ? _uploadFileInfo : _remoteRootFileInfo;
@@ -712,13 +730,13 @@ protected:
if (verb == QLatin1String("PROPFIND")) if (verb == QLatin1String("PROPFIND"))
// Ignore outgoingData always returning somethign good enough, works for now. // Ignore outgoingData always returning somethign good enough, works for now.
return new FakePropfindReply{info, op, request, this}; return new FakePropfindReply{info, op, request, this};
else if (verb == QLatin1String("GET")) else if (verb == QLatin1String("GET") || op == QNetworkAccessManager::GetOperation)
return new FakeGetReply{info, op, request, this}; return new FakeGetReply{info, op, request, this};
else if (verb == QLatin1String("PUT")) else if (verb == QLatin1String("PUT") || op == QNetworkAccessManager::PutOperation)
return new FakePutReply{info, op, request, outgoingData->readAll(), this}; return new FakePutReply{info, op, request, outgoingData->readAll(), this};
else if (verb == QLatin1String("MKCOL")) else if (verb == QLatin1String("MKCOL"))
return new FakeMkcolReply{info, op, request, this}; return new FakeMkcolReply{info, op, request, this};
else if (verb == QLatin1String("DELETE")) else if (verb == QLatin1String("DELETE") || op == QNetworkAccessManager::DeleteOperation)
return new FakeDeleteReply{info, op, request, this}; return new FakeDeleteReply{info, op, request, this};
else if (verb == QLatin1String("MOVE") && !isUpload) else if (verb == QLatin1String("MOVE") && !isUpload)
return new FakeMoveReply{info, op, request, this}; return new FakeMoveReply{info, op, request, this};
@@ -798,7 +816,13 @@ public:
FileInfo currentRemoteState() { return _fakeQnam->currentRemoteState(); } FileInfo currentRemoteState() { return _fakeQnam->currentRemoteState(); }
FileInfo &uploadState() { return _fakeQnam->uploadState(); } FileInfo &uploadState() { return _fakeQnam->uploadState(); }
QStringList &serverErrorPaths() { return _fakeQnam->errorPaths(); } struct ErrorList {
FakeQNAM *_qnam;
void append(const QString &path, int error = 500)
{ _qnam->errorPaths().insert(path, error); }
void clear() { _qnam->errorPaths().clear(); }
};
ErrorList serverErrorPaths() { return {_fakeQnam}; }
QString localPath() const { QString localPath() const {
// SyncEngine wants a trailing slash // SyncEngine wants a trailing slash

View File

@@ -225,6 +225,40 @@ private slots:
QCOMPARE(finishedSpy.size(), 1); QCOMPARE(finishedSpy.size(), 1);
QCOMPARE(finishedSpy.first().first().toBool(), false); QCOMPARE(finishedSpy.first().first().toBool(), false);
} }
void testDirDownloadWithError() {
FakeFolder fakeFolder{FileInfo::A12_B12_C12_S12()};
QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
fakeFolder.remoteModifier().mkdir("Y");
fakeFolder.remoteModifier().mkdir("Y/Z");
fakeFolder.remoteModifier().insert("Y/Z/d0");
fakeFolder.remoteModifier().insert("Y/Z/d1");
fakeFolder.remoteModifier().insert("Y/Z/d2");
fakeFolder.remoteModifier().insert("Y/Z/d3");
fakeFolder.remoteModifier().insert("Y/Z/d4");
fakeFolder.remoteModifier().insert("Y/Z/d5");
fakeFolder.remoteModifier().insert("Y/Z/d6");
fakeFolder.remoteModifier().insert("Y/Z/d7");
fakeFolder.remoteModifier().insert("Y/Z/d8");
fakeFolder.remoteModifier().insert("Y/Z/d9");
fakeFolder.serverErrorPaths().append("Y/Z/d2", 503); // 503 is a fatal error
fakeFolder.serverErrorPaths().append("Y/Z/d3", 503); // 503 is a fatal error
QVERIFY(!fakeFolder.syncOnce());
QCoreApplication::processEvents(); // should not crash
QSet<QString> seen;
for(const QList<QVariant> &args : completeSpy) {
auto item = args[0].value<SyncFileItemPtr>();
qDebug() << item->_file << item->_isDirectory << item->_status;
QVERIFY(!seen.contains(item->_file)); // signal only sent once per item
seen.insert(item->_file);
if (item->_file == "Y/Z/d2" || item->_file == "Y/Z/d3") {
QVERIFY(item->_status == SyncFileItem::FatalError);
}
QVERIFY(item->_file != "Y/Z/d9"); // we should have aborted the sync before d9 starts
}
}
}; };
QTEST_GUILESS_MAIN(TestSyncEngine) QTEST_GUILESS_MAIN(TestSyncEngine)

View File

@@ -40,7 +40,7 @@ private slots:
QCOMPARE(octetsToString(10240) , QString("10 KB")); QCOMPARE(octetsToString(10240) , QString("10 KB"));
QCOMPARE(octetsToString(123456) , QString("121 KB")); QCOMPARE(octetsToString(123456) , QString("121 KB"));
QCOMPARE(octetsToString(1234567) , QString("1 MB")); QCOMPARE(octetsToString(1234567) , QString("1.2 MB"));
QCOMPARE(octetsToString(12345678) , QString("12 MB")); QCOMPARE(octetsToString(12345678) , QString("12 MB"));
QCOMPARE(octetsToString(123456789) , QString("118 MB")); QCOMPARE(octetsToString(123456789) , QString("118 MB"));
QCOMPARE(octetsToString(1000LL*1000*1000 * 5) , QString("4.7 GB")); QCOMPARE(octetsToString(1000LL*1000*1000 * 5) , QString("4.7 GB"));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff