mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2026-04-03 18:11:32 +02:00
Compare commits
227 Commits
v2.4.0-bet
...
v2.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6e9755121 | ||
|
|
f283c0c59c | ||
|
|
0d83d7cb68 | ||
|
|
2e10cec2cb | ||
|
|
3802131bad | ||
|
|
5467950f62 | ||
|
|
3ba5bb52f3 | ||
|
|
5c011c5c21 | ||
|
|
532e975687 | ||
|
|
7efc3220f4 | ||
|
|
b5444def55 | ||
|
|
c7ba898fe0 | ||
|
|
7a8c8b19c2 | ||
|
|
72c9d207a0 | ||
|
|
1e4727cfec | ||
|
|
8237c5be69 | ||
|
|
63877d5293 | ||
|
|
737353a017 | ||
|
|
528b5e3108 | ||
|
|
5d098140e7 | ||
|
|
aeb4eed7b5 | ||
|
|
c68b112858 | ||
|
|
b4f2c3c369 | ||
|
|
084f522de1 | ||
|
|
5d5e0220b4 | ||
|
|
7b5ce24302 | ||
|
|
19e33d0924 | ||
|
|
f8a4994307 | ||
|
|
04fb952814 | ||
|
|
8d6e9523bb | ||
|
|
b317cc18d0 | ||
|
|
03a5833e29 | ||
|
|
91a9c65173 | ||
|
|
edabed9594 | ||
|
|
838beded20 | ||
|
|
7dcfe9993c | ||
|
|
b73e2fbdab | ||
|
|
d49771b43a | ||
|
|
928cdf4f4f | ||
|
|
8da6fc40a1 | ||
|
|
6e84a8d420 | ||
|
|
29557ea550 | ||
|
|
37a93dca63 | ||
|
|
b632b7e0fa | ||
|
|
fc8e3b0914 | ||
|
|
861d777899 | ||
|
|
ff3e3ce886 | ||
|
|
567f7eb205 | ||
|
|
c3c8ab85ec | ||
|
|
d1c887d754 | ||
|
|
991f4faafb | ||
|
|
371acb296d | ||
|
|
1119bcf539 | ||
|
|
d67018311f | ||
|
|
eaecec418e | ||
|
|
ab27bcacdf | ||
|
|
536a051460 | ||
|
|
259a117db5 | ||
|
|
167939a8c9 | ||
|
|
41ebfb635e | ||
|
|
017b8e9de3 | ||
|
|
6453c57979 | ||
|
|
a01c73be19 | ||
|
|
f63737ecb0 | ||
|
|
dd87799a82 | ||
|
|
da6b515bfc | ||
|
|
f9a03fa288 | ||
|
|
a5c66cf289 | ||
|
|
41b908e293 | ||
|
|
6ffed87b08 | ||
|
|
bd5ea43547 | ||
|
|
8ba27cb0f1 | ||
|
|
1ab8bb62ae | ||
|
|
6f8248ebfd | ||
|
|
33306dcc38 | ||
|
|
888818a9f7 | ||
|
|
d90229242e | ||
|
|
b14ba325d5 | ||
|
|
628e310501 | ||
|
|
131647442f | ||
|
|
01b79f2ff1 | ||
|
|
73062e21a3 | ||
|
|
e7e6584cab | ||
|
|
7373c68aeb | ||
|
|
13f1122c50 | ||
|
|
ba5fb5aca7 | ||
|
|
55a91926c1 | ||
|
|
b5e129aa6b | ||
|
|
9fc175231d | ||
|
|
4dd0a75cce | ||
|
|
3cfd502f7e | ||
|
|
452a99f7d3 | ||
|
|
a9d37a0784 | ||
|
|
22b19636e9 | ||
|
|
6ee3310e2b | ||
|
|
065b1eed11 | ||
|
|
dbc2d4a8b6 | ||
|
|
7eb2dc21af | ||
|
|
8fb9700869 | ||
|
|
f95e044206 | ||
|
|
795ab29514 | ||
|
|
72262b565e | ||
|
|
06a86033c1 | ||
|
|
e17d5defe3 | ||
|
|
959d60e957 | ||
|
|
31f516f390 | ||
|
|
b6835186a3 | ||
|
|
c32ba4aee3 | ||
|
|
e720f84005 | ||
|
|
48ef2e4563 | ||
|
|
a0d9fc4354 | ||
|
|
e4b2d27c65 | ||
|
|
c846a329a8 | ||
|
|
c1bf90ddad | ||
|
|
b418bf6db4 | ||
|
|
72363155d8 | ||
|
|
f254ee3211 | ||
|
|
497b327d43 | ||
|
|
d831369f86 | ||
|
|
c1a1e55207 | ||
|
|
c3dbb20ce3 | ||
|
|
ba4712c922 | ||
|
|
a19eb59461 | ||
|
|
484ec95596 | ||
|
|
6f5b3eb4d7 | ||
|
|
a32b3e565b | ||
|
|
a8a6f82270 | ||
|
|
24d6fda360 | ||
|
|
d4106b9a88 | ||
|
|
d0713d018c | ||
|
|
a33fc2a0db | ||
|
|
ca200e788e | ||
|
|
c454d626b6 | ||
|
|
4cf7ca4162 | ||
|
|
c22e3aaa4b | ||
|
|
7d70f1becb | ||
|
|
a476c5420a | ||
|
|
cdd8d10940 | ||
|
|
883deb1c5d | ||
|
|
e389fcaecb | ||
|
|
9078d9cfab | ||
|
|
51b662fdfe | ||
|
|
b6d74ad753 | ||
|
|
5656323434 | ||
|
|
81baebf113 | ||
|
|
f7c884d4d1 | ||
|
|
7aa9af08c3 | ||
|
|
15803d1837 | ||
|
|
c0c10fd5f1 | ||
|
|
e2d4a38639 | ||
|
|
f89676d4bb | ||
|
|
b9c167fc13 | ||
|
|
3bac06d6e5 | ||
|
|
504e11b5bb | ||
|
|
b80ba97154 | ||
|
|
e3e38f3eac | ||
|
|
93c9f4316b | ||
|
|
47a7000121 | ||
|
|
6c9026e330 | ||
|
|
d59c609a73 | ||
|
|
6ee48c8f15 | ||
|
|
6af823b36a | ||
|
|
8c7ea61623 | ||
|
|
067508c082 | ||
|
|
79065ba5c6 | ||
|
|
3fbd156c83 | ||
|
|
8fb191afcf | ||
|
|
ab37856a91 | ||
|
|
69e81e8f65 | ||
|
|
46e796303e | ||
|
|
298f1ab570 | ||
|
|
79dd8e9074 | ||
|
|
8d5afff0a4 | ||
|
|
b357003a95 | ||
|
|
5cc3b526e8 | ||
|
|
1b2a8ba6b0 | ||
|
|
75676f8830 | ||
|
|
530853c988 | ||
|
|
dcf0baa9de | ||
|
|
3e294d5339 | ||
|
|
36573a5c6f | ||
|
|
9a835af7ce | ||
|
|
ca48ff793c | ||
|
|
fc8c88be41 | ||
|
|
3485109125 | ||
|
|
a1136e7695 | ||
|
|
ceac18c554 | ||
|
|
99f32dcb99 | ||
|
|
ac937030f0 | ||
|
|
67d77dd6ce | ||
|
|
755ef0119a | ||
|
|
1a1ab92ed9 | ||
|
|
b29e9b931d | ||
|
|
0479322c1d | ||
|
|
0be7b6fe1f | ||
|
|
ee98daf9ea | ||
|
|
f2beaba3e9 | ||
|
|
823c7469e1 | ||
|
|
10efe1faee | ||
|
|
e0954b0999 | ||
|
|
5ff3e448f5 | ||
|
|
58bcbba841 | ||
|
|
30e3932af8 | ||
|
|
e86937e2e2 | ||
|
|
6276d9290d | ||
|
|
cde6589af8 | ||
|
|
482d538559 | ||
|
|
6cc5ce7a66 | ||
|
|
7a80fe4939 | ||
|
|
b8444053b8 | ||
|
|
753d7addb4 | ||
|
|
c0ae96e7a8 | ||
|
|
e99d64011e | ||
|
|
72809ef5b1 | ||
|
|
7a790c88e8 | ||
|
|
01377f6ea9 | ||
|
|
96dede4e84 | ||
|
|
72783aa068 | ||
|
|
658a8c0609 | ||
|
|
e7a91a1169 | ||
|
|
14a51458ab | ||
|
|
86c2e9e825 | ||
|
|
480932a58a | ||
|
|
529bcab009 | ||
|
|
9a4871abeb | ||
|
|
40665c05ac | ||
|
|
63cf0e347b |
91
.drone.yml
Normal file
91
.drone.yml
Normal file
@@ -0,0 +1,91 @@
|
||||
#
|
||||
# We are building GCC with make and Clang with ninja, the combinations are more
|
||||
# or less arbitrarily chosen. We just want to check that both compilers and both
|
||||
# CMake generators work. It's unlikely that a specific generator only breaks
|
||||
# with a specific compiler.
|
||||
#
|
||||
|
||||
workspace:
|
||||
base: /drone
|
||||
path: src/github.com/owncloud/client
|
||||
|
||||
branches:
|
||||
- master
|
||||
- 2.4
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: plugins/git
|
||||
pull: true
|
||||
tags: false
|
||||
|
||||
pipeline:
|
||||
prepare-clang:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- mkdir clang-build
|
||||
- cd clang-build
|
||||
- cmake -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE="Debug" -DUNIT_TESTING=1 ..
|
||||
|
||||
building-clang:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- cd clang-build
|
||||
- ninja -j4
|
||||
|
||||
testing-clang:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- cd clang-build
|
||||
- useradd -m -s /bin/bash tester
|
||||
- chown -R tester:tester .
|
||||
- su-exec tester ctest --output-on-failure
|
||||
|
||||
prepare-gcc:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- mkdir gcc-build
|
||||
- cd gcc-build
|
||||
- cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE="Debug" -DUNIT_TESTING=1 ..
|
||||
|
||||
building-gcc:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- cd gcc-build
|
||||
- make -j4
|
||||
|
||||
testing-gcc:
|
||||
image: owncloudci/client:latest
|
||||
pull: true
|
||||
environment:
|
||||
- LC_ALL=C.UTF-8
|
||||
commands:
|
||||
- cd gcc-build
|
||||
- useradd -m -s /bin/bash tester
|
||||
- chown -R tester:tester .
|
||||
- su-exec tester ctest --output-on-failure
|
||||
|
||||
notify-slack:
|
||||
image: plugins/slack
|
||||
pull: true
|
||||
secrets: [ slack_webhook ]
|
||||
channel: desktop
|
||||
when:
|
||||
local: false
|
||||
status: [ changed, failure ]
|
||||
event: [ push ]
|
||||
37
.travis.yml
37
.travis.yml
@@ -1,37 +0,0 @@
|
||||
sudo: required
|
||||
|
||||
language: cpp
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
branches:
|
||||
only:
|
||||
- coverity_scan
|
||||
|
||||
before_install:
|
||||
- sudo sh -c "echo 'deb http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_14.04/ /' >> /etc/apt/sources.list.d/owncloud-client.list"
|
||||
- sudo sh -c "echo 'deb-src http://download.opensuse.org/repositories/isv:/ownCloud:/desktop/Ubuntu_14.04/ /' >> /etc/apt/sources.list.d/owncloud-client.list"
|
||||
- wget http://download.opensuse.org/repositories/isv:ownCloud:desktop/Ubuntu_14.04/Release.key
|
||||
- sudo apt-key add - < Release.key
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y build-dep owncloud-client
|
||||
- checkout=$(git show-ref --head --hash head)
|
||||
- cd ../
|
||||
- wget https://scan.coverity.com/download/linux-64 --post-data "token=$token&project=owncloud%2Fmirall" -O coverity_tool.tgz
|
||||
- mkdir coverity
|
||||
- tar -xvf coverity_tool.tgz -C coverity --strip-components=1
|
||||
- export PATH=$PATH:$PWD/coverity/bin/
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
|
||||
install:
|
||||
- cd ../
|
||||
- mkdir client-build
|
||||
- cd client-build
|
||||
- cmake -DCMAKE_BUILD_TYPE="Debug" $TRAVIS_BUILD_DIR
|
||||
- cov-build --dir cov-int make
|
||||
- tar czvf client.tgz cov-int
|
||||
- curl --form token=$token --form email=lukas@statuscode.ch --form file=@$PWD/client.tgz --form version="$checkout" --form description="$checkout" https://scan.coverity.com/builds?project=owncloud%2Fmirall
|
||||
|
||||
# Hack to stop processing
|
||||
script: true
|
||||
@@ -243,6 +243,7 @@ set(WITH_TESTING ${UNIT_TESTING})
|
||||
if(BUILD_CLIENT)
|
||||
add_subdirectory(src)
|
||||
if(NOT BUILD_LIBRARIES_ONLY)
|
||||
add_subdirectory(man)
|
||||
add_subdirectory(doc)
|
||||
add_subdirectory(doc/dev)
|
||||
if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin)
|
||||
|
||||
47
ChangeLog
47
ChangeLog
@@ -1,9 +1,46 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
version 2.4.0 (2017-11-XX)
|
||||
* If you're using 2.4.0 alpha1, please upgrade as the alpha1 had an issue with hidden files!
|
||||
version 2.4.2 (2018-06-xx)
|
||||
* Linux: Tray workarounds (#6545)
|
||||
* Fix nautilus/nemo shell issues (#6393, #6406)
|
||||
* Updater: Add update channel feature (#6259)
|
||||
* Updater: Support EXE->MSI upgrade
|
||||
* SyncJournal: Fixes for sync folders on removable media (#6049, #6049)
|
||||
* SslButton: Add HTTP/2 info (#3146)
|
||||
* Fix assert when using ownCloud server 5 (which you should not) (#6403)
|
||||
* Normalize local path (#4424)
|
||||
* Blacklisting must prevent parent etag updates (#6411)
|
||||
* macdeployqt: Adjust minimum version based on our Qt (#5932)
|
||||
* macOS: Unload the Finder extension on exit (#5382, #3819)
|
||||
* Upload: Adjust timeout for final job based on file size (#6527)
|
||||
* Sync: When detecting a local move, keep the local mtime (#6629)
|
||||
* Credentials: Retry fetching from the keychain in case the keychain is still starting (#4274, #6522)
|
||||
* OAuth2: Try to refresh the token even if the credentials weren't ready (#6522)
|
||||
|
||||
version 2.4.1 (2018-02-xx)
|
||||
* Ignore files with file names that can't be encoded for the filesystem (#6287, #5676, #5719)
|
||||
* Issues: Speed up insertion and add hard upper limit (#6272)
|
||||
* Notifications: Fix "Dismiss" action
|
||||
* Notifications: Fix timer invocation on macOS
|
||||
* Notifications: Immediately poll when account online
|
||||
* Protocol: Remove entries for auto resolved conflicts (#6316)
|
||||
* owncloudcmd: Set proxy before capabilities call (#6281)
|
||||
* owncloudcmd: Do not do the capability call when --nonshib is passed
|
||||
* Avatars: Use old location for servers <10 (#6279)
|
||||
* Link shares: Change default share name (#6298)
|
||||
* Sharing: Use maximum allowed permissions for new share (#6346)
|
||||
* Nautilus integration: Work with python2 and python3
|
||||
* Windows: Don't delete contents behind directory junctions (#6322)
|
||||
* SyncJournal: Don't use LIKE with paths (#6322)
|
||||
* Fix setting launch-on-startup when the first account is set up (#6347)
|
||||
* HTTP2: Only allow with Qt 5.9.4 (#6285)
|
||||
* Crash fixes
|
||||
|
||||
version 2.4.0 (2017-12-21)
|
||||
* If you're using 2.4.0 alpha1, please upgrade as previous alphas/rcs had an issue with hidden files and renames!
|
||||
* OAuth2 authentication support by opening external browser (#5668)
|
||||
* Shibboleth: Change to use OAuth2 if supported (#6198)
|
||||
* Sharing: Add support for multiple public link shares (#5655)
|
||||
* Sharing: Add option to copy/email private links (#5023, #5627)
|
||||
* Sharing: Add option "show file listing" (#5837)
|
||||
@@ -61,10 +98,11 @@ version 2.4.0 (2017-11-XX)
|
||||
* Sync: Introduce overall errors that are not tied to a file (#5746)
|
||||
* Sync: Better messaging for 507 Insufficient Storage (#5537)
|
||||
* Sync: Create conflicts by comparing the hash of files with identical mtime/size (#5589)
|
||||
* Sync: Avoid downloads by comparing the hash of files with identical mtime/size (#6153)
|
||||
* Sync: Upload conflict files if OWNCLOUD_UPLOAD_CONFLICT_FILES environment variable is set (#6038)
|
||||
* Sync: Blacklist: Don't let errors become warnings (#5516)
|
||||
* Sync: Check etag again after active sync (#4116)
|
||||
* Sync: Rename handling fixes: duplicate file ids (#6096)
|
||||
* Sync: Rename handling fixes: duplicate file ids (#6096, #6212)
|
||||
* Sync: Rename handling fixes: File size must be equal
|
||||
* Sync: Rename handling: Fix duplicate files on abort/resume sync (#5949)
|
||||
* Sync: Add capability for invalid filename regexes (#6092)
|
||||
@@ -78,6 +116,7 @@ version 2.4.0 (2017-11-XX)
|
||||
* Crash fixes
|
||||
* Test improvements
|
||||
* Small UI layout fixes
|
||||
* Performance improvements
|
||||
* Maintenance Mode: Detect maintenance mode (#4485)
|
||||
* Maintenance Mode: Add a 1 to 5 min reconnection delay (#5872)
|
||||
* HTTP: Send a unique X-Request-ID with each request (#5853)
|
||||
@@ -93,6 +132,7 @@ version 2.4.0 (2017-11-XX)
|
||||
* Compilation: Remove Qt 4 code (#6025, #5702, #5505)
|
||||
* Harmonize source code style with clang-format (#5732)
|
||||
* Switch over to Qt 5 function pointer signal/slot syntax (#6041)
|
||||
* Compile with stack-smashing protection
|
||||
* Updater: Rudimentary support for beta channel (#6048)
|
||||
|
||||
version 2.3.4 (2017-11-02)
|
||||
@@ -108,6 +148,7 @@ version 2.3.3 (2017-08-29)
|
||||
* Overlay Icons: Fix potential hangs on Windows
|
||||
* SyncJournalDB: Don't use ._ as filename pattern if that does not work because of SMB storage settings (#5844)
|
||||
* SyncJournalDB: Log reason for sqlite3 opening errors
|
||||
* Notifications: Proapgate "Dismiss" button action to server (#5922)
|
||||
* Switch Linux build also to Qt 5.6.2 (#5470)
|
||||
* Stopped maintaining Qt 4 buildability
|
||||
|
||||
|
||||
51
Jenkinsfile
vendored
51
Jenkinsfile
vendored
@@ -1,51 +0,0 @@
|
||||
#!groovy
|
||||
|
||||
//
|
||||
// We now run the tests in Debug mode so that ASSERTs are triggered.
|
||||
// Ideally we should run the tests in both Debug and Release so we catch
|
||||
// all possible error combinations.
|
||||
// See also the top comment in syncenginetestutils.h
|
||||
//
|
||||
|
||||
node('CLIENT') {
|
||||
stage 'Checkout'
|
||||
checkout scm
|
||||
sh '''git submodule update --init'''
|
||||
|
||||
stage 'Qt5'
|
||||
sh '''rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE="Debug" -DUNIT_TESTING=1 -DWITH_TESTING=1 -DBUILD_WITH_QT4=OFF ..
|
||||
make -j4
|
||||
ctest -V --output-on-failure'''
|
||||
|
||||
stage 'Qt5 - clang'
|
||||
sh '''rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE="Debug" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUNIT_TESTING=1 -DWITH_TESTING=1 -DBUILD_WITH_QT4=OFF ..
|
||||
make -j4
|
||||
ctest -V --output-on-failure'''
|
||||
|
||||
|
||||
stage 'Win32'
|
||||
def win32 = docker.image('guruz/docker-owncloud-client-win32:latest')
|
||||
win32.pull() // make sure we have the latest available from Docker Hub
|
||||
win32.inside {
|
||||
sh '''
|
||||
rm -rf build-win32
|
||||
mkdir build-win32
|
||||
cd build-win32
|
||||
../admin/win/download_runtimes.sh
|
||||
cmake .. -DCMAKE_TOOLCHAIN_FILE=../admin/win/Toolchain-mingw32-openSUSE.cmake -DWITH_CRASHREPORTER=ON
|
||||
make -j4
|
||||
make package
|
||||
ctest .
|
||||
'''
|
||||
}
|
||||
|
||||
// Stage 'macOS' TODO
|
||||
}
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-back
|
||||
|
||||
option( WITH_CRASHREPORTER "Build crashreporter" OFF )
|
||||
set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE string "URL for crash reporter" )
|
||||
set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ownCloud Desktop Client
|
||||
|
||||
[](https://jenkins.owncloud.org/job/owncloud-client/job/client/job/master/)
|
||||
[](https://drone.owncloud.com/owncloud/client)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
set( MIRALL_VERSION_MAJOR 2 )
|
||||
set( MIRALL_VERSION_MINOR 4 )
|
||||
set( MIRALL_VERSION_PATCH 0 )
|
||||
set( MIRALL_VERSION_YEAR 2017 )
|
||||
set( MIRALL_VERSION_PATCH 2 )
|
||||
set( MIRALL_VERSION_YEAR 2018 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
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 )
|
||||
|
||||
if( NOT DEFINED MIRALL_VERSION_BUILD )
|
||||
|
||||
@@ -11,6 +11,7 @@ else()
|
||||
set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "0")
|
||||
endif()
|
||||
|
||||
find_package(Qt5 5.6 COMPONENTS Core REQUIRED)
|
||||
configure_file(create_mac_pkg.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
|
||||
configure_file(macosx.pkgproj ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
||||
configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh)
|
||||
|
||||
@@ -23,7 +23,7 @@ identity="$3"
|
||||
prjfile=$build_path/admin/osx/macosx.pkgproj
|
||||
|
||||
# The name of the installer package
|
||||
installer="@APPLICATION_SHORTNAME@-@MIRALL_VERSION_FULL@@MIRALL_VERSION_SUFFIX@"
|
||||
installer="@APPLICATION_SHORTNAME@-qt@Qt5Core_VERSION@-@MIRALL_VERSION_FULL@@MIRALL_VERSION_SUFFIX@"
|
||||
installer_file="$installer.pkg"
|
||||
installer_file_tar="$installer.pkg.tar"
|
||||
installer_file_tar_bz2="$installer.pkg.tar.bz2"
|
||||
|
||||
@@ -22,6 +22,7 @@ import subprocess
|
||||
import commands
|
||||
import sys
|
||||
from glob import glob
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
def QueryQMake(attrib):
|
||||
return subprocess.check_output([qmake_path, '-query', attrib]).rstrip('\n')
|
||||
@@ -92,6 +93,8 @@ commands.append(['mkdir', '-p', resources_dir])
|
||||
plugins_dir = os.path.join(bundle_dir, 'Contents', 'PlugIns')
|
||||
binaries = [i for i in glob(os.path.join(bundle_dir, 'Contents', 'MacOS', "*")) if is_exe(i)];
|
||||
|
||||
qt_version = QueryQMake('QT_VERSION')
|
||||
print "Using Qt", qt_version
|
||||
|
||||
fixed_libraries = []
|
||||
fixed_frameworks = []
|
||||
@@ -334,9 +337,19 @@ def FindQtPlugin(name):
|
||||
for binary in binaries:
|
||||
FixBinary(binary)
|
||||
|
||||
|
||||
if LooseVersion(qt_version) >= LooseVersion("5.10.0"):
|
||||
QT_PLUGINS.append('styles/libqmacstyle.dylib')
|
||||
for plugin in QT_PLUGINS:
|
||||
FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
||||
|
||||
if LooseVersion(qt_version) >= LooseVersion("5.10.0"):
|
||||
args = ['plutil', '-insert', 'LSMinimumSystemVersion', '-string', '10.10.0', os.path.join(bundle_dir, 'Contents', 'Info.plist')]
|
||||
commands.append(args)
|
||||
else:
|
||||
args = ['plutil', '-insert', 'LSMinimumSystemVersion', '-string', '10.7.0', os.path.join(bundle_dir, 'Contents', 'Info.plist')]
|
||||
commands.append(args)
|
||||
|
||||
if len(sys.argv) <= 2:
|
||||
print 'Will run %d commands:' % len(commands)
|
||||
for command in commands:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 25 KiB |
@@ -27,11 +27,9 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@MIRALL_VERSION_STRING@</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>(C) 2014-2016 @APPLICATION_VENDOR@</string>
|
||||
<string>(C) 2014-2018 @APPLICATION_VENDOR@</string>
|
||||
<key>SUShowReleaseNotes</key>
|
||||
<false/>
|
||||
<key>LSMinimumBundleVersion</key>
|
||||
<string>10.7.0</string>
|
||||
<key>SUPublicDSAKeyFile</key>
|
||||
<string>dsa_pub.pem</string>
|
||||
</dict>
|
||||
|
||||
@@ -4,7 +4,6 @@ if(SPHINX_FOUND)
|
||||
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
|
||||
# HTML output directory
|
||||
set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html")
|
||||
set(SPHINX_MAN_DIR "${CMAKE_CURRENT_BINARY_DIR}/man1")
|
||||
set(SPHINX_PDF_DIR "${CMAKE_CURRENT_BINARY_DIR}/latex")
|
||||
set(SPHINX_QCH_DIR "${CMAKE_CURRENT_BINARY_DIR}/qthelp")
|
||||
set(SPHINX_HTMLHELP_DIR "${CMAKE_CURRENT_BINARY_DIR}/htmlhelp")
|
||||
@@ -17,8 +16,6 @@ if(SPHINX_FOUND)
|
||||
install(DIRECTORY ${SPHINX_PDF_DIR} DESTINATION ${APPLICATION_DOC_DIR} OPTIONAL)
|
||||
install(DIRECTORY ${SPHINX_QCH_DIR} DESTINATION ${APPLICATION_DOC_DIR} OPTIONAL)
|
||||
|
||||
install(DIRECTORY ${SPHINX_MAN_DIR} DESTINATION ${CMAKE_INSTALL_MANDIR} OPTIONAL)
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" conf.py @ONLY)
|
||||
|
||||
if(WITH_DOC)
|
||||
@@ -73,11 +70,6 @@ if(SPHINX_FOUND)
|
||||
-D html_theme=owncloud_com
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${SPHINX_HTML_DIR}/com )
|
||||
add_custom_target( doc-man ${SPHINX_EXECUTABLE}
|
||||
-q -c . -b man
|
||||
-d ${SPHINX_CACHE_DIR}/man
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${SPHINX_MAN_DIR} )
|
||||
|
||||
## Building CHM files requires HTML Help Workshop. Since it requires wine
|
||||
## with special dependencies, it's impossible to write a cmake check for it.
|
||||
@@ -92,4 +84,4 @@ if(SPHINX_FOUND)
|
||||
${SPHINX_HTMLHELP_DIR} )
|
||||
add_custom_target( doc-chm pushd ${SPHINX_HTMLHELP_DIR}; ${MSHTML_COMPILER} *.hhp; popd
|
||||
DEPENDS doc-chm-sphinx )
|
||||
endif(SPHINX_FOUND)
|
||||
endif(SPHINX_FOUND)
|
||||
14
man/CMakeLists.txt
Normal file
14
man/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
if(SPHINX_FOUND)
|
||||
|
||||
# Sphinx cache with pickled ReST documents
|
||||
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
|
||||
|
||||
# HTML output directory
|
||||
set(SPHINX_MAN_DIR "${CMAKE_CURRENT_BINARY_DIR}/man1")
|
||||
install(DIRECTORY ${SPHINX_MAN_DIR} DESTINATION ${CMAKE_INSTALL_MANDIR} OPTIONAL)
|
||||
add_custom_target( doc-man ${SPHINX_EXECUTABLE}
|
||||
-c ${CMAKE_SOURCE_DIR}/doc -b man
|
||||
-d ${SPHINX_CACHE_DIR}/man
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${SPHINX_MAN_DIR} )
|
||||
endif(SPHINX_FOUND)
|
||||
1
man/index.rst
Normal file
1
man/index.rst
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
33
man/owncloud.1.rst
Normal file
33
man/owncloud.1.rst
Normal file
@@ -0,0 +1,33 @@
|
||||
:orphan:
|
||||
|
||||
owncloud(1)
|
||||
————
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
*owncloud* [`OPTIONS`...]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
The ownCloud Client is a file synchronization desktop utility. It synchronizes files on your local computer, tablet, or handheld device with an ownCloud Server. If you make a change to the files on one device, the change is propagated to all other synchronized devices using the desktop synchronization clients.
|
||||
|
||||
Normally, you start the client by clicking on the desktop icon or by starting it from the client application menu. After starting, an ownCloud icon appears in the computer system tray or on your tablet or handheld device.
|
||||
|
||||
Options
|
||||
=======
|
||||
.. include:: ../doc/options.rst
|
||||
|
||||
Config File
|
||||
===========
|
||||
.. include:: ../doc/conffile.rst
|
||||
|
||||
BUGS
|
||||
====
|
||||
|
||||
Please report bugs at https://github.com/owncloud/client/issues.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
:manpage:`owncloudcmd(1)`
|
||||
97
man/owncloudcmd.1.rst
Normal file
97
man/owncloudcmd.1.rst
Normal file
@@ -0,0 +1,97 @@
|
||||
:orphan:
|
||||
|
||||
owncloudcmd(1)
|
||||
—————
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
*owncloudcmd* [`OPTIONS`...] sourcedir owncloudurl
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
owncloudcmd is the command line tool used for the ownCloud file synchronization
|
||||
desktop utility.
|
||||
|
||||
Contrary to the :manpage:`owncloud(1)` GUI client, `owncloudcmd` only performs
|
||||
a single sync run and then exits. In so doing, `owncloudcmd` replaces the
|
||||
`ocsync` binary used for the same purpose in earlier releases.
|
||||
|
||||
A *sync run* synchronizes a single local directory using a WebDAV share on a
|
||||
remote ownCloud server.
|
||||
|
||||
To invoke the command line client, provide the local and the remote repository:
|
||||
The first parameter is the local directory. The second parameter is
|
||||
the server URL.
|
||||
|
||||
.. note:: Prior to the 1.6 release of owncloudcmd, the tool only accepted
|
||||
``owncloud://`` or ``ownclouds://`` in place of ``http://`` and ``https://`` as
|
||||
a scheme. See ``Examples`` for details.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
``—user``, ``-u`` ``[user]``
|
||||
Use ``user`` as the login name.
|
||||
|
||||
``—password``, ``-p`` ``[password]``
|
||||
Use ``password`` as the password.
|
||||
|
||||
``-n``
|
||||
Use ``netrc (5)`` for login.
|
||||
|
||||
``—non-interactive``
|
||||
Do not prompt for questions.
|
||||
|
||||
``—silent``, ``—s``
|
||||
Inhibits verbose log output.
|
||||
|
||||
``—trust``
|
||||
Trust any SSL certificate, including invalid ones.
|
||||
|
||||
``—httpproxy http://[user@pass:]<server>:<port>``
|
||||
Uses ``server`` as HTTP proxy.
|
||||
|
||||
``—nonshib``
|
||||
Uses Non Shibboleth WebDAV Authentication
|
||||
|
||||
``—davpath [path]``
|
||||
Overrides the WebDAV Path with ``path``
|
||||
|
||||
``—exclude [file]``
|
||||
Exclude list file
|
||||
|
||||
``—unsyncedfolders [file]``
|
||||
File containing the list of unsynced folders (selective sync)
|
||||
|
||||
``—max-sync-retries [n]``
|
||||
Retries maximum n times (defaults to 3)
|
||||
|
||||
``-h``
|
||||
Sync hidden files,do not ignore them
|
||||
|
||||
Example
|
||||
=======
|
||||
To synchronize the ownCloud directory ``Music`` to the local directory ``media/music``
|
||||
through a proxy listening on port ``8080`` on the gateway machine ``192.168.178.1``,
|
||||
the command line would be::
|
||||
|
||||
$ owncloudcmd —httpproxy http://192.168.178.1:8080 \
|
||||
$HOME/media/music \
|
||||
https://server/owncloud/remote.php/webdav/Music
|
||||
|
||||
``owncloudcmd`` will enquire user name and password, unless they have
|
||||
been specified on the command line or ``-n`` (see `netrc(5)`) has been passed.
|
||||
|
||||
Using the legacy scheme, it would be::
|
||||
|
||||
$ owncloudcmd —httpproxy http://192.168.178.1:8080 \
|
||||
$HOME/media/music \
|
||||
ownclouds://server/owncloud/remote.php/webdav/Music
|
||||
|
||||
|
||||
BUGS
|
||||
====
|
||||
Please report bugs at https://github.com/owncloud/client/issues.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
:manpage:`owncloud(1)`
|
||||
@@ -8,6 +8,56 @@ GenericName=Folder Sync
|
||||
Icon=@APPLICATION_EXECUTABLE@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
# Translations
|
||||
|
||||
@@ -395,6 +445,11 @@ Icon[it]=@APPLICATION_EXECUTABLE@
|
||||
Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
||||
GenericName[ko]=폴더 동기화
|
||||
Name[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
||||
Icon[ko]=@APPLICATION_EXECUTABLE@
|
||||
Comment[lo]=@APPLICATION_NAME@ ການປະສານຂໍ້ມູນຄອມພິວເຕີລູກຂ່າຍ
|
||||
GenericName[lo]=ໂຟນເດີຊິງ
|
||||
Name[lo]=@APPLICATION_NAME@ ຊິງຄອມພິວເຕີລູກຂ່າຍ
|
||||
Icon[lo]=@APPLICATION_EXECUTABLE@
|
||||
Comment[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens
|
||||
GenericName[hu_HU]=Könyvtár szinkronizálás
|
||||
Name[hu_HU]=@APPLICATION_NAME@ asztali szinkr. kliens
|
||||
|
||||
@@ -15,8 +15,13 @@
|
||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
# for more details.
|
||||
|
||||
import sys
|
||||
python3 = sys.version_info[0] >= 3
|
||||
|
||||
import os
|
||||
import urllib
|
||||
if python3:
|
||||
import urllib.parse
|
||||
import socket
|
||||
import tempfile
|
||||
|
||||
@@ -29,12 +34,15 @@ from gi.repository import GObject, Nautilus
|
||||
appname = 'ownCloud'
|
||||
|
||||
print("Initializing "+appname+"-client-nautilus extension")
|
||||
|
||||
print("Using python version {}".format(sys.version_info))
|
||||
|
||||
def get_local_path(url):
|
||||
if url[0:7] == 'file://':
|
||||
url = url[7:]
|
||||
return urllib.unquote(url)
|
||||
if python3:
|
||||
return urllib.parse.unquote(url)
|
||||
else:
|
||||
return urllib.unquote(url).decode('utf-8')
|
||||
|
||||
def get_runtime_dir():
|
||||
"""Returns the value of $XDG_RUNTIME_DIR, a directory path.
|
||||
@@ -56,7 +64,7 @@ class SocketConnect(GObject.GObject):
|
||||
self._watch_id = 0
|
||||
self._sock = None
|
||||
self._listeners = [self._update_registered_paths]
|
||||
self._remainder = ''
|
||||
self._remainder = ''.encode('utf-8')
|
||||
self.nautilusVFSFile_table = {} # not needed in this object actually but shared
|
||||
# all over the other objects.
|
||||
|
||||
@@ -74,7 +82,7 @@ class SocketConnect(GObject.GObject):
|
||||
# print("Server command: " + cmd)
|
||||
if self.connected:
|
||||
try:
|
||||
self._sock.send(cmd)
|
||||
self._sock.send(cmd.encode('utf-8'))
|
||||
except:
|
||||
print("Sending failed.")
|
||||
self.reconnect()
|
||||
@@ -89,18 +97,15 @@ class SocketConnect(GObject.GObject):
|
||||
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
|
||||
try:
|
||||
print("Socket File: " + sock_file)
|
||||
self._sock.connect(sock_file) # fails if sock_file doesn't exist
|
||||
self.connected = True
|
||||
print("Setting connected to %r." % self.connected )
|
||||
self._watch_id = GObject.io_add_watch(self._sock, GObject.IO_IN, self._handle_notify)
|
||||
print("Socket watch id: " + str(self._watch_id))
|
||||
|
||||
self.sendCommand('GET_STRINGS:\n')
|
||||
|
||||
return False # Don't run again
|
||||
except Exception as e:
|
||||
print("Could not connect to unix socket. " + str(e))
|
||||
print("Could not connect to unix socket " + sock_file + ". " + str(e))
|
||||
except Exception as e: # Bad habbit
|
||||
print("Connect could not be established, try again later.")
|
||||
self._sock.close()
|
||||
@@ -113,24 +118,24 @@ class SocketConnect(GObject.GObject):
|
||||
# Prepend the remaining data from last call
|
||||
if len(self._remainder) > 0:
|
||||
data = self._remainder + data
|
||||
self._remainder = ''
|
||||
self._remainder = ''.encode('utf-8')
|
||||
|
||||
if len(data) > 0:
|
||||
# Remember the remainder for next round
|
||||
lastNL = data.rfind('\n');
|
||||
lastNL = data.rfind('\n'.encode('utf-8'));
|
||||
if lastNL > -1 and lastNL < len(data):
|
||||
self._remainder = data[lastNL+1:]
|
||||
data = data[:lastNL]
|
||||
|
||||
for l in data.split('\n'):
|
||||
self._handle_server_response(l)
|
||||
for l in data.split('\n'.encode('utf-8')):
|
||||
self._handle_server_response(l.decode('utf-8'))
|
||||
else:
|
||||
return False
|
||||
|
||||
return True # Run again
|
||||
|
||||
def _handle_server_response(self, line):
|
||||
print("Server response: " + line)
|
||||
# print("Server response: " + line)
|
||||
parts = line.split(':')
|
||||
action = parts[0]
|
||||
args = parts[1:]
|
||||
@@ -257,11 +262,11 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
|
||||
|
||||
def context_menu_action(self, menu, action, file):
|
||||
filename = get_local_path(file.get_uri())
|
||||
print("Context menu: " + action + ' ' + filename)
|
||||
# print("Context menu: " + action + ' ' + filename)
|
||||
socketConnect.sendCommand(action + ":" + filename + "\n")
|
||||
|
||||
|
||||
class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
|
||||
class SyncStateExtension(GObject.GObject, Nautilus.InfoProvider):
|
||||
def __init__(self):
|
||||
GObject.GObject.__init__(self)
|
||||
|
||||
|
||||
@@ -8,20 +8,29 @@ if(NOT TOKEN_AUTH_ONLY)
|
||||
find_package(Qt5Keychain REQUIRED)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Enable DEP & ASLR
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
|
||||
if(NOT WIN32)
|
||||
if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(alpha|parisc|hppa)"))
|
||||
if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9))
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
|
||||
else()
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Enable DEP & ASLR
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
||||
endif()
|
||||
|
||||
@@ -27,7 +27,7 @@ endif()
|
||||
|
||||
if(NOT BUILD_LIBRARIES_ONLY)
|
||||
add_executable(${cmd_NAME} ${cmd_SRC})
|
||||
qt5_use_modules(${cmd_NAME} Network Sql)
|
||||
qt5_use_modules(${cmd_NAME} Network )
|
||||
set_target_properties(${cmd_NAME} PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
|
||||
set_target_properties(${cmd_NAME} PROPERTIES
|
||||
|
||||
@@ -420,46 +420,6 @@ int main(int argc, char **argv)
|
||||
folder.chop(1);
|
||||
}
|
||||
|
||||
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
|
||||
|
||||
HttpCredentialsText *cred = new HttpCredentialsText(user, password);
|
||||
|
||||
if (options.trustSSL) {
|
||||
cred->setSSLTrusted(true);
|
||||
}
|
||||
account->setUrl(url);
|
||||
account->setCredentials(cred);
|
||||
account->setSslErrorHandler(sslErrorHandler);
|
||||
|
||||
//obtain capabilities using event loop
|
||||
QEventLoop loop;
|
||||
|
||||
JsonApiJob *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
||||
qDebug() << "Server capabilities" << caps;
|
||||
account->setCapabilities(caps.toVariantMap());
|
||||
loop.quit();
|
||||
});
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
if (job->reply()->error() != QNetworkReply::NoError){
|
||||
std::cout<<"Error connecting to server\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// much lower age than the default since this utility is usually made to be run right after a change in the tests
|
||||
SyncEngine::minimumFileAgeForUpload = 0;
|
||||
|
||||
int restartCount = 0;
|
||||
restart_sync:
|
||||
|
||||
|
||||
opts = &options;
|
||||
|
||||
if (!options.proxy.isNull()) {
|
||||
QString host;
|
||||
int port = 0;
|
||||
@@ -477,16 +437,58 @@ restart_sync:
|
||||
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
||||
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, host, port));
|
||||
} else {
|
||||
qFatal("Could not read httpproxy. The proxy should have the format \"http://hostname:port\".");
|
||||
}
|
||||
} else {
|
||||
clientProxy.setupQtProxyFromConfig();
|
||||
QString url(options.target_url);
|
||||
if (url.startsWith("owncloud")) {
|
||||
url.remove(0, 8);
|
||||
url = QString("http%1").arg(url);
|
||||
}
|
||||
|
||||
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
|
||||
|
||||
HttpCredentialsText *cred = new HttpCredentialsText(user, password);
|
||||
|
||||
if (options.trustSSL) {
|
||||
cred->setSSLTrusted(true);
|
||||
}
|
||||
account->setUrl(url);
|
||||
account->setCredentials(cred);
|
||||
account->setSslErrorHandler(sslErrorHandler);
|
||||
|
||||
// Perform a call to get the capabilities.
|
||||
if (!options.nonShib) {
|
||||
// Do not do it if '--nonshib' was passed. This mean we should only connect to the 'nonshib'
|
||||
// dav endpoint. Since we do not get the capabilities, in that case, this has the additional
|
||||
// side effect that chunking-ng will be disabled. (because otherwise it would use the new
|
||||
// 'dav' endpoint instead of the nonshib one (which still use the old chunking)
|
||||
|
||||
QEventLoop loop;
|
||||
JsonApiJob *job = new JsonApiJob(account, QLatin1String("ocs/v1.php/cloud/capabilities"));
|
||||
job->setTimeout(timeoutToUseMsec);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, [&](const QJsonDocument &json) {
|
||||
auto caps = json.object().value("ocs").toObject().value("data").toObject().value("capabilities").toObject();
|
||||
qDebug() << "Server capabilities" << caps;
|
||||
account->setCapabilities(caps.toVariantMap());
|
||||
loop.quit();
|
||||
});
|
||||
job->start();
|
||||
|
||||
loop.exec();
|
||||
|
||||
if (job->reply()->error() != QNetworkReply::NoError){
|
||||
std::cout<<"Error connecting to server\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// much lower age than the default since this utility is usually made to be run right after a change in the tests
|
||||
SyncEngine::minimumFileAgeForUpload = 0;
|
||||
|
||||
int restartCount = 0;
|
||||
restart_sync:
|
||||
|
||||
opts = &options;
|
||||
|
||||
QStringList selectiveSyncList;
|
||||
if (!options.unsyncedfolders.isEmpty()) {
|
||||
QFile f(options.unsyncedfolders);
|
||||
|
||||
@@ -90,6 +90,21 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
|
||||
return header;
|
||||
}
|
||||
|
||||
QByteArray findBestChecksum(const QByteArray &checksums)
|
||||
{
|
||||
int i = 0;
|
||||
// The order of the searches here defines the preference ordering.
|
||||
if (-1 != (i = checksums.indexOf("SHA1:"))
|
||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
||||
// Now i is the start of the best checksum
|
||||
// Grab it until the next space or end of string.
|
||||
auto checksum = checksums.mid(i);
|
||||
return checksum.mid(0, checksum.indexOf(" "));
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum)
|
||||
{
|
||||
if (header.isEmpty()) {
|
||||
|
||||
@@ -36,6 +36,16 @@ static const char checkSumAdlerC[] = "Adler32";
|
||||
|
||||
class SyncJournalDb;
|
||||
|
||||
/**
|
||||
* Returns the highest-quality checksum in a 'checksums'
|
||||
* property retrieved from the server.
|
||||
*
|
||||
* Example: "ADLER32:1231 SHA1:ab124124 MD5:2131affa21"
|
||||
* -> "SHA1:ab124124"
|
||||
*/
|
||||
OCSYNC_EXPORT QByteArray findBestChecksum(const QByteArray &checksums);
|
||||
|
||||
|
||||
/// Creates a checksum header from type and value.
|
||||
OCSYNC_EXPORT QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
|
||||
|
||||
@@ -478,4 +478,22 @@ bool FileSystem::isLnkFile(const QString &filename)
|
||||
return filename.endsWith(".lnk");
|
||||
}
|
||||
|
||||
bool FileSystem::isJunction(const QString &filename)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
WIN32_FIND_DATA findData;
|
||||
HANDLE hFind = FindFirstFileEx((const wchar_t *)filename.utf16(), FindExInfoBasic, &findData, FindExSearchNameMatch, NULL, 0);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
FindClose(hFind);
|
||||
return false;
|
||||
}
|
||||
return findData.dwFileAttributes != INVALID_FILE_ATTRIBUTES
|
||||
&& findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT
|
||||
&& findData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT;
|
||||
#else
|
||||
Q_UNUSED(filename);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -141,8 +141,16 @@ namespace FileSystem {
|
||||
*/
|
||||
bool OCSYNC_EXPORT isFileLocked(const QString &fileName);
|
||||
|
||||
/**
|
||||
* Returns whether the file is a shortcut file (ends with .lnk)
|
||||
*/
|
||||
bool OCSYNC_EXPORT isLnkFile(const QString &filename);
|
||||
|
||||
/**
|
||||
* Returns whether the file is a junction (windows only)
|
||||
*/
|
||||
bool OCSYNC_EXPORT isJunction(const QString &filename);
|
||||
|
||||
/*
|
||||
* This function takes a path and converts it to a UNC representation of the
|
||||
* string. That means that it prepends a \\?\ (unless already UNC) and converts
|
||||
|
||||
@@ -344,6 +344,7 @@ void SqlQuery::bindValue(int pos, const QVariant &value)
|
||||
break;
|
||||
case QVariant::UInt:
|
||||
case QVariant::LongLong:
|
||||
case QVariant::ULongLong:
|
||||
res = sqlite3_bind_int64(_stmt, pos, value.toLongLong());
|
||||
break;
|
||||
case QVariant::DateTime: {
|
||||
|
||||
@@ -32,6 +32,13 @@
|
||||
|
||||
#include "common/c_jhash.h"
|
||||
|
||||
// SQL expression to check whether path.startswith(prefix + '/')
|
||||
// Note: '/' + 1 == '0'
|
||||
#define IS_PREFIX_PATH_OF(prefix, path) \
|
||||
"(" path " > (" prefix "||'/') AND " path " < (" prefix "||'0'))"
|
||||
#define IS_PREFIX_PATH_OR_EQUAL(prefix, path) \
|
||||
"(" path " == " prefix " OR " IS_PREFIX_PATH_OF(prefix, path) ")"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg)
|
||||
@@ -45,7 +52,7 @@ Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg)
|
||||
static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &query)
|
||||
{
|
||||
rec._path = query.baValue(0);
|
||||
rec._inode = query.intValue(1);
|
||||
rec._inode = query.int64Value(1);
|
||||
rec._modtime = query.int64Value(2);
|
||||
rec._type = query.intValue(3);
|
||||
rec._etag = query.baValue(4);
|
||||
@@ -58,7 +65,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
|
||||
|
||||
static QString defaultJournalMode(const QString &dbPath)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
#if defined(Q_OS_WIN)
|
||||
// See #2693: Some exFAT file systems seem unable to cope with the
|
||||
// WAL journaling mode. They work fine with DELETE.
|
||||
QString fileSystem = FileSystem::fileSystemForPath(dbPath);
|
||||
@@ -67,6 +74,11 @@ static QString defaultJournalMode(const QString &dbPath)
|
||||
qCInfo(lcDb) << "Filesystem contains FAT - using DELETE journal mode";
|
||||
return "DELETE";
|
||||
}
|
||||
#elif defined(Q_OS_MAC)
|
||||
if (dbPath.startsWith("/Volumes/")) {
|
||||
qCInfo(lcDb) << "Mounted sync dir, do not use WAL for" << dbPath;
|
||||
return "DELETE";
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(dbPath)
|
||||
#endif
|
||||
@@ -264,6 +276,11 @@ bool SyncJournalDb::sqlFail(const QString &log, const SqlQuery &query)
|
||||
bool SyncJournalDb::checkConnect()
|
||||
{
|
||||
if (_db.isOpen()) {
|
||||
if (!QFile::exists(_dbFile)) {
|
||||
qCWarning(lcDb) << "Database open, but file file" + _dbFile + " does not exist";
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -550,7 +567,7 @@ bool SyncJournalDb::checkConnect()
|
||||
_getFilesBelowPathQuery.reset(new SqlQuery(_db));
|
||||
if (_getFilesBelowPathQuery->prepare(
|
||||
GET_FILE_RECORD_QUERY
|
||||
" WHERE path > (?1||'/') AND path < (?1||'0') ORDER BY path||'/' ASC")) {
|
||||
" WHERE " IS_PREFIX_PATH_OF("?1", "path") " ORDER BY path||'/' ASC")) {
|
||||
return sqlFail("prepare _getFilesBelowPathQuery", *_getFilesBelowPathQuery);
|
||||
}
|
||||
|
||||
@@ -620,7 +637,7 @@ bool SyncJournalDb::checkConnect()
|
||||
}
|
||||
|
||||
_deleteFileRecordRecursively.reset(new SqlQuery(_db));
|
||||
if (_deleteFileRecordRecursively->prepare("DELETE FROM metadata WHERE path LIKE(?||'/%')")) {
|
||||
if (_deleteFileRecordRecursively->prepare("DELETE FROM metadata WHERE " IS_PREFIX_PATH_OF("?1", "path"))) {
|
||||
return sqlFail("prepare _deleteFileRecordRecursively", *_deleteFileRecordRecursively);
|
||||
}
|
||||
|
||||
@@ -1780,9 +1797,8 @@ void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
|
||||
}
|
||||
|
||||
SqlQuery query(_db);
|
||||
query.prepare("UPDATE metadata SET fileid = '', inode = '0' WHERE path == ?1 OR path LIKE(?2||'/%')");
|
||||
query.prepare("UPDATE metadata SET fileid = '', inode = '0' WHERE " IS_PREFIX_PATH_OR_EQUAL("?1", "path"));
|
||||
query.bindValue(1, path);
|
||||
query.bindValue(2, path);
|
||||
query.exec();
|
||||
|
||||
// We also need to remove the ETags so the update phase refreshes the directory paths
|
||||
@@ -1792,25 +1808,28 @@ void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
|
||||
|
||||
void SyncJournalDb::avoidReadFromDbOnNextSync(const QByteArray &fileName)
|
||||
{
|
||||
// Make sure that on the next sync, fileName is not read from the DB but uses the PROPFIND to
|
||||
// get the info from the server
|
||||
// We achieve that by clearing the etag of the parents directory recursively
|
||||
|
||||
QMutexLocker locker(&_mutex);
|
||||
|
||||
if (!checkConnect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove trailing slash
|
||||
auto argument = fileName;
|
||||
if (argument.endsWith('/'))
|
||||
argument.chop(1);
|
||||
|
||||
SqlQuery query(_db);
|
||||
// This query will match entries for which the path is a prefix of fileName
|
||||
// Note: CSYNC_FTW_TYPE_DIR == 2
|
||||
query.prepare("UPDATE metadata SET md5='_invalid_' WHERE ?1 LIKE(path||'/%') AND type == 2;");
|
||||
query.bindValue(1, fileName);
|
||||
query.prepare("UPDATE metadata SET md5='_invalid_' WHERE " IS_PREFIX_PATH_OR_EQUAL("path", "?1") " AND type == 2;");
|
||||
query.bindValue(1, argument);
|
||||
query.exec();
|
||||
|
||||
// Prevent future overwrite of the etag for this sync
|
||||
_avoidReadFromDbOnNextSyncFilter.append(fileName);
|
||||
// Prevent future overwrite of the etags of this folder and all
|
||||
// parent folders for this sync
|
||||
argument.append('/');
|
||||
_avoidReadFromDbOnNextSyncFilter.append(argument);
|
||||
}
|
||||
|
||||
void SyncJournalDb::forceRemoteDiscoveryNextSync()
|
||||
|
||||
@@ -158,15 +158,16 @@ public:
|
||||
void setSelectiveSyncList(SelectiveSyncListType type, const QStringList &list);
|
||||
|
||||
/**
|
||||
* Make sure that on the next sync, fileName is not read from the DB but uses the PROPFIND to
|
||||
* get the info from the server
|
||||
* Make sure that on the next sync fileName and its parents are discovered from the server.
|
||||
*
|
||||
* Specifically, this sets the md5 field of fileName and all its parents to _invalid_.
|
||||
* That means its metadata and, if it's a directory, its direct contents.
|
||||
*
|
||||
* Specifically, etag (md5 field) of fileName and all its parents are set to _invalid_.
|
||||
* That causes a metadata difference and a resulting discovery from the remote for the
|
||||
* affected folders.
|
||||
*
|
||||
* Since folders in the selective sync list will not be rediscovered (csync_ftw,
|
||||
* _csync_detect_update skip them), the _invalid_ marker will stay and it. And any
|
||||
* _csync_detect_update skip them), the _invalid_ marker will stay. And any
|
||||
* child items in the db will be ignored when reading a remote tree from the database.
|
||||
*/
|
||||
void avoidReadFromDbOnNextSync(const QString &fileName) { avoidReadFromDbOnNextSync(fileName.toUtf8()); }
|
||||
@@ -268,6 +269,8 @@ private:
|
||||
/* This is the list of paths we called avoidReadFromDbOnNextSync on.
|
||||
* It means that they should not be written to the DB in any case since doing
|
||||
* that would write the etag and would void the purpose of avoidReadFromDbOnNextSync
|
||||
*
|
||||
* The contained paths have a trailing /.
|
||||
*/
|
||||
QList<QByteArray> _avoidReadFromDbOnNextSyncFilter;
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ bool hasLaunchOnStartup_private(const QString &)
|
||||
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
||||
CFURLRef itemUrlRef = NULL;
|
||||
|
||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
|
||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
|
||||
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
||||
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
||||
returnValue = true;
|
||||
@@ -100,7 +100,7 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
|
||||
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
||||
CFURLRef itemUrlRef = NULL;
|
||||
|
||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
|
||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
|
||||
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
||||
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
||||
LSSharedFileListItemRemove(loginItems, item); // remove it!
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
PROJECT( CrashReporter )
|
||||
cmake_policy(SET CMP0017 NEW)
|
||||
|
||||
list(APPEND crashreporter_SOURCES main.cpp)
|
||||
list(APPEND crashreporter_RC resources.qrc)
|
||||
|
||||
qt_wrap_ui( crashreporter_UI_HEADERS ${crashreporter_UI} )
|
||||
qt_add_resources( crashreporter_RC_RCC ${crashreporter_RC} )
|
||||
|
||||
|
||||
# TODO: differentiate release channel
|
||||
# if(BUILD_RELEASE)
|
||||
# set(CRASHREPORTER_RELEASE_CHANNEL "release")
|
||||
@@ -15,9 +8,30 @@ qt_add_resources( crashreporter_RC_RCC ${crashreporter_RC} )
|
||||
set(CRASHREPORTER_RELEASE_CHANNEL "nightly")
|
||||
# endif()
|
||||
|
||||
# Theme
|
||||
if(DEFINED OEM_THEME_DIR AND EXISTS "${OEM_THEME_DIR}/theme/colored")
|
||||
set(CRASHREPORTER_ICON_DIR "${OEM_THEME_DIR}/theme/colored")
|
||||
else()
|
||||
set(CRASHREPORTER_ICON_DIR "${CMAKE_SOURCE_DIR}/theme/colored")
|
||||
endif()
|
||||
|
||||
set(CRASHREPORTER_ICON_FILENAME "${APPLICATION_ICON_NAME}-icon.png")
|
||||
set(CRASHREPORTER_ICON ":/${CRASHREPORTER_ICON_FILENAME}")
|
||||
set(CRASHREPORTER_ICON_SIZE "128")
|
||||
set(CRASHREPORTER_ICON_PATH "${CRASHREPORTER_ICON_DIR}/${APPLICATION_ICON_NAME}-icon-${CRASHREPORTER_ICON_SIZE}.png")
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/resources.qrc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CrashReporterConfig.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/CrashReporterConfig.h)
|
||||
|
||||
# Sources
|
||||
list(APPEND crashreporter_SOURCES main.cpp)
|
||||
list(APPEND crashreporter_RC "${CMAKE_CURRENT_BINARY_DIR}/resources.qrc")
|
||||
|
||||
qt_wrap_ui( crashreporter_UI_HEADERS ${crashreporter_UI} )
|
||||
qt_add_resources( crashreporter_RC_RCC ${crashreporter_RC} )
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR}
|
||||
"../3rdparty/libcrashreporter-qt/src/"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file alias="owncloud-icon.png">../../theme/colored/owncloud-icon-128.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
5
src/crashreporter/resources.qrc.in
Normal file
5
src/crashreporter/resources.qrc.in
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file alias="@CRASHREPORTER_ICON_FILENAME@">@CRASHREPORTER_ICON_PATH@</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -85,6 +85,8 @@ int csync_update(CSYNC *ctx) {
|
||||
csync_gettime(&start);
|
||||
ctx->current = LOCAL_REPLICA;
|
||||
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "## Starting local discovery ##");
|
||||
|
||||
rc = csync_ftw(ctx, ctx->local.uri, csync_walker, MAX_DEPTH);
|
||||
if (rc < 0) {
|
||||
if(ctx->status_code == CSYNC_STATUS_OK) {
|
||||
@@ -104,6 +106,8 @@ int csync_update(CSYNC *ctx) {
|
||||
csync_gettime(&start);
|
||||
ctx->current = REMOTE_REPLICA;
|
||||
|
||||
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "## Starting remote discovery ##");
|
||||
|
||||
rc = csync_ftw(ctx, "", csync_walker, MAX_DEPTH);
|
||||
if (rc < 0) {
|
||||
if(ctx->status_code == CSYNC_STATUS_OK) {
|
||||
@@ -211,14 +215,14 @@ static int _csync_treewalk_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
|
||||
if (other_file_it == other_tree->cend()) {
|
||||
/* Check the renamed path as well. */
|
||||
QByteArray renamed_path = csync_rename_adjust_path(ctx, cur->path);
|
||||
QByteArray renamed_path = csync_rename_adjust_parent_path(ctx, cur->path);
|
||||
if (renamed_path != cur->path)
|
||||
other_file_it = other_tree->find(renamed_path);
|
||||
}
|
||||
|
||||
if (other_file_it == other_tree->cend()) {
|
||||
/* Check the source path as well. */
|
||||
QByteArray renamed_path = csync_rename_adjust_path_source(ctx, cur->path);
|
||||
QByteArray renamed_path = csync_rename_adjust_parent_path_source(ctx, cur->path);
|
||||
if (renamed_path != cur->path)
|
||||
other_file_it = other_tree->find(renamed_path);
|
||||
}
|
||||
@@ -314,6 +318,9 @@ int csync_s::reinitialize() {
|
||||
local.files.clear();
|
||||
remote.files.clear();
|
||||
|
||||
renames.folder_renamed_from.clear();
|
||||
renames.folder_renamed_to.clear();
|
||||
|
||||
status = CSYNC_STATUS_INIT;
|
||||
SAFE_FREE(error_string);
|
||||
|
||||
|
||||
@@ -108,7 +108,8 @@ enum csync_status_codes_e {
|
||||
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED,
|
||||
CSYNC_STATUS_FORBIDDEN,
|
||||
CSYNC_STATUS_INDIVIDUAL_TOO_DEEP,
|
||||
CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE
|
||||
CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE,
|
||||
CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE
|
||||
};
|
||||
|
||||
typedef enum csync_status_codes_e CSYNC_STATUS;
|
||||
|
||||
@@ -33,7 +33,8 @@ enum csync_exclude_type_e {
|
||||
CSYNC_FILE_EXCLUDE_LONG_FILENAME,
|
||||
CSYNC_FILE_EXCLUDE_HIDDEN,
|
||||
CSYNC_FILE_EXCLUDE_STAT_FAILED,
|
||||
CSYNC_FILE_EXCLUDE_CONFLICT
|
||||
CSYNC_FILE_EXCLUDE_CONFLICT,
|
||||
CSYNC_FILE_EXCLUDE_CANNOT_ENCODE
|
||||
};
|
||||
typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
|
||||
if (!other) {
|
||||
/* Check the renamed path as well. */
|
||||
other = other_tree->findFile(csync_rename_adjust_path(ctx, cur->path));
|
||||
other = other_tree->findFile(csync_rename_adjust_parent_path(ctx, cur->path));
|
||||
}
|
||||
if (!other) {
|
||||
/* Check if it is ignored */
|
||||
@@ -147,24 +147,25 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
|
||||
bool processedRename = false;
|
||||
auto renameCandidateProcessing = [&](const OCC::SyncJournalFileRecord &base) {
|
||||
auto renameCandidateProcessing = [&](const QByteArray &basePath) {
|
||||
if (processedRename)
|
||||
return;
|
||||
if (!base.isValid())
|
||||
if (basePath.isEmpty())
|
||||
return;
|
||||
|
||||
/* First, check that the file is NOT in our tree (another file with the same name was added) */
|
||||
if (our_tree->findFile(base._path)) {
|
||||
qCDebug(lcReconcile, "Origin found in our tree : %s", base._path.constData());
|
||||
if (our_tree->findFile(basePath)) {
|
||||
other = nullptr;
|
||||
qCInfo(lcReconcile, "Origin found in our tree : %s", basePath.constData());
|
||||
} else {
|
||||
/* Find the potential rename source file in the other tree.
|
||||
* If the renamed file could not be found in the opposite tree, that is because it
|
||||
* is not longer existing there, maybe because it was renamed or deleted.
|
||||
* The journal is cleaned up later after propagation.
|
||||
*/
|
||||
other = other_tree->findFile(base._path);
|
||||
qCDebug(lcReconcile, "Rename origin in other tree (%s) %s",
|
||||
base._path.constData(), other ? "found" : "not found");
|
||||
other = other_tree->findFile(basePath);
|
||||
qCInfo(lcReconcile, "Rename origin in other tree (%s) %s",
|
||||
basePath.constData(), other ? "found" : "not found");
|
||||
}
|
||||
|
||||
if(!other) {
|
||||
@@ -174,7 +175,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
// Some other EVAL_RENAME already claimed other.
|
||||
// We do nothing: maybe a different candidate for
|
||||
// other is found as well?
|
||||
qCDebug(lcReconcile, "Other has already been renamed to %s",
|
||||
qCInfo(lcReconcile, "Other has already been renamed to %s",
|
||||
other->rename_path.constData());
|
||||
} else if (cur->type == CSYNC_FTW_TYPE_DIR
|
||||
// The local replica is reconciled first, so the remote tree would
|
||||
@@ -186,18 +187,22 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
|| other->instruction == CSYNC_INSTRUCTION_NONE
|
||||
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|
||||
|| other->instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||
qCDebug(lcReconcile, "Switching %s to RENAME to %s",
|
||||
qCInfo(lcReconcile, "Switching %s to RENAME to %s",
|
||||
other->path.constData(), cur->path.constData());
|
||||
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
||||
other->rename_path = cur->path;
|
||||
if( !cur->file_id.isEmpty() ) {
|
||||
other->file_id = cur->file_id;
|
||||
}
|
||||
if (ctx->current == LOCAL_REPLICA) {
|
||||
// Keep the local mtime.
|
||||
other->modtime = cur->modtime;
|
||||
}
|
||||
other->inode = cur->inode;
|
||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
// We have consumed 'other': exit this loop to not consume another one.
|
||||
processedRename = true;
|
||||
} else if (our_tree->findFile(csync_rename_adjust_path(ctx, other->path)) == cur) {
|
||||
} else if (our_tree->findFile(csync_rename_adjust_parent_path(ctx, other->path)) == cur) {
|
||||
// If we're here, that means that the other side's reconcile will be able
|
||||
// to work against cur: The filename itself didn't change, only a parent
|
||||
// directory was renamed! In that case it's safe to ignore the rename
|
||||
@@ -206,7 +211,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
// Local: The remote reconcile will be able to deal with this.
|
||||
// Remote: The local replica has already dealt with this.
|
||||
// See the EVAL_RENAME case when other was found directly.
|
||||
qCDebug(lcReconcile, "File in a renamed directory, other side's instruction: %d",
|
||||
qCInfo(lcReconcile, "File in a renamed directory, other side's instruction: %d",
|
||||
other->instruction);
|
||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
} else {
|
||||
@@ -214,7 +219,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
// and the instruction in the local tree is NEW while cur has EVAL_RENAME
|
||||
// due to a remote move of the same file. In these scenarios we just
|
||||
// want the instruction to stay NEW.
|
||||
qCDebug(lcReconcile, "Other already has instruction %d",
|
||||
qCInfo(lcReconcile, "Other already has instruction %d",
|
||||
other->instruction);
|
||||
}
|
||||
};
|
||||
@@ -222,15 +227,37 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||
if (ctx->current == LOCAL_REPLICA) {
|
||||
/* use the old name to find the "other" node */
|
||||
OCC::SyncJournalFileRecord base;
|
||||
qCDebug(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
|
||||
qCInfo(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
|
||||
cur->inode);
|
||||
ctx->statedb->getFileRecordByInode(cur->inode, &base);
|
||||
renameCandidateProcessing(base);
|
||||
renameCandidateProcessing(base._path);
|
||||
} else {
|
||||
ASSERT(ctx->current == REMOTE_REPLICA);
|
||||
qCDebug(lcReconcile, "Finding rename origin through file ID %s",
|
||||
cur->file_id.constData());
|
||||
ctx->statedb->getFileRecordsByFileId(cur->file_id, renameCandidateProcessing);
|
||||
|
||||
// The update phase has already mapped out all dir->dir renames, check the
|
||||
// path that is consistent with that first. Otherwise update mappings and
|
||||
// reconcile mappings might disagree, leading to odd situations down the
|
||||
// line.
|
||||
auto basePath = csync_rename_adjust_full_path_source(ctx, cur->path);
|
||||
if (basePath != cur->path) {
|
||||
qCInfo(lcReconcile, "Trying rename origin by csync_rename mapping %s",
|
||||
basePath.constData());
|
||||
// We go through getFileRecordsByFileId to ensure the basePath
|
||||
// computed in this way also has the expected fileid.
|
||||
ctx->statedb->getFileRecordsByFileId(cur->file_id,
|
||||
[&](const OCC::SyncJournalFileRecord &base) {
|
||||
if (base._path == basePath)
|
||||
renameCandidateProcessing(basePath);
|
||||
});
|
||||
}
|
||||
|
||||
// Also feed all the other files with the same fileid if necessary
|
||||
if (!processedRename) {
|
||||
qCInfo(lcReconcile, "Finding rename origin through file ID %s",
|
||||
cur->file_id.constData());
|
||||
ctx->statedb->getFileRecordsByFileId(cur->file_id,
|
||||
[&](const OCC::SyncJournalFileRecord &base) { renameCandidateProcessing(base._path); });
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
@@ -36,7 +36,7 @@ void csync_rename_record(CSYNC* ctx, const QByteArray &from, const QByteArray &t
|
||||
ctx->renames.folder_renamed_from[to] = from;
|
||||
}
|
||||
|
||||
QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
|
||||
QByteArray csync_rename_adjust_parent_path(CSYNC *ctx, const QByteArray &path)
|
||||
{
|
||||
if (ctx->renames.folder_renamed_to.empty())
|
||||
return path;
|
||||
@@ -50,11 +50,25 @@ QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
|
||||
return path;
|
||||
}
|
||||
|
||||
QByteArray csync_rename_adjust_path_source(CSYNC* ctx, const QByteArray &path)
|
||||
QByteArray csync_rename_adjust_parent_path_source(CSYNC *ctx, const QByteArray &path)
|
||||
{
|
||||
if (ctx->renames.folder_renamed_from.empty())
|
||||
return path;
|
||||
for (auto p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
|
||||
for (ByteArrayRef p = _parentDir(path); !p.isEmpty(); p = _parentDir(p)) {
|
||||
auto it = ctx->renames.folder_renamed_from.find(p);
|
||||
if (it != ctx->renames.folder_renamed_from.end()) {
|
||||
QByteArray rep = it->second + path.mid(p.length());
|
||||
return rep;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
QByteArray csync_rename_adjust_full_path_source(CSYNC *ctx, const QByteArray &path)
|
||||
{
|
||||
if (ctx->renames.folder_renamed_from.empty())
|
||||
return path;
|
||||
for (ByteArrayRef p = path; !p.isEmpty(); p = _parentDir(p)) {
|
||||
auto it = ctx->renames.folder_renamed_from.find(p);
|
||||
if (it != ctx->renames.folder_renamed_from.end()) {
|
||||
QByteArray rep = it->second + path.mid(p.length());
|
||||
|
||||
@@ -22,10 +22,19 @@
|
||||
|
||||
#include "csync.h"
|
||||
|
||||
/* Return the final destination path of a given patch in case of renames */
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_path(CSYNC *ctx, const QByteArray &path);
|
||||
/* Return the final destination path of a given patch in case of renames
|
||||
*
|
||||
* Does only map the parent directories. If the directory "A" is renamed to
|
||||
* "B" then this function will not map "A" to "B". Only "A/foo" -> "B/foo".
|
||||
*/
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_parent_path(CSYNC *ctx, const QByteArray &path);
|
||||
|
||||
/* Return the source of a given path in case of renames */
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_path_source(CSYNC *ctx, const QByteArray &path);
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_parent_path_source(CSYNC *ctx, const QByteArray &path);
|
||||
|
||||
/* like the parent_path variant, but applying to the full path */
|
||||
QByteArray OCSYNC_EXPORT csync_rename_adjust_full_path_source(CSYNC *ctx, const QByteArray &path);
|
||||
|
||||
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const QByteArray &from, const QByteArray &to);
|
||||
/* Return the amount of renamed item recorded */
|
||||
bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
#include "common/utility.h"
|
||||
#include "common/asserts.h"
|
||||
|
||||
#include <QtCore/QTextCodec>
|
||||
|
||||
// Needed for PRIu64 on MinGW in C++ mode.
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#include <inttypes.h>
|
||||
@@ -128,12 +130,12 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
* This code should probably be in csync_exclude, but it does not have the fs parameter.
|
||||
* Keep it here for now */
|
||||
if (ctx->ignore_hidden_files && (fs->is_hidden)) {
|
||||
qCDebug(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
|
||||
qCInfo(lcUpdate, "file excluded because it is a hidden file: %s", fs->path.constData());
|
||||
excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
|
||||
}
|
||||
} else {
|
||||
/* File is ignored because it's matched by a user- or system exclude pattern. */
|
||||
qCDebug(lcUpdate, "%s excluded (%d)", fs->path.constData(), excluded);
|
||||
qCInfo(lcUpdate, "%s excluded (%d)", fs->path.constData(), excluded);
|
||||
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
|
||||
return 1;
|
||||
}
|
||||
@@ -148,9 +150,25 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
}
|
||||
}
|
||||
|
||||
auto localCodec = QTextCodec::codecForLocale();
|
||||
if (ctx->current == REMOTE_REPLICA && localCodec->mibEnum() != 106) {
|
||||
/* If the locale codec is not UTF-8, we must check that the filename from the server can
|
||||
* be encoded in the local file system.
|
||||
*
|
||||
* We cannot use QTextCodec::canEncode() since that can incorrectly return true, see
|
||||
* https://bugreports.qt.io/browse/QTBUG-6925.
|
||||
*/
|
||||
QTextEncoder encoder(localCodec, QTextCodec::ConvertInvalidToNull);
|
||||
if (encoder.fromUnicode(QString::fromUtf8(fs->path)).contains('\0')) {
|
||||
qCInfo(lcUpdate, "cannot encode %s to local encoding %d",
|
||||
fs->path.constData(), localCodec->mibEnum());
|
||||
excluded = CSYNC_FILE_EXCLUDE_CANNOT_ENCODE;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs->type == CSYNC_FTW_TYPE_FILE ) {
|
||||
if (fs->modtime == 0) {
|
||||
qCDebug(lcUpdate, "file: %s - mtime is zero!", fs->path.constData());
|
||||
qCInfo(lcUpdate, "file: %s - mtime is zero!", fs->path.constData());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,10 +195,12 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
/* we have an update! */
|
||||
qCInfo(lcUpdate, "Database entry found, compare: %" PRId64 " <-> %" PRId64
|
||||
", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64
|
||||
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x, ignore: %d",
|
||||
", size: %" PRId64 " <-> %" PRId64 ", perms: %x <-> %x"
|
||||
", checksum: %s <-> %s , ignore: %d",
|
||||
((int64_t) fs->modtime), ((int64_t) base._modtime),
|
||||
fs->etag.constData(), base._etag.constData(), (uint64_t) fs->inode, (uint64_t) base._inode,
|
||||
(uint64_t) fs->size, (uint64_t) base._fileSize, *reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm), base._serverHasIgnoredFiles );
|
||||
(uint64_t) fs->size, (uint64_t) base._fileSize, *reinterpret_cast<short*>(&fs->remotePerm), *reinterpret_cast<short*>(&base._remotePerm), fs->checksumHeader.constData(),
|
||||
base._checksumHeader.constData(), base._serverHasIgnoredFiles);
|
||||
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL;
|
||||
|
||||
@@ -210,7 +230,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
checksumIdentical = fs->checksumHeader == base._checksumHeader;
|
||||
}
|
||||
if (checksumIdentical) {
|
||||
qCDebug(lcUpdate, "NOTE: Checksums are identical, file did not actually change: %s", fs->path.constData());
|
||||
qCInfo(lcUpdate, "NOTE: Checksums are identical, file did not actually change: %s", fs->path.constData());
|
||||
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||
goto out;
|
||||
}
|
||||
@@ -234,7 +254,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
* The metadata comparison ensure that we fetch all the file id or permission when
|
||||
* upgrading owncloud
|
||||
*/
|
||||
qCDebug(lcUpdate, "Reading from database: %s", fs->path.constData());
|
||||
qCInfo(lcUpdate, "Reading from database: %s", fs->path.constData());
|
||||
ctx->remote.read_from_db = true;
|
||||
}
|
||||
/* If it was remembered in the db that the remote dir has ignored files, store
|
||||
@@ -245,7 +265,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
}
|
||||
if (metadata_differ) {
|
||||
/* file id or permissions has changed. Which means we need to update them in the DB. */
|
||||
qCDebug(lcUpdate, "Need to update metadata for: %s", fs->path.constData());
|
||||
qCInfo(lcUpdate, "Need to update metadata for: %s", fs->path.constData());
|
||||
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||
} else {
|
||||
fs->instruction = CSYNC_INSTRUCTION_NONE;
|
||||
@@ -253,7 +273,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
} else {
|
||||
/* check if it's a file and has been renamed */
|
||||
if (ctx->current == LOCAL_REPLICA) {
|
||||
qCDebug(lcUpdate, "Checking for rename based on inode # %" PRId64 "", (uint64_t) fs->inode);
|
||||
qCInfo(lcUpdate, "Checking for rename based on inode # %" PRId64 "", (uint64_t) fs->inode);
|
||||
|
||||
OCC::SyncJournalFileRecord base;
|
||||
if(!ctx->statedb->getFileRecordByInode(fs->inode, &base)) {
|
||||
@@ -280,13 +300,13 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
_rel_to_abs(ctx, fs->path), base._checksumHeader,
|
||||
ctx->callbacks.checksum_userdata);
|
||||
if (!fs->checksumHeader.isEmpty()) {
|
||||
qCDebug(lcUpdate, "checking checksum of potential rename %s %s <-> %s", fs->path.constData(), fs->checksumHeader.constData(), base._checksumHeader.constData());
|
||||
qCInfo(lcUpdate, "checking checksum of potential rename %s %s <-> %s", fs->path.constData(), fs->checksumHeader.constData(), base._checksumHeader.constData());
|
||||
isRename = fs->checksumHeader == base._checksumHeader;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRename) {
|
||||
qCDebug(lcUpdate, "pot rename detected based on inode # %" PRId64 "", (uint64_t) fs->inode);
|
||||
qCInfo(lcUpdate, "pot rename detected based on inode # %" PRId64 "", (uint64_t) fs->inode);
|
||||
/* inode found so the file has been renamed */
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
||||
@@ -296,6 +316,8 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
goto out;
|
||||
|
||||
} else {
|
||||
qCInfo(lcUpdate, "Checking for rename based on fileid %s", fs->file_id.constData());
|
||||
|
||||
/* Remote Replica Rename check */
|
||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||
|
||||
@@ -332,7 +354,7 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
||||
csync_rename_record(ctx, base._path, fs->path);
|
||||
}
|
||||
|
||||
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
||||
qCInfo(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||
done = true;
|
||||
};
|
||||
@@ -375,6 +397,8 @@ out:
|
||||
fs->error_status = CSYNC_STATUS_INDIVIDUAL_STAT_FAILED;
|
||||
} else if (excluded == CSYNC_FILE_EXCLUDE_CONFLICT) {
|
||||
fs->error_status = CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE;
|
||||
} else if (excluded == CSYNC_FILE_EXCLUDE_CANNOT_ENCODE) {
|
||||
fs->error_status = CSYNC_STATUS_INDIVIDUAL_CANNOT_ENCODE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,11 +462,11 @@ int csync_walker(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> fs) {
|
||||
}
|
||||
break;
|
||||
case CSYNC_FTW_TYPE_SLINK:
|
||||
qCDebug(lcUpdate, "symlink: %s - not supported", fs->path.constData());
|
||||
qCInfo(lcUpdate, "symlink: %s - not supported", fs->path.constData());
|
||||
break;
|
||||
default:
|
||||
qCInfo(lcUpdate, "item: %s - item type %d not iterated", fs->path.constData(), fs->type);
|
||||
return 0;
|
||||
break;
|
||||
}
|
||||
|
||||
rc = _csync_detect_update(ctx, std::move(fs));
|
||||
@@ -463,7 +487,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
||||
* their correct etags again and we don't run into this case.
|
||||
*/
|
||||
if( rec._etag == "_invalid_") {
|
||||
qCDebug(lcUpdate, "%s selective sync excluded", rec._path.constData());
|
||||
qCInfo(lcUpdate, "%s selective sync excluded", rec._path.constData());
|
||||
skipbase = rec._path;
|
||||
skipbase += '/';
|
||||
return;
|
||||
@@ -485,7 +509,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
||||
* without a full remote discovery being triggered. */
|
||||
CSYNC_EXCLUDE_TYPE excluded = csync_excluded_traversal(ctx, st->path, st->type);
|
||||
if (excluded != CSYNC_NOT_EXCLUDED) {
|
||||
qDebug(lcUpdate, "%s excluded (%d)", st->path.constData(), excluded);
|
||||
qInfo(lcUpdate, "%s excluded from db read (%d)", st->path.constData(), excluded);
|
||||
|
||||
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE
|
||||
|| excluded == CSYNC_FILE_SILENTLY_EXCLUDED) {
|
||||
@@ -504,7 +528,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
||||
ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR;
|
||||
return false;
|
||||
}
|
||||
qDebug(lcUpdate, "%" PRId64 " entries read below path %s from db.", count, uri);
|
||||
qInfo(lcUpdate, "%" PRId64 " entries read below path %s from db.", count, uri);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -700,7 +724,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
||||
}
|
||||
|
||||
csync_vio_closedir(ctx, dh);
|
||||
qCDebug(lcUpdate, " <= Closing walk for %s with read_from_db %d", uri, read_from_db);
|
||||
qCInfo(lcUpdate, " <= Closing walk for %s with read_from_db %d", uri, read_from_db);
|
||||
|
||||
return rc;
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ IF( APPLE )
|
||||
if(SPARKLE_FOUND)
|
||||
# Define this, we need to check in updater.cpp
|
||||
add_definitions( -DHAVE_SPARKLE )
|
||||
list(APPEND updater_SRCS updater/sparkleupdater_mac.mm)
|
||||
list(APPEND updater_SRCS updater/sparkleupdater_mac.mm updater/sparkleupdater.h)
|
||||
endif()
|
||||
ENDIF()
|
||||
|
||||
|
||||
@@ -263,7 +263,6 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
}
|
||||
|
||||
if (_model->classify(index) == FolderStatusModel::SubFolder) {
|
||||
QTreeView *tv = ui->_folderList;
|
||||
QMenu *menu = new QMenu(tv);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
@@ -275,8 +274,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
ac->setEnabled(false);
|
||||
}
|
||||
|
||||
menu->exec(QCursor::pos());
|
||||
|
||||
menu->popup(tv->mapToGlobal(pos));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,6 +289,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
auto folderMan = FolderMan::instance();
|
||||
|
||||
QMenu *menu = new QMenu(tv);
|
||||
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QAction *ac = menu->addAction(tr("Open folder"));
|
||||
@@ -316,7 +315,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
|
||||
ac = menu->addAction(tr("Remove folder sync connection"));
|
||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder);
|
||||
menu->exec(tv->mapToGlobal(pos));
|
||||
menu->popup(tv->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
|
||||
|
||||
@@ -312,10 +312,10 @@ void AccountState::slotInvalidCredentials()
|
||||
|
||||
if (account()->credentials()->ready()) {
|
||||
account()->credentials()->invalidateToken();
|
||||
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
|
||||
if (creds->refreshAccessToken())
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
|
||||
if (creds->refreshAccessToken())
|
||||
return;
|
||||
}
|
||||
account()->credentials()->askFromUser();
|
||||
}
|
||||
|
||||
@@ -429,14 +429,13 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod
|
||||
}
|
||||
|
||||
endNotificationRequest(job->widget(), replyCode);
|
||||
// FIXME: remove the widget after a couple of seconds
|
||||
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
||||
|
||||
// if the notification was successful start a timer that triggers
|
||||
// removal of the done widgets in a few seconds
|
||||
// Add 200 millisecs to the predefined value to make sure that the timer in
|
||||
// widget's method readyToClose() has elapsed.
|
||||
if (replyCode == OCS_SUCCESS_STATUS_CODE) {
|
||||
if (replyCode == OCS_SUCCESS_STATUS_CODE || replyCode == OCS_SUCCESS_STATUS_CODE_V2) {
|
||||
scheduleWidgetToRemove(job->widget());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,7 +338,9 @@ void Application::slotownCloudWizardDone(int res)
|
||||
shouldSetAutoStart = shouldSetAutoStart
|
||||
&& QCoreApplication::applicationDirPath().startsWith("/Applications/");
|
||||
#endif
|
||||
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), shouldSetAutoStart);
|
||||
if (shouldSetAutoStart) {
|
||||
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
||||
}
|
||||
|
||||
_gui->slotShowSettings();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "creds/shibbolethcredentials.h"
|
||||
#include "shibboleth/shibbolethuserjob.h"
|
||||
#include "creds/credentialscommon.h"
|
||||
#include "creds/httpcredentialsgui.h"
|
||||
|
||||
#include "accessmanager.h"
|
||||
#include "account.h"
|
||||
@@ -151,7 +152,31 @@ void ShibbolethCredentials::fetchFromKeychainHelper()
|
||||
|
||||
void ShibbolethCredentials::askFromUser()
|
||||
{
|
||||
showLoginWindow();
|
||||
// First, we do a DetermineAuthTypeJob to make sure that the server is still using shibboleth and did not upgrade to oauth
|
||||
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||
connect(job, &DetermineAuthTypeJob::authType, [this, job](DetermineAuthTypeJob::AuthType type) {
|
||||
if (type == DetermineAuthTypeJob::Shibboleth) {
|
||||
// Normal case, still shibboleth
|
||||
showLoginWindow();
|
||||
} else if (type == DetermineAuthTypeJob::OAuth) {
|
||||
// Hack: upgrade to oauth
|
||||
auto newCred = new HttpCredentialsGui;
|
||||
job->setParent(0);
|
||||
job->deleteLater();
|
||||
auto account = this->_account;
|
||||
auto user = this->_user;
|
||||
account->setCredentials(newCred); // delete this
|
||||
account->setCredentialSetting(QLatin1String("user"), user);
|
||||
newCred->fetchUser();
|
||||
newCred->askFromUser();
|
||||
} else {
|
||||
// Basic auth or unkown. Since it may be unkown it might be a temporary failure, don't replace the credentials here
|
||||
// Still show the login window in that case not to break the flow.
|
||||
showLoginWindow();
|
||||
}
|
||||
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
bool ShibbolethCredentials::stillValid(QNetworkReply *reply)
|
||||
|
||||
@@ -121,6 +121,10 @@ void Folder::checkLocalPath()
|
||||
{
|
||||
const QFileInfo fi(_definition.localPath);
|
||||
_canonicalLocalPath = fi.canonicalFilePath();
|
||||
#ifdef Q_OS_MAC
|
||||
// Workaround QTBUG-55896 (Should be fixed in Qt 5.8)
|
||||
_canonicalLocalPath = _canonicalLocalPath.normalized(QString::NormalizationForm_C);
|
||||
#endif
|
||||
if (_canonicalLocalPath.isEmpty()) {
|
||||
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
|
||||
_canonicalLocalPath = _definition.localPath;
|
||||
|
||||
@@ -59,7 +59,7 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
auto classif = static_cast<const FolderStatusModel *>(index.model())->classify(index);
|
||||
if (classif == FolderStatusModel::AddButton) {
|
||||
const int margins = aliasFm.height(); // same as 2*aliasMargin of paint
|
||||
QFontMetrics fm(option.font);
|
||||
QFontMetrics fm(qApp->font("QPushButton"));
|
||||
QStyleOptionButton opt;
|
||||
static_cast<QStyleOption &>(opt) = option;
|
||||
opt.text = addFolderText();
|
||||
@@ -138,7 +138,10 @@ void FolderStatusDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
||||
opt.rect.setWidth(qMin(opt.rect.width(), hint.width()));
|
||||
opt.rect.adjust(0, aliasMargin, 0, -aliasMargin);
|
||||
opt.rect = QStyle::visualRect(option.direction, option.rect, opt.rect);
|
||||
painter->save();
|
||||
painter->setFont(qApp->font("QPushButton"));
|
||||
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget);
|
||||
painter->restore();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <QWizardPage>
|
||||
#include <QTreeWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QEvent>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -536,9 +537,11 @@ FolderWizard::FolderWizard(AccountPtr account, QWidget *parent)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setPage(Page_Source, _folderWizardSourcePage);
|
||||
_folderWizardSourcePage->installEventFilter(this);
|
||||
if (!Theme::instance()->singleSyncFolder()) {
|
||||
_folderWizardTargetPage = new FolderWizardRemotePath(account);
|
||||
setPage(Page_Target, _folderWizardTargetPage);
|
||||
_folderWizardTargetPage->installEventFilter(this);
|
||||
}
|
||||
setPage(Page_SelectiveSync, _folderWizardSelectiveSyncPage);
|
||||
|
||||
@@ -551,5 +554,27 @@ FolderWizard::~FolderWizard()
|
||||
{
|
||||
}
|
||||
|
||||
bool FolderWizard::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::LayoutRequest) {
|
||||
// Workaround QTBUG-3396: forces QWizardPrivate::updateLayout()
|
||||
QTimer::singleShot(0, this, [this] { setTitleFormat(titleFormat()); });
|
||||
}
|
||||
return QWizard::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void FolderWizard::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWizard::resizeEvent(event);
|
||||
|
||||
// workaround for QTBUG-22819: when the error label word wrap, the minimum height is not adjusted
|
||||
if (auto page = currentPage()) {
|
||||
int hfw = page->heightForWidth(page->width());
|
||||
if (page->height() < hfw) {
|
||||
page->setMinimumSize(page->minimumSizeHint().width(), hfw);
|
||||
setTitleFormat(titleFormat()); // And another workaround for QTBUG-3396
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
||||
@@ -149,6 +149,9 @@ public:
|
||||
explicit FolderWizard(AccountPtr account, QWidget *parent = 0);
|
||||
~FolderWizard();
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
FolderWizardLocalPath *_folderWizardSourcePage;
|
||||
FolderWizardRemotePath *_folderWizardTargetPage;
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
#include "updater/updater.h"
|
||||
#include "updater/ocupdater.h"
|
||||
#ifdef Q_OS_MAC
|
||||
// FIXME We should unify those, but Sparkle does everything behind the scene transparently
|
||||
#include "updater/sparkleupdater.h"
|
||||
#endif
|
||||
#include "ignorelisteditor.h"
|
||||
|
||||
#include "config.h"
|
||||
@@ -32,6 +36,7 @@
|
||||
#include <QNetworkProxy>
|
||||
#include <QDir>
|
||||
#include <QScopedValueRollback>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -88,6 +93,18 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
||||
|
||||
// accountAdded means the wizard was finished and the wizard might change some options.
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings);
|
||||
|
||||
// Only our standard brandings currently support beta channel
|
||||
Theme *theme = Theme::instance();
|
||||
if (theme->appName() != QLatin1String("ownCloud") && theme->appName() != QLatin1String("testpilotcloud") ) {
|
||||
#ifdef Q_OS_MAC
|
||||
// Because we don't have any statusString from the SparkleUpdater anyway we can hide the whole thing
|
||||
_ui->updatesGroupBox->hide();
|
||||
#else
|
||||
_ui->updateChannelLabel->hide();
|
||||
_ui->updateChannel->hide();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettings::~GeneralSettings()
|
||||
@@ -117,22 +134,79 @@ void GeneralSettings::loadMiscSettings()
|
||||
|
||||
void GeneralSettings::slotUpdateInfo()
|
||||
{
|
||||
// Note: the sparkle-updater is not an OCUpdater
|
||||
OCUpdater *updater = qobject_cast<OCUpdater *>(Updater::instance());
|
||||
if (ConfigFile().skipUpdateCheck()) {
|
||||
updater = 0; // don't show update info if updates are disabled
|
||||
if (ConfigFile().skipUpdateCheck() || !Updater::instance()) {
|
||||
// updater disabled on compile
|
||||
_ui->updatesGroupBox->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updater) {
|
||||
connect(updater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection);
|
||||
connect(_ui->restartButton, &QAbstractButton::clicked, updater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection);
|
||||
// Note: the sparkle-updater is not an OCUpdater
|
||||
OCUpdater *ocupdater = qobject_cast<OCUpdater *>(Updater::instance());
|
||||
if (ocupdater) {
|
||||
connect(ocupdater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection);
|
||||
connect(_ui->restartButton, &QAbstractButton::clicked, ocupdater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection);
|
||||
connect(_ui->restartButton, &QAbstractButton::clicked, qApp, &QApplication::quit, Qt::UniqueConnection);
|
||||
_ui->updateStateLabel->setText(updater->statusString());
|
||||
_ui->restartButton->setVisible(updater->downloadState() == OCUpdater::DownloadComplete);
|
||||
} else {
|
||||
// can't have those infos from sparkle currently
|
||||
_ui->updatesGroupBox->setVisible(false);
|
||||
|
||||
_ui->updateStateLabel->setText(ocupdater->statusString());
|
||||
_ui->restartButton->setVisible(ocupdater->downloadState() == OCUpdater::DownloadComplete);
|
||||
|
||||
}
|
||||
#ifdef Q_OS_MAC
|
||||
else if (SparkleUpdater *sparkleUpdater = qobject_cast<SparkleUpdater *>(Updater::instance())) {
|
||||
_ui->updateStateLabel->setText(sparkleUpdater->statusString());
|
||||
_ui->restartButton->setVisible(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Channel selection
|
||||
_ui->updateChannel->setCurrentIndex(ConfigFile().updateChannel() == "beta" ? 1 : 0);
|
||||
connect(_ui->updateChannel, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
this, &GeneralSettings::slotUpdateChannelChanged, Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void GeneralSettings::slotUpdateChannelChanged(int index)
|
||||
{
|
||||
QString channel = index == 0 ? QStringLiteral("stable") : QStringLiteral("beta");
|
||||
if (channel == ConfigFile().updateChannel())
|
||||
return;
|
||||
|
||||
auto msgBox = new QMessageBox(
|
||||
QMessageBox::Warning,
|
||||
tr("Change update channel?"),
|
||||
tr("The update channel determines which client updates will be offered "
|
||||
"for installation. The \"stable\" channel contains only upgrades that "
|
||||
"are considered reliable, while the versions in the \"beta\" channel "
|
||||
"may contain newer features and bugfixes, but have not yet been tested "
|
||||
"thoroughly."
|
||||
"\n\n"
|
||||
"Note that this selects only what pool upgrades are taken from, and that "
|
||||
"there are no downgrades: So going back from the beta channel to "
|
||||
"the stable channel usually cannot be done immediately and means waiting "
|
||||
"for a stable version that is newer than the currently installed beta "
|
||||
"version."),
|
||||
QMessageBox::NoButton,
|
||||
this);
|
||||
msgBox->addButton(tr("Change update channel"), QMessageBox::AcceptRole);
|
||||
msgBox->addButton(tr("Cancel"), QMessageBox::RejectRole);
|
||||
connect(msgBox, &QMessageBox::finished, msgBox, [this, channel, msgBox](int result) {
|
||||
msgBox->deleteLater();
|
||||
if (result == QMessageBox::AcceptRole) {
|
||||
ConfigFile().setUpdateChannel(channel);
|
||||
if (OCUpdater *updater = qobject_cast<OCUpdater *>(Updater::instance())) {
|
||||
updater->setUpdateUrl(Updater::updateUrl());
|
||||
updater->checkForUpdate();
|
||||
}
|
||||
#ifdef Q_OS_MAC
|
||||
else if (SparkleUpdater *updater = qobject_cast<SparkleUpdater *>(Updater::instance())) {
|
||||
updater->setUpdateUrl(Updater::updateUrl());
|
||||
updater->checkForUpdate();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
_ui->updateChannel->setCurrentText(ConfigFile().updateChannel());
|
||||
}
|
||||
});
|
||||
msgBox->open();
|
||||
}
|
||||
|
||||
void GeneralSettings::saveMiscSettings()
|
||||
|
||||
@@ -44,6 +44,7 @@ private slots:
|
||||
void slotToggleLaunchOnStartup(bool);
|
||||
void slotToggleOptionalDesktopNotifications(bool);
|
||||
void slotUpdateInfo();
|
||||
void slotUpdateChannelChanged(int index);
|
||||
void slotIgnoreFilesEditor();
|
||||
void loadMiscSettings();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>785</width>
|
||||
<height>523</height>
|
||||
<height>533</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -74,48 +74,88 @@
|
||||
<property name="title">
|
||||
<string>Updates</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="updateStateLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restartButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Restart && Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="updateChannelLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Channel</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>updateChannel</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="updateChannel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>stable</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>beta</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="updateStateLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restartButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Restart && Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -247,7 +287,10 @@
|
||||
<tabstop>ignoredFilesButton</tabstop>
|
||||
<tabstop>newFolderLimitCheckBox</tabstop>
|
||||
<tabstop>newFolderLimitSpinBox</tabstop>
|
||||
<tabstop>newExternalStorage</tabstop>
|
||||
<tabstop>showInExplorerNavigationPaneCheckBox</tabstop>
|
||||
<tabstop>crashreporterCheckBox</tabstop>
|
||||
<tabstop>updateChannel</tabstop>
|
||||
<tabstop>restartButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* If more issues are reported than this they will not show up
|
||||
* to avoid performance issues around sorting this many issues.
|
||||
*/
|
||||
static const int maxIssueCount = 50000;
|
||||
|
||||
IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _ui(new Ui::IssuesWidget)
|
||||
@@ -96,6 +102,14 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
#if defined(Q_OS_MAC)
|
||||
_ui->_treeWidget->setMinimumWidth(400);
|
||||
#endif
|
||||
|
||||
_reenableSorting.setInterval(5000);
|
||||
connect(&_reenableSorting, &QTimer::timeout, this,
|
||||
[this]() { _ui->_treeWidget->setSortingEnabled(true); });
|
||||
|
||||
_ui->_tooManyIssuesWarning->hide();
|
||||
connect(this, &IssuesWidget::issueCountUpdated, this,
|
||||
[this](int count) { _ui->_tooManyIssuesWarning->setVisible(count >= maxIssueCount); });
|
||||
}
|
||||
|
||||
IssuesWidget::~IssuesWidget()
|
||||
@@ -153,11 +167,17 @@ void IssuesWidget::addItem(QTreeWidgetItem *item)
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
int insertLoc = 0;
|
||||
int count = _ui->_treeWidget->topLevelItemCount();
|
||||
if (count >= maxIssueCount)
|
||||
return;
|
||||
|
||||
_ui->_treeWidget->setSortingEnabled(false);
|
||||
_reenableSorting.start();
|
||||
|
||||
// Insert item specific errors behind the others
|
||||
int insertLoc = 0;
|
||||
if (!item->text(1).isEmpty()) {
|
||||
for (int i = 0; i < _ui->_treeWidget->topLevelItemCount(); ++i) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (_ui->_treeWidget->topLevelItem(i)->text(1).isEmpty()) {
|
||||
insertLoc = i + 1;
|
||||
} else {
|
||||
@@ -196,7 +216,7 @@ void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &p
|
||||
|
||||
void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||
{
|
||||
if (!item->hasErrorStatus())
|
||||
if (!item->showInIssuesTab())
|
||||
return;
|
||||
QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item);
|
||||
if (!line)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QDialog>
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
#include <QTimer>
|
||||
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
@@ -84,6 +85,9 @@ private:
|
||||
/// Wipes all insufficient remote storgage blacklist entries
|
||||
void retryInsufficentRemoteStorageErrors(const QString &folderAlias);
|
||||
|
||||
/// Each insert disables sorting, this timer reenables it
|
||||
QTimer _reenableSorting;
|
||||
|
||||
Ui::IssuesWidget *_ui;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -127,6 +127,13 @@
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="_tooManyIssuesWarning">
|
||||
<property name="text">
|
||||
<string>There were too many issues. Not all will be visible here.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
|
||||
@@ -128,8 +128,8 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode)
|
||||
|
||||
QString timeStr = locale.toString(QTime::currentTime());
|
||||
|
||||
// the ocs API returns stat code 100 if it succeeded.
|
||||
if (statusCode != OCS_SUCCESS_STATUS_CODE) {
|
||||
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
|
||||
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
|
||||
qCWarning(lcNotifications) << "Notification Request to Server failed, leave button visible.";
|
||||
for (i = 0; i < _buttons.count(); i++) {
|
||||
_buttons.at(i)->setEnabled(true);
|
||||
|
||||
@@ -28,6 +28,7 @@ OcsJob::OcsJob(AccountPtr account)
|
||||
: AbstractNetworkJob(account, "")
|
||||
{
|
||||
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
|
||||
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE_V2);
|
||||
setIgnoreCredentialFailure(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include <QUrl>
|
||||
|
||||
#define OCS_SUCCESS_STATUS_CODE 100
|
||||
// Apparantly the v2.php URLs can return that
|
||||
#define OCS_SUCCESS_STATUS_CODE_V2 200
|
||||
|
||||
class QJsonDocument;
|
||||
|
||||
|
||||
@@ -50,18 +50,13 @@ const char propertyAccountC[] = "oc_account";
|
||||
ownCloudGui::ownCloudGui(Application *parent)
|
||||
: QObject(parent)
|
||||
, _tray(0)
|
||||
,
|
||||
#if defined(Q_OS_MAC)
|
||||
_settingsDialog(new SettingsDialogMac(this))
|
||||
,
|
||||
, _settingsDialog(new SettingsDialogMac(this))
|
||||
#else
|
||||
_settingsDialog(new SettingsDialog(this))
|
||||
,
|
||||
, _settingsDialog(new SettingsDialog(this))
|
||||
#endif
|
||||
_logBrowser(0)
|
||||
, _contextMenuVisibleOsx(false)
|
||||
, _logBrowser(0)
|
||||
, _recentActionsMenu(0)
|
||||
, _qdbusmenuWorkaround(false)
|
||||
, _app(parent)
|
||||
{
|
||||
_tray = new Systray();
|
||||
@@ -117,7 +112,7 @@ void ownCloudGui::slotOpenSettingsDialog()
|
||||
|
||||
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
if (_qdbusmenuWorkaround) {
|
||||
if (_workaroundFakeDoubleClick) {
|
||||
static QElapsedTimer last_click;
|
||||
if (last_click.isValid() && last_click.elapsed() < 200) {
|
||||
return;
|
||||
@@ -349,7 +344,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
||||
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
|
||||
}
|
||||
|
||||
QAction *action = new QAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()), menu);
|
||||
QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()));
|
||||
auto alias = folder->alias();
|
||||
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
|
||||
}
|
||||
@@ -381,17 +376,19 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
||||
|
||||
void ownCloudGui::slotContextMenuAboutToShow()
|
||||
{
|
||||
// For some reason on OS X _contextMenu->isVisible returns always false
|
||||
_contextMenuVisibleOsx = true;
|
||||
_contextMenuVisibleManual = true;
|
||||
|
||||
// Update icon in sys tray, as it might change depending on the context menu state
|
||||
slotComputeOverallSyncStatus();
|
||||
|
||||
if (!_workaroundNoAboutToShowUpdate) {
|
||||
updateContextMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void ownCloudGui::slotContextMenuAboutToHide()
|
||||
{
|
||||
// For some reason on OS X _contextMenu->isVisible returns always false
|
||||
_contextMenuVisibleOsx = false;
|
||||
_contextMenuVisibleManual = false;
|
||||
|
||||
// Update icon in sys tray, as it might change depending on the context menu state
|
||||
slotComputeOverallSyncStatus();
|
||||
@@ -399,11 +396,11 @@ void ownCloudGui::slotContextMenuAboutToHide()
|
||||
|
||||
bool ownCloudGui::contextMenuVisible() const
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
return _contextMenuVisibleOsx;
|
||||
#else
|
||||
// On some platforms isVisible doesn't work and always returns false,
|
||||
// elsewhere aboutToHide is unreliable.
|
||||
if (_workaroundManualVisibility)
|
||||
return _contextMenuVisibleManual;
|
||||
return _contextMenu->isVisible();
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool minimalTrayMenu()
|
||||
@@ -426,12 +423,36 @@ static bool updateWhileVisible()
|
||||
}
|
||||
}
|
||||
|
||||
static QByteArray forceQDBusTrayWorkaround()
|
||||
static QByteArray envForceQDBusTrayWorkaround()
|
||||
{
|
||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
|
||||
return var;
|
||||
}
|
||||
|
||||
static QByteArray envForceWorkaroundShowAndHideTray()
|
||||
{
|
||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE");
|
||||
return var;
|
||||
}
|
||||
|
||||
static QByteArray envForceWorkaroundNoAboutToShowUpdate()
|
||||
{
|
||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW");
|
||||
return var;
|
||||
}
|
||||
|
||||
static QByteArray envForceWorkaroundFakeDoubleClick()
|
||||
{
|
||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK");
|
||||
return var;
|
||||
}
|
||||
|
||||
static QByteArray envForceWorkaroundManualVisibility()
|
||||
{
|
||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY");
|
||||
return var;
|
||||
}
|
||||
|
||||
void ownCloudGui::setupContextMenu()
|
||||
{
|
||||
if (_contextMenu) {
|
||||
@@ -454,51 +475,65 @@ void ownCloudGui::setupContextMenu()
|
||||
return;
|
||||
}
|
||||
|
||||
// Enables workarounds for bugs introduced in Qt 5.5.0
|
||||
// In particular QTBUG-47863 #3672 (tray menu fails to update and
|
||||
// becomes unresponsive) and QTBUG-48068 #3722 (click signal is
|
||||
// emitted several times)
|
||||
// The Qt version check intentionally uses 5.0.0 (where platformMenu()
|
||||
// was introduced) instead of 5.5.0 to avoid issues where the Qt
|
||||
// version used to build is different from the one used at runtime.
|
||||
// If we build with 5.6.1 or newer, we can skip this because the
|
||||
// bugs should be fixed there.
|
||||
#ifdef Q_OS_LINUX
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) && (QT_VERSION < QT_VERSION_CHECK(5, 6, 0))
|
||||
if (qVersion() == QByteArray("5.5.0")) {
|
||||
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
|
||||
if (platformMenu
|
||||
&& platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
|
||||
_qdbusmenuWorkaround = true;
|
||||
qCWarning(lcApplication) << "Enabled QDBusPlatformMenu workaround";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
auto applyEnvVariable = [](bool *sw, const QByteArray &value) {
|
||||
if (value == "1")
|
||||
*sw = true;
|
||||
if (value == "0")
|
||||
*sw = false;
|
||||
};
|
||||
|
||||
if (forceQDBusTrayWorkaround() == "1") {
|
||||
_qdbusmenuWorkaround = true;
|
||||
} else if (forceQDBusTrayWorkaround() == "0") {
|
||||
_qdbusmenuWorkaround = false;
|
||||
// This is an old compound flag that people might still depend on
|
||||
bool qdbusmenuWorkarounds = false;
|
||||
applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround());
|
||||
if (qdbusmenuWorkarounds) {
|
||||
_workaroundFakeDoubleClick = true;
|
||||
_workaroundNoAboutToShowUpdate = true;
|
||||
_workaroundShowAndHideTray = true;
|
||||
}
|
||||
|
||||
// When the qdbusmenuWorkaround is necessary, we can't do on-demand updates
|
||||
// because the workaround is to hide and show the tray icon.
|
||||
if (_qdbusmenuWorkaround) {
|
||||
connect(&_workaroundBatchTrayUpdate, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
|
||||
_workaroundBatchTrayUpdate.setInterval(30 * 1000);
|
||||
_workaroundBatchTrayUpdate.setSingleShot(true);
|
||||
} else {
|
||||
// Update the context menu whenever we're about to show it
|
||||
// to the user.
|
||||
#ifdef Q_OS_MAC
|
||||
// https://bugreports.qt.io/browse/QTBUG-54633
|
||||
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
|
||||
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
|
||||
#else
|
||||
connect(_contextMenu.data(), &QMenu::aboutToShow, this, &ownCloudGui::updateContextMenu);
|
||||
// https://bugreports.qt.io/browse/QTBUG-54633
|
||||
_workaroundNoAboutToShowUpdate = true;
|
||||
_workaroundManualVisibility = true;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// For KDE sessions if the platform plugin is missing,
|
||||
// neither aboutToShow() updates nor the isVisible() call
|
||||
// work. At least aboutToHide is reliable.
|
||||
// https://github.com/owncloud/client/issues/6545
|
||||
static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
|
||||
static QByteArray desktopSession = qgetenv("DESKTOP_SESSION");
|
||||
bool isKde =
|
||||
xdgCurrentDesktop.contains("KDE")
|
||||
|| desktopSession.contains("plasma")
|
||||
|| desktopSession.contains("kde");
|
||||
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
|
||||
if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
|
||||
_workaroundManualVisibility = true;
|
||||
_workaroundNoAboutToShowUpdate = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate());
|
||||
applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick());
|
||||
applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray());
|
||||
applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility());
|
||||
|
||||
qCInfo(lcApplication) << "Tray menu workarounds:"
|
||||
<< "noabouttoshow:" << _workaroundNoAboutToShowUpdate
|
||||
<< "fakedoubleclick:" << _workaroundFakeDoubleClick
|
||||
<< "showhide:" << _workaroundShowAndHideTray
|
||||
<< "manualvisibility:" << _workaroundManualVisibility;
|
||||
|
||||
|
||||
connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
|
||||
_delayedTrayUpdateTimer.setInterval(2 * 1000);
|
||||
_delayedTrayUpdateTimer.setSingleShot(true);
|
||||
|
||||
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
|
||||
// unfortunately aboutToHide is unreliable, it seems to work on OSX though
|
||||
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
|
||||
|
||||
// Populate the context menu now.
|
||||
updateContextMenu();
|
||||
@@ -510,13 +545,21 @@ void ownCloudGui::updateContextMenu()
|
||||
return;
|
||||
}
|
||||
|
||||
if (_qdbusmenuWorkaround) {
|
||||
// If it's visible, we can't update live, and it won't be updated lazily: reschedule
|
||||
if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) {
|
||||
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||
_delayedTrayUpdateTimer.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_workaroundShowAndHideTray) {
|
||||
// To make tray menu updates work with these bugs (see setupContextMenu)
|
||||
// we need to hide and show the tray icon. We don't want to do that
|
||||
// while it's visible!
|
||||
if (contextMenuVisible()) {
|
||||
if (!_workaroundBatchTrayUpdate.isActive()) {
|
||||
_workaroundBatchTrayUpdate.start();
|
||||
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||
_delayedTrayUpdateTimer.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -631,35 +674,30 @@ void ownCloudGui::updateContextMenu()
|
||||
}
|
||||
_contextMenu->addAction(_actionQuit);
|
||||
|
||||
if (_qdbusmenuWorkaround) {
|
||||
if (_workaroundShowAndHideTray) {
|
||||
_tray->show();
|
||||
}
|
||||
}
|
||||
|
||||
void ownCloudGui::updateContextMenuNeeded()
|
||||
{
|
||||
// For the workaround case updating while visible is impossible. Instead
|
||||
// occasionally update the menu when it's invisible.
|
||||
if (_qdbusmenuWorkaround) {
|
||||
if (!_workaroundBatchTrayUpdate.isActive()) {
|
||||
_workaroundBatchTrayUpdate.start();
|
||||
}
|
||||
// if it's visible and we can update live: update now
|
||||
if (contextMenuVisible() && updateWhileVisible()) {
|
||||
// Note: don't update while visible on OSX
|
||||
// https://bugreports.qt.io/browse/QTBUG-54845
|
||||
updateContextMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// https://bugreports.qt.io/browse/QTBUG-54845
|
||||
// We cannot update on demand or while visible -> update when invisible.
|
||||
if (!contextMenuVisible()) {
|
||||
updateContextMenu();
|
||||
// if we can't lazily update: update later
|
||||
if (_workaroundNoAboutToShowUpdate) {
|
||||
// Note: don't update immediately even in the invisible case
|
||||
// as that can lead to extremely frequent menu updates
|
||||
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||
_delayedTrayUpdateTimer.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (updateWhileVisible() && contextMenuVisible())
|
||||
updateContextMenu();
|
||||
#endif
|
||||
|
||||
// If no update was done here, we might update it on-demand due to
|
||||
// the aboutToShow() signal.
|
||||
}
|
||||
|
||||
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
|
||||
|
||||
@@ -119,14 +119,19 @@ private:
|
||||
// tray's menu
|
||||
QScopedPointer<QMenu> _contextMenu;
|
||||
|
||||
// Manually tracking whether the context menu is visible, but only works
|
||||
// on OSX because aboutToHide is not reliable everywhere.
|
||||
bool _contextMenuVisibleOsx;
|
||||
// Manually tracking whether the context menu is visible via aboutToShow
|
||||
// and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere
|
||||
// so this only gets used with _workaroundManualVisibility (when the tray's
|
||||
// isVisible() is unreliable)
|
||||
bool _contextMenuVisibleManual = false;
|
||||
|
||||
QMenu *_recentActionsMenu;
|
||||
QVector<QMenu *> _accountMenus;
|
||||
bool _qdbusmenuWorkaround;
|
||||
QTimer _workaroundBatchTrayUpdate;
|
||||
bool _workaroundShowAndHideTray = false;
|
||||
bool _workaroundNoAboutToShowUpdate = false;
|
||||
bool _workaroundFakeDoubleClick = false;
|
||||
bool _workaroundManualVisibility = false;
|
||||
QTimer _delayedTrayUpdateTimer;
|
||||
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
|
||||
|
||||
QAction *_actionLogin;
|
||||
|
||||
@@ -195,7 +195,7 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo
|
||||
|
||||
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||
{
|
||||
if (item->hasErrorStatus())
|
||||
if (!item->showInProtocolTab())
|
||||
return;
|
||||
QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item);
|
||||
if (line) {
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg)
|
||||
|
||||
const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications");
|
||||
|
||||
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -47,7 +49,7 @@ void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr)
|
||||
}
|
||||
|
||||
// if the previous notification job has finished, start next.
|
||||
_notificationJob = new JsonApiJob(ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this);
|
||||
_notificationJob = new JsonApiJob(ptr->account(), notificationsPath, this);
|
||||
QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived,
|
||||
this, &ServerNotificationHandler::slotNotificationsReceived);
|
||||
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState *>(ptr));
|
||||
@@ -94,6 +96,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
||||
|
||||
a._links.append(al);
|
||||
}
|
||||
|
||||
// Add another action to dismiss notification on server
|
||||
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
|
||||
ActivityLink al;
|
||||
al._label = tr("Dismiss");
|
||||
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
|
||||
al._verb = "DELETE";
|
||||
al._isPrimary = false;
|
||||
a._links.append(al);
|
||||
|
||||
list.append(a);
|
||||
}
|
||||
emit newNotificationList(list);
|
||||
|
||||
@@ -254,6 +254,9 @@ void SettingsDialog::accountAdded(AccountState *s)
|
||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged);
|
||||
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged);
|
||||
|
||||
// Refresh immediatly when getting online
|
||||
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender);
|
||||
|
||||
slotRefreshActivity(s);
|
||||
}
|
||||
|
||||
@@ -396,6 +399,11 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q
|
||||
return createActionWithIcon(coloredIcon, text, iconPath);
|
||||
}
|
||||
|
||||
void SettingsDialog::slotRefreshActivityAccountStateSender()
|
||||
{
|
||||
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
|
||||
}
|
||||
|
||||
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
|
||||
{
|
||||
if (accountState) {
|
||||
|
||||
@@ -59,6 +59,7 @@ public slots:
|
||||
void showIssuesList(const QString &folderAlias);
|
||||
void slotSwitchPage(QAction *action);
|
||||
void slotRefreshActivity(AccountState *accountState);
|
||||
void slotRefreshActivityAccountStateSender();
|
||||
void slotAccountAvatarChanged();
|
||||
void slotAccountDisplayNameChanged();
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "generalsettings.h"
|
||||
#include "networksettings.h"
|
||||
#include "accountsettings.h"
|
||||
#include "accountstate.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "configfile.h"
|
||||
#include "progressdispatcher.h"
|
||||
@@ -121,6 +122,7 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
|
||||
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometry(this);
|
||||
_activitySettings->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||
}
|
||||
|
||||
void SettingsDialogMac::closeEvent(QCloseEvent *event)
|
||||
@@ -160,6 +162,9 @@ void SettingsDialogMac::accountAdded(AccountState *s)
|
||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialogMac::slotAccountAvatarChanged);
|
||||
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialogMac::slotAccountDisplayNameChanged);
|
||||
|
||||
// Refresh immediatly when getting online
|
||||
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialogMac::slotRefreshActivityAccountStateSender);
|
||||
|
||||
slotRefreshActivity(s);
|
||||
}
|
||||
|
||||
@@ -175,6 +180,11 @@ void SettingsDialogMac::accountRemoved(AccountState *s)
|
||||
_activitySettings->slotRemoveAccount(s);
|
||||
}
|
||||
|
||||
void SettingsDialogMac::slotRefreshActivityAccountStateSender()
|
||||
{
|
||||
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
|
||||
}
|
||||
|
||||
void SettingsDialogMac::slotRefreshActivity(AccountState *accountState)
|
||||
{
|
||||
if (accountState) {
|
||||
|
||||
@@ -49,6 +49,7 @@ public slots:
|
||||
void showActivityPage();
|
||||
void showIssuesList(const QString &folderAlias);
|
||||
void slotRefreshActivity(AccountState *accountState);
|
||||
void slotRefreshActivityAccountStateSender();
|
||||
|
||||
private slots:
|
||||
void accountAdded(AccountState *);
|
||||
|
||||
@@ -58,7 +58,9 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
//Is this a file or folder?
|
||||
QFileInfo fi(localPath);
|
||||
_isFile = fi.isFile();
|
||||
_ui->nameLineEdit->setText(tr("%1 link").arg(fi.fileName()));
|
||||
|
||||
// Note: the share name cannot be longer than 64 characters
|
||||
_ui->nameLineEdit->setText(tr("Public link"));
|
||||
|
||||
// the following progress indicator widgets are added to layouts which makes them
|
||||
// automatically deleted once the dialog dies.
|
||||
@@ -158,12 +160,12 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
||||
_linkContextMenu = new QMenu(this);
|
||||
connect(_linkContextMenu, &QMenu::triggered,
|
||||
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
|
||||
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
|
||||
_openLinkAction = _linkContextMenu->addAction(tr("Open link in browser"));
|
||||
_copyLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard"));
|
||||
_copyDirectLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard (direct download)"));
|
||||
_emailLinkAction = _linkContextMenu->addAction(tr("Send link by email"));
|
||||
_emailDirectLinkAction = _linkContextMenu->addAction(tr("Send link by email (direct download)"));
|
||||
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
|
||||
|
||||
/*
|
||||
* Create the share manager and connect it properly
|
||||
@@ -221,11 +223,10 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
||||
// Connect all shares signals to gui slots
|
||||
connect(share.data(), &Share::serverError, this, &ShareLinkWidget::slotServerError);
|
||||
connect(share.data(), &Share::shareDeleted, this, &ShareLinkWidget::slotDeleteShareFetched);
|
||||
connect(share.data(), SIGNAL(expireDateSet()), SLOT(slotExpireSet()));
|
||||
connect(share.data(), SIGNAL(publicUploadSet()), SLOT(slotPermissionsSet()));
|
||||
connect(share.data(), SIGNAL(passwordSet()), SLOT(slotPasswordSet()));
|
||||
connect(share.data(), SIGNAL(passwordSetError(int, QString)), SLOT(slotPasswordSetError(int, QString)));
|
||||
connect(share.data(), &Share::permissionsSet, this, &ShareLinkWidget::slotPermissionsSet);
|
||||
connect(linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireSet);
|
||||
connect(linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
|
||||
connect(linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
|
||||
|
||||
// Build the table row
|
||||
auto row = table->rowCount();
|
||||
|
||||
@@ -289,7 +289,7 @@ void ShareManager::slotLinkShareCreated(const QJsonDocument &reply)
|
||||
void ShareManager::createShare(const QString &path,
|
||||
const Share::ShareType shareType,
|
||||
const QString shareWith,
|
||||
const Share::Permissions permissions)
|
||||
const Share::Permissions desiredPermissions)
|
||||
{
|
||||
auto job = new OcsShareJob(_account);
|
||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||
@@ -305,17 +305,18 @@ void ShareManager::createShare(const QString &path,
|
||||
|
||||
// Limit the permissions we request for a share to the ones the item
|
||||
// was shared with initially.
|
||||
auto perm = permissions;
|
||||
if (permissions == SharePermissionDefault) {
|
||||
perm = existingPermissions;
|
||||
} else if (existingPermissions != SharePermissionDefault) {
|
||||
perm &= existingPermissions;
|
||||
auto validPermissions = desiredPermissions;
|
||||
if (validPermissions == SharePermissionDefault) {
|
||||
validPermissions = existingPermissions;
|
||||
}
|
||||
if (existingPermissions != SharePermissionDefault) {
|
||||
validPermissions &= existingPermissions;
|
||||
}
|
||||
|
||||
OcsShareJob *job = new OcsShareJob(_account);
|
||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotShareCreated);
|
||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||
job->createShare(path, shareType, shareWith, permissions);
|
||||
job->createShare(path, shareType, shareWith, validPermissions);
|
||||
});
|
||||
job->getSharedWithMe();
|
||||
}
|
||||
|
||||
@@ -183,6 +183,10 @@ SocketApi::SocketApi(QObject *parent)
|
||||
// Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
|
||||
// Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
|
||||
socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".socketApi";
|
||||
#ifdef Q_OS_MAC
|
||||
// Tell Finder to use the Extension (checking it from System Preferences -> Extensions)
|
||||
system("pluginkit -e use -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
|
||||
#endif
|
||||
} else if (Utility::isLinux() || Utility::isBSD()) {
|
||||
QString runtimeDir;
|
||||
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||
@@ -220,6 +224,11 @@ SocketApi::~SocketApi()
|
||||
// All remaining sockets will be destroyed with _localServer, their parent
|
||||
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
|
||||
_listeners.clear();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Unload the extension (uncheck from System Preferences -> Extensions)
|
||||
system("pluginkit -e ignore -i " APPLICATION_REV_DOMAIN ".FinderSyncExt &");
|
||||
#endif
|
||||
}
|
||||
|
||||
void SocketApi::slotNewConnection()
|
||||
|
||||
@@ -204,6 +204,10 @@ void SslButton::slotUpdateMenu()
|
||||
|
||||
AccountPtr account = _accountState->account();
|
||||
|
||||
if (account->isHttp2Supported()) {
|
||||
_menu->addAction("HTTP/2")->setEnabled(false);
|
||||
}
|
||||
|
||||
if (account->url().scheme() == QLatin1String("https")) {
|
||||
QString sslVersion = account->_sessionCipher.protocolString()
|
||||
+ ", " + account->_sessionCipher.authenticationMethod()
|
||||
|
||||
@@ -92,6 +92,11 @@ OCUpdater::OCUpdater(const QUrl &url)
|
||||
{
|
||||
}
|
||||
|
||||
void OCUpdater::setUpdateUrl(const QUrl &url)
|
||||
{
|
||||
_updateUrl = url;
|
||||
}
|
||||
|
||||
bool OCUpdater::performUpdate()
|
||||
{
|
||||
ConfigFile cfg;
|
||||
@@ -179,6 +184,81 @@ void OCUpdater::setDownloadState(DownloadState state)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
// Following functions are taken from https://github.com/qt/qtbase/blob/5.8/src/corelib/io/qprocess_win.cpp
|
||||
// to make use of this fix https://github.com/qt/qtbase/commit/bec2fc19fd18768b16925597871c77a61e716abd
|
||||
// for QTBUG-53833: Without this we get an ugly powershell window on update. In 2.5/master we use Qt 5.10
|
||||
// which obviously already has the fix.
|
||||
static QString qt_create_commandline(const QString &program, const QStringList &arguments)
|
||||
{
|
||||
QString args;
|
||||
if (!program.isEmpty()) {
|
||||
QString programName = program;
|
||||
if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' ')))
|
||||
programName = QLatin1Char('\"') + programName + QLatin1Char('\"');
|
||||
programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
|
||||
|
||||
// add the prgram as the first arg ... it works better
|
||||
args = programName + QLatin1Char(' ');
|
||||
}
|
||||
|
||||
for (int i=0; i<arguments.size(); ++i) {
|
||||
QString tmp = arguments.at(i);
|
||||
// Quotes are escaped and their preceding backslashes are doubled.
|
||||
tmp.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\\""));
|
||||
if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
|
||||
// The argument must not end with a \ since this would be interpreted
|
||||
// as escaping the quote -- rather put the \ behind the quote: e.g.
|
||||
// rather use "foo"\ than "foo\"
|
||||
int i = tmp.length();
|
||||
while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\'))
|
||||
--i;
|
||||
tmp.insert(i, QLatin1Char('"'));
|
||||
tmp.prepend(QLatin1Char('"'));
|
||||
}
|
||||
args += QLatin1Char(' ') + tmp;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDir = QString(), qint64 *pid = 0)
|
||||
{
|
||||
// static const DWORD errorElevationRequired = 740;
|
||||
|
||||
QString args = qt_create_commandline(program, arguments);
|
||||
bool success = false;
|
||||
PROCESS_INFORMATION pinfo;
|
||||
|
||||
DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
|
||||
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
|
||||
STARTUPINFOW startupInfo = { sizeof( STARTUPINFO ), 0, 0, 0,
|
||||
(ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
|
||||
(ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
};
|
||||
success = CreateProcess(0, (wchar_t*)args.utf16(),
|
||||
0, 0, FALSE, dwCreationFlags, 0,
|
||||
workingDir.isEmpty() ? 0 : (wchar_t*)workingDir.utf16(),
|
||||
&startupInfo, &pinfo);
|
||||
|
||||
// if (success) {
|
||||
CloseHandle(pinfo.hThread);
|
||||
CloseHandle(pinfo.hProcess);
|
||||
if (pid)
|
||||
*pid = pinfo.dwProcessId;
|
||||
// } else if (GetLastError() == errorElevationRequired) {
|
||||
// success = startDetachedUacPrompt(program, arguments, workingDir, pid);
|
||||
// }
|
||||
|
||||
return success;
|
||||
}
|
||||
#else
|
||||
bool startDetached(const QString &program, const QStringList &arguments, const QString &workingDir = QString(), qint64 *pid = 0)
|
||||
{
|
||||
return QProcess::startDetached(program, arguments, workingDir, pid);
|
||||
}
|
||||
#endif
|
||||
|
||||
void OCUpdater::slotStartInstaller()
|
||||
{
|
||||
ConfigFile cfg;
|
||||
@@ -187,8 +267,30 @@ void OCUpdater::slotStartInstaller()
|
||||
settings.setValue(autoUpdateAttemptedC, true);
|
||||
settings.sync();
|
||||
qCInfo(lcUpdater) << "Running updater" << updateFile;
|
||||
QProcess::startDetached(updateFile, QStringList() << "/S"
|
||||
<< "/launch");
|
||||
|
||||
if(updateFile.endsWith(".exe")) {
|
||||
QProcess::startDetached(updateFile, QStringList() << "/S"
|
||||
<< "/launch");
|
||||
} else if(updateFile.endsWith(".msi")) {
|
||||
// When MSIs are installed without gui they cannot launch applications
|
||||
// as they lack the user context. That is why we need to run the client
|
||||
// manually here. We wrap the msiexec and client invocation in a powershell
|
||||
// script because owncloud.exe will be shut down for installation.
|
||||
// | Out-Null forces powershell to wait for msiexec to finish.
|
||||
auto preparePathForPowershell = [](QString path) {
|
||||
path.replace("'", "''");
|
||||
|
||||
return QDir::toNativeSeparators(path);
|
||||
};
|
||||
|
||||
auto msiLogFile = cfg.configPath() + "msi.log";
|
||||
QString command = QString("&{msiexec /norestart /passive /i '%1' /L*V '%2'| Out-Null ; &'%3'}")
|
||||
.arg(preparePathForPowershell(updateFile))
|
||||
.arg(preparePathForPowershell(msiLogFile))
|
||||
.arg(preparePathForPowershell(QCoreApplication::applicationFilePath()));
|
||||
|
||||
startDetached("powershell.exe", QStringList{"-Command", command});
|
||||
}
|
||||
}
|
||||
|
||||
void OCUpdater::checkForUpdate()
|
||||
@@ -298,7 +400,7 @@ void NSISUpdater::versionInfoArrived(const UpdateInfo &info)
|
||||
showDialog(info);
|
||||
}
|
||||
if (!url.isEmpty()) {
|
||||
_targetFile = cfg.configPath() + url.mid(url.lastIndexOf('/'));
|
||||
_targetFile = cfg.configPath() + url.mid(url.lastIndexOf('/')+1);
|
||||
if (QFile(_targetFile).exists()) {
|
||||
setDownloadState(DownloadComplete);
|
||||
} else {
|
||||
|
||||
@@ -99,6 +99,8 @@ public:
|
||||
UpdateOnlyAvailableThroughSystem };
|
||||
explicit OCUpdater(const QUrl &url);
|
||||
|
||||
void setUpdateUrl(const QUrl &url);
|
||||
|
||||
bool performUpdate();
|
||||
|
||||
void checkForUpdate() Q_DECL_OVERRIDE;
|
||||
|
||||
@@ -23,15 +23,20 @@ namespace OCC {
|
||||
|
||||
class SparkleUpdater : public Updater
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SparkleUpdater(const QString &appCastUrl);
|
||||
SparkleUpdater(const QUrl &appCastUrl);
|
||||
~SparkleUpdater();
|
||||
|
||||
void setUpdateUrl(const QUrl &url);
|
||||
|
||||
// unused in this updater
|
||||
void checkForUpdate() Q_DECL_OVERRIDE;
|
||||
void backgroundCheckForUpdate() Q_DECL_OVERRIDE;
|
||||
bool handleStartup() Q_DECL_OVERRIDE { return false; }
|
||||
|
||||
QString statusString();
|
||||
|
||||
private:
|
||||
class Private;
|
||||
Private *d;
|
||||
|
||||
@@ -73,7 +73,7 @@ class SparkleUpdater::Private
|
||||
};
|
||||
|
||||
// Delete ~/Library//Preferences/com.owncloud.desktopclient.plist to re-test
|
||||
SparkleUpdater::SparkleUpdater(const QString& appCastUrl)
|
||||
SparkleUpdater::SparkleUpdater(const QUrl& appCastUrl)
|
||||
: Updater()
|
||||
{
|
||||
d = new Private;
|
||||
@@ -89,9 +89,7 @@ SparkleUpdater::SparkleUpdater(const QString& appCastUrl)
|
||||
[d->updater resetUpdateCycle];
|
||||
[d->updater retain];
|
||||
|
||||
NSURL* url = [NSURL URLWithString:
|
||||
[NSString stringWithUTF8String: appCastUrl.toUtf8().data()]];
|
||||
[d->updater setFeedURL: url];
|
||||
setUpdateUrl(appCastUrl);
|
||||
|
||||
// Sparkle 1.8 required
|
||||
NSString *userAgent = [NSString stringWithUTF8String: Utility::userAgentString().data()];
|
||||
@@ -104,7 +102,14 @@ SparkleUpdater::~SparkleUpdater()
|
||||
delete d;
|
||||
}
|
||||
|
||||
void SparkleUpdater::setUpdateUrl(const QUrl &url)
|
||||
{
|
||||
NSURL* nsurl = [NSURL URLWithString:
|
||||
[NSString stringWithUTF8String: url.toString().toUtf8().data()]];
|
||||
[d->updater setFeedURL: nsurl];
|
||||
}
|
||||
|
||||
// FIXME: Should be changed to not instanicate the SparkleUpdater at all in this case
|
||||
bool autoUpdaterAllowed()
|
||||
{
|
||||
// See https://github.com/owncloud/client/issues/2931
|
||||
@@ -133,4 +138,10 @@ void SparkleUpdater::backgroundCheckForUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
QString SparkleUpdater::statusString()
|
||||
{
|
||||
// FIXME Show the real state depending on the callbacks
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "theme.h"
|
||||
#include "common/utility.h"
|
||||
#include "version.h"
|
||||
#include "configfile.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -39,6 +40,29 @@ Updater *Updater::instance()
|
||||
return _instance;
|
||||
}
|
||||
|
||||
QUrl Updater::updateUrl()
|
||||
{
|
||||
QUrl updateBaseUrl(QString::fromLocal8Bit(qgetenv("OCC_UPDATE_URL")));
|
||||
if (updateBaseUrl.isEmpty()) {
|
||||
updateBaseUrl = QUrl(QLatin1String(APPLICATION_UPDATE_URL));
|
||||
}
|
||||
if (!updateBaseUrl.isValid() || updateBaseUrl.host() == ".") {
|
||||
return QUrl();
|
||||
}
|
||||
|
||||
auto url = addQueryParams(updateBaseUrl);
|
||||
|
||||
#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE)
|
||||
url.addQueryItem(QLatin1String("sparkle"), QLatin1String("true"));
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
url.addQueryItem(QLatin1String("msi"), QLatin1String("true"));
|
||||
#endif
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
QUrl Updater::addQueryParams(const QUrl &url)
|
||||
{
|
||||
QUrl paramUrl = url;
|
||||
@@ -64,13 +88,10 @@ QUrl Updater::addQueryParams(const QUrl &url)
|
||||
|
||||
QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX));
|
||||
paramUrl.addQueryItem(QLatin1String("versionsuffix"), suffix);
|
||||
if (suffix.startsWith("nightly")
|
||||
|| suffix.startsWith("alpha")
|
||||
|| suffix.startsWith("rc")
|
||||
|| suffix.startsWith("beta")) {
|
||||
paramUrl.addQueryItem(QLatin1String("channel"), "beta");
|
||||
// FIXME: Provide a checkbox in UI to enable regular versions to switch
|
||||
// to beta channel
|
||||
|
||||
auto channel = ConfigFile().updateChannel();
|
||||
if (channel != "stable") {
|
||||
paramUrl.addQueryItem(QLatin1String("channel"), channel);
|
||||
}
|
||||
|
||||
return paramUrl;
|
||||
@@ -97,23 +118,18 @@ QString Updater::getSystemInfo()
|
||||
// To test, cmake with -DAPPLICATION_UPDATE_URL="http://127.0.0.1:8080/test.rss"
|
||||
Updater *Updater::create()
|
||||
{
|
||||
QUrl updateBaseUrl(QString::fromLocal8Bit(qgetenv("OCC_UPDATE_URL")));
|
||||
if (updateBaseUrl.isEmpty()) {
|
||||
updateBaseUrl = QUrl(QLatin1String(APPLICATION_UPDATE_URL));
|
||||
}
|
||||
if (!updateBaseUrl.isValid() || updateBaseUrl.host() == ".") {
|
||||
auto url = updateUrl();
|
||||
if (url.isEmpty()) {
|
||||
qCWarning(lcUpdater) << "Not a valid updater URL, will not do update check";
|
||||
return 0;
|
||||
}
|
||||
updateBaseUrl = addQueryParams(updateBaseUrl);
|
||||
#if defined(Q_OS_MAC) && defined(HAVE_SPARKLE)
|
||||
updateBaseUrl.addQueryItem(QLatin1String("sparkle"), QLatin1String("true"));
|
||||
return new SparkleUpdater(updateBaseUrl.toString());
|
||||
return new SparkleUpdater(url);
|
||||
#elif defined(Q_OS_WIN32)
|
||||
// the best we can do is notify about updates
|
||||
return new NSISUpdater(updateBaseUrl);
|
||||
return new NSISUpdater(url);
|
||||
#else
|
||||
return new PassiveUpdateNotifier(QUrl(updateBaseUrl));
|
||||
return new PassiveUpdateNotifier(url);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
};
|
||||
|
||||
static Updater *instance();
|
||||
static QUrl updateUrl();
|
||||
|
||||
virtual void checkForUpdate() = 0;
|
||||
virtual void backgroundCheckForUpdate() = 0;
|
||||
|
||||
@@ -93,9 +93,8 @@ QNetworkReply *AccessManager::createRequest(QNetworkAccessManager::Operation op,
|
||||
qInfo(lcAccessManager) << op << verb << newRequest.url().toString() << "has X-Request-ID" << requestId;
|
||||
newRequest.setRawHeader("X-Request-ID", requestId);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
// only enable HTTP2 with Qt 5.9 because Qt 5.8.0 has too many bugs
|
||||
// (only use one connection if the server does not support HTTP2)
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 4)
|
||||
// only enable HTTP2 with Qt 5.9.4 because old Qt have too many bugs (e.g. QTBUG-64359 is fixed in >= Qt 5.9.4)
|
||||
if (newRequest.url().scheme() == "https") { // Not for "http": QTBUG-61397
|
||||
newRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ void Account::setDavUser(const QString &newDavUser)
|
||||
_davUser = newDavUser;
|
||||
}
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
QImage Account::avatar() const
|
||||
{
|
||||
return _avatarImg;
|
||||
@@ -102,6 +103,7 @@ void Account::setAvatar(const QImage &img)
|
||||
_avatarImg = img;
|
||||
emit accountChangedAvatar();
|
||||
}
|
||||
#endif
|
||||
|
||||
QString Account::displayName() const
|
||||
{
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
#include <QSslCipher>
|
||||
#include <QSslError>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
#include <QPixmap>
|
||||
#endif
|
||||
|
||||
#include "common/utility.h"
|
||||
#include <memory>
|
||||
@@ -86,8 +89,10 @@ public:
|
||||
QString davDisplayName() const;
|
||||
void setDavDisplayName(const QString &newDisplayName);
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
QImage avatar() const;
|
||||
void setAvatar(const QImage &img);
|
||||
#endif
|
||||
|
||||
/// The name of the account as shown in the toolbar
|
||||
QString displayName() const;
|
||||
@@ -213,7 +218,7 @@ public:
|
||||
/** Detects a specific bug in older server versions */
|
||||
bool rootEtagChangesNotOnlySubFolderEtags();
|
||||
|
||||
/** True when the server supports HTTP2 */
|
||||
/** True when the server connection is using HTTP2 */
|
||||
bool isHttp2Supported() { return _http2Supported; }
|
||||
void setHttp2Supported(bool value) { _http2Supported = value; }
|
||||
|
||||
@@ -266,7 +271,9 @@ private:
|
||||
QString _id;
|
||||
QString _davUser;
|
||||
QString _displayName;
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
QImage _avatarImg;
|
||||
#endif
|
||||
QMap<QString, QVariant> _settingsMap;
|
||||
QUrl _url;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "theme.h"
|
||||
#include "common/utility.h"
|
||||
#include "common/asserts.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
||||
@@ -52,6 +53,7 @@ static const char crashReporterC[] = "crashReporter";
|
||||
static const char optionalDesktopNoficationsC[] = "optionalDesktopNotifications";
|
||||
static const char skipUpdateCheckC[] = "skipUpdateCheck";
|
||||
static const char updateCheckIntervalC[] = "updateCheckInterval";
|
||||
static const char updateChannelC[] = "updateChannel";
|
||||
static const char geometryC[] = "geometry";
|
||||
static const char timeoutC[] = "timeout";
|
||||
static const char chunkSizeC[] = "chunkSize";
|
||||
@@ -468,6 +470,28 @@ void ConfigFile::setSkipUpdateCheck(bool skip, const QString &connection)
|
||||
settings.sync();
|
||||
}
|
||||
|
||||
QString ConfigFile::updateChannel() const
|
||||
{
|
||||
QString defaultUpdateChannel = QStringLiteral("stable");
|
||||
QString suffix = QString::fromLatin1(MIRALL_STRINGIFY(MIRALL_VERSION_SUFFIX));
|
||||
if (suffix.startsWith("daily")
|
||||
|| suffix.startsWith("nightly")
|
||||
|| suffix.startsWith("alpha")
|
||||
|| suffix.startsWith("rc")
|
||||
|| suffix.startsWith("beta")) {
|
||||
defaultUpdateChannel = QStringLiteral("beta");
|
||||
}
|
||||
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
return settings.value(QLatin1String(updateChannelC), defaultUpdateChannel).toString();
|
||||
}
|
||||
|
||||
void ConfigFile::setUpdateChannel(const QString &channel)
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
settings.setValue(QLatin1String(updateChannelC), channel);
|
||||
}
|
||||
|
||||
int ConfigFile::maxLogLines() const
|
||||
{
|
||||
QSettings settings(configFile(), QSettings::IniFormat);
|
||||
|
||||
@@ -131,6 +131,9 @@ public:
|
||||
bool skipUpdateCheck(const QString &connection = QString()) const;
|
||||
void setSkipUpdateCheck(bool, const QString &);
|
||||
|
||||
QString updateChannel() const;
|
||||
void setUpdateChannel(const QString &channel);
|
||||
|
||||
void saveGeometryHeader(QHeaderView *header);
|
||||
void restoreGeometryHeader(QHeaderView *header);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <QLoggingCategory>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QPixmap>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "connectionvalidator.h"
|
||||
@@ -318,6 +317,7 @@ bool ConnectionValidator::setAndCheckServerVersion(const QString &version)
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
// Record that the server supports HTTP/2
|
||||
// Actual decision if we should use HTTP/2 is done in AccessManager::createRequest
|
||||
if (auto job = qobject_cast<AbstractNetworkJob *>(sender())) {
|
||||
if (auto reply = job->reply()) {
|
||||
_account->setHttp2Supported(
|
||||
@@ -333,24 +333,28 @@ void ConnectionValidator::slotUserFetched(const QJsonDocument &json)
|
||||
QString user = json.object().value("ocs").toObject().value("data").toObject().value("id").toString();
|
||||
if (!user.isEmpty()) {
|
||||
_account->setDavUser(user);
|
||||
|
||||
AvatarJob *job = new AvatarJob(_account, this);
|
||||
job->setTimeout(20 * 1000);
|
||||
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
|
||||
|
||||
job->start();
|
||||
}
|
||||
QString displayName = json.object().value("ocs").toObject().value("data").toObject().value("display-name").toString();
|
||||
if (!displayName.isEmpty()) {
|
||||
_account->setDavDisplayName(displayName);
|
||||
}
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
AvatarJob *job = new AvatarJob(_account, this);
|
||||
job->setTimeout(20 * 1000);
|
||||
QObject::connect(job, &AvatarJob::avatarPixmap, this, &ConnectionValidator::slotAvatarImage);
|
||||
job->start();
|
||||
#else
|
||||
reportResult(Connected);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
void ConnectionValidator::slotAvatarImage(const QImage &img)
|
||||
{
|
||||
_account->setAvatar(img);
|
||||
reportResult(Connected);
|
||||
}
|
||||
#endif
|
||||
|
||||
void ConnectionValidator::reportResult(Status status)
|
||||
{
|
||||
|
||||
@@ -123,7 +123,9 @@ protected slots:
|
||||
|
||||
void slotCapabilitiesRecieved(const QJsonDocument &);
|
||||
void slotUserFetched(const QJsonDocument &);
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
void slotAvatarImage(const QImage &img);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void reportResult(Status status);
|
||||
|
||||
@@ -117,6 +117,7 @@ HttpCredentials::HttpCredentials(const QString &user, const QString &password, c
|
||||
, _clientSslKey(key)
|
||||
, _clientSslCertificate(certificate)
|
||||
, _keychainMigration(false)
|
||||
, _retryOnKeyChainError(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -219,6 +220,21 @@ void HttpCredentials::deleteOldKeychainEntries()
|
||||
|
||||
void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
Q_ASSERT(!incoming->insecureFallback()); // If insecureFallback is set, the next test would be pointless
|
||||
if (_retryOnKeyChainError && (incoming->error() == QKeychain::NoBackendAvailable
|
||||
|| incoming->error() == QKeychain::OtherError)) {
|
||||
// Could be that the backend was not yet available. Wait some extra seconds.
|
||||
// (Issues #4274 and #6522)
|
||||
// (For kwallet, the error is OtherError instead of NoBackendAvailable, maybe a bug in QtKeychain)
|
||||
qCInfo(lcHttpCredentials) << "Backend unavailable (yet?) Retrying in a few seconds." << incoming->errorString();
|
||||
QTimer::singleShot(10000, this, &HttpCredentials::fetchFromKeychainHelper);
|
||||
_retryOnKeyChainError = false;
|
||||
return;
|
||||
}
|
||||
_retryOnKeyChainError = false;
|
||||
#endif
|
||||
|
||||
// Store PEM in memory
|
||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incoming);
|
||||
if (readJob->error() == NoError && readJob->binaryData().length() > 0) {
|
||||
|
||||
@@ -141,6 +141,7 @@ protected:
|
||||
QSslKey _clientSslKey;
|
||||
QSslCertificate _clientSslCertificate;
|
||||
bool _keychainMigration;
|
||||
bool _retryOnKeyChainError = true; // true if we haven't done yet any reading from keychain
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ QString TokenCredentials::password() const
|
||||
return _password;
|
||||
}
|
||||
|
||||
QNetworkAccessManager *TokenCredentials::getQNAM() const
|
||||
QNetworkAccessManager *TokenCredentials::createQNAM() const
|
||||
{
|
||||
AccessManager *qnam = new TokenCredentialsAccessManager(this);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
TokenCredentials(const QString &user, const QString &password, const QString &token);
|
||||
|
||||
QString authType() const Q_DECL_OVERRIDE;
|
||||
QNetworkAccessManager *getQNAM() const Q_DECL_OVERRIDE;
|
||||
QNetworkAccessManager *createQNAM() const Q_DECL_OVERRIDE;
|
||||
bool ready() const Q_DECL_OVERRIDE;
|
||||
void askFromUser() Q_DECL_OVERRIDE;
|
||||
void fetchFromKeychain() Q_DECL_OVERRIDE;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "account.h"
|
||||
#include "theme.h"
|
||||
#include "common/asserts.h"
|
||||
#include "common/checksums.h"
|
||||
|
||||
#include <csync_private.h>
|
||||
#include <csync_rename.h>
|
||||
@@ -74,7 +75,7 @@ bool DiscoveryJob::isInSelectiveSyncBlackList(const QByteArray &path) const
|
||||
|
||||
// Also try to adjust the path if there was renames
|
||||
if (csync_rename_count(_csync_ctx)) {
|
||||
QByteArray adjusted = csync_rename_adjust_path_source(_csync_ctx, path);
|
||||
QByteArray adjusted = csync_rename_adjust_parent_path_source(_csync_ctx, path);
|
||||
if (adjusted != path) {
|
||||
return findPathInList(_selectiveSyncBlackList, QString::fromUtf8(adjusted));
|
||||
}
|
||||
@@ -300,28 +301,6 @@ void DiscoverySingleDirectoryJob::abort()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest-quality checksum in a 'checksums'
|
||||
* property retrieved from the server.
|
||||
*
|
||||
* Example: "ADLER32:1231 SHA1:ab124124 MD5:2131affa21"
|
||||
* -> "SHA1:ab124124"
|
||||
*/
|
||||
static QByteArray findBestChecksum(const QByteArray &checksums)
|
||||
{
|
||||
int i = 0;
|
||||
// The order of the searches here defines the preference ordering.
|
||||
if (-1 != (i = checksums.indexOf("SHA1:"))
|
||||
|| -1 != (i = checksums.indexOf("MD5:"))
|
||||
|| -1 != (i = checksums.indexOf("Adler32:"))) {
|
||||
// Now i is the start of the best checksum
|
||||
// Grab it until the next space or end of string.
|
||||
auto checksum = checksums.mid(i);
|
||||
return checksum.mid(0, checksum.indexOf(" "));
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
static std::unique_ptr<csync_file_stat_t> propertyMapToFileStat(const QMap<QString, QString> &map)
|
||||
{
|
||||
std::unique_ptr<csync_file_stat_t> file_stat(new csync_file_stat_t);
|
||||
|
||||
@@ -219,7 +219,7 @@ void Logger::setLogFlush(bool flush)
|
||||
|
||||
void Logger::setLogDebug(bool debug)
|
||||
{
|
||||
QLoggingCategory::setFilterRules(debug ? QStringLiteral("qt.*=true\n*.debug=true") : QString());
|
||||
QLoggingCategory::setFilterRules(debug ? QStringLiteral("sync.*.debug=true\ngui.*.debug=true") : QString());
|
||||
_logDebug = debug;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <QCoreApplication>
|
||||
#include <QPixmap>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -626,10 +625,15 @@ bool PropfindJob::finished()
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
AvatarJob::AvatarJob(AccountPtr account, QObject *parent)
|
||||
: AbstractNetworkJob(account, QString(), parent)
|
||||
{
|
||||
_avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/128.png").arg(account->davUser()));
|
||||
if (account->serverVersionInt() >= Account::makeServerVersion(10, 0, 0)) {
|
||||
_avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/128.png").arg(account->davUser()));
|
||||
} else {
|
||||
_avatarUrl = Utility::concatUrlPath(account->url(), QString("index.php/avatar/%1/128").arg(account->davUser()));
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarJob::start()
|
||||
@@ -656,6 +660,7 @@ bool AvatarJob::finished()
|
||||
emit(avatarPixmap(avImage));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*********************************************************************************************/
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ private:
|
||||
QList<QByteArray> _properties;
|
||||
};
|
||||
|
||||
|
||||
#ifndef TOKEN_AUTH_ONLY
|
||||
/**
|
||||
* @brief The AvatarJob class
|
||||
*
|
||||
@@ -155,7 +155,7 @@ signals:
|
||||
* @brief avatarPixmap - returns either a valid pixmap or not.
|
||||
*/
|
||||
|
||||
void avatarPixmap(QImage);
|
||||
void avatarPixmap(const QImage &);
|
||||
|
||||
private slots:
|
||||
virtual bool finished() Q_DECL_OVERRIDE;
|
||||
@@ -163,6 +163,7 @@ private slots:
|
||||
private:
|
||||
QUrl _avatarUrl;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Send a Proppatch request
|
||||
|
||||
@@ -579,8 +579,7 @@ bool OwncloudPropagator::localFileNameClash(const QString &relFile)
|
||||
re = false;
|
||||
qCWarning(lcPropagator) << "No valid fileinfo";
|
||||
} else {
|
||||
// Need to normalize to composited form because of
|
||||
// https://bugreports.qt-project.org/browse/QTBUG-39622
|
||||
// Need to normalize to composited form because of QTBUG-39622/QTBUG-55896
|
||||
const QString cName = fileInfo.canonicalFilePath().normalized(QString::NormalizationForm_C);
|
||||
bool equal = (file == cName);
|
||||
re = (!equal && !cName.endsWith(relFile, Qt::CaseSensitive));
|
||||
@@ -821,10 +820,13 @@ void PropagatorCompositeJob::slotSubJobFinished(SyncFileItem::Status status)
|
||||
ASSERT(i >= 0);
|
||||
_runningJobs.remove(i);
|
||||
|
||||
// Any sub job error will cause the whole composite to fail. This is important
|
||||
// for knowing whether to update the etag in PropagateDirectory, for example.
|
||||
if (status == SyncFileItem::FatalError
|
||||
|| status == SyncFileItem::NormalError
|
||||
|| status == SyncFileItem::SoftError
|
||||
|| status == SyncFileItem::DetailError) {
|
||||
|| status == SyncFileItem::DetailError
|
||||
|| status == SyncFileItem::BlacklistedError) {
|
||||
_hasError = status;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,8 +210,9 @@ public:
|
||||
|
||||
virtual ~PropagatorCompositeJob()
|
||||
{
|
||||
qDeleteAll(_jobsToDo);
|
||||
qDeleteAll(_runningJobs);
|
||||
// Don't delete jobs in _jobsToDo and _runningJobs: they have parents
|
||||
// that will be responsible for cleanup. Deleting them here would risk
|
||||
// deleting something that has already been deleted by a shared parent.
|
||||
}
|
||||
|
||||
void appendJob(PropagatorJob *job)
|
||||
|
||||
@@ -647,7 +647,7 @@ void PropagateDownloadFile::slotGetFinished()
|
||||
this, &PropagateDownloadFile::transmissionChecksumValidated);
|
||||
connect(validator, &ValidateChecksumHeader::validationFailed,
|
||||
this, &PropagateDownloadFile::slotChecksumFail);
|
||||
auto checksumHeader = job->reply()->rawHeader(checkSumHeaderC);
|
||||
auto checksumHeader = findBestChecksum(job->reply()->rawHeader(checkSumHeaderC));
|
||||
auto contentMd5Header = job->reply()->rawHeader(contentMd5HeaderC);
|
||||
if (checksumHeader.isEmpty() && !contentMd5Header.isEmpty())
|
||||
checksumHeader = "MD5:" + contentMd5Header;
|
||||
|
||||
@@ -553,6 +553,16 @@ void PropagateUploadFileCommon::commonErrorHandling(AbstractNetworkJob *job)
|
||||
abortWithError(status, errorString);
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::adjustLastJobTimeout(AbstractNetworkJob *job, quint64 fileSize)
|
||||
{
|
||||
job->setTimeout(qBound(
|
||||
job->timeoutMsec(),
|
||||
// Calculate 3 minutes for each gigabyte of data
|
||||
qint64((3 * 60 * 1000) * fileSize / 1e9),
|
||||
// Maximum of 30 minutes
|
||||
qint64(30 * 60 * 1000)));
|
||||
}
|
||||
|
||||
void PropagateUploadFileCommon::slotJobDestroyed(QObject *job)
|
||||
{
|
||||
_jobs.erase(std::remove(_jobs.begin(), _jobs.end(), job), _jobs.end());
|
||||
|
||||
@@ -281,6 +281,17 @@ protected:
|
||||
*/
|
||||
void commonErrorHandling(AbstractNetworkJob *job);
|
||||
|
||||
/**
|
||||
* Increases the timeout for the final MOVE/PUT for large files.
|
||||
*
|
||||
* This is an unfortunate workaround since the drawback is not being able to
|
||||
* detect real disconnects in a timely manner. Shall go away when the server
|
||||
* response starts coming quicker, or there is some sort of async api.
|
||||
*
|
||||
* See #6527, enterprise#2480
|
||||
*/
|
||||
static void adjustLastJobTimeout(AbstractNetworkJob *job, quint64 fileSize);
|
||||
|
||||
// Bases headers that need to be sent with every chunk
|
||||
QMap<QByteArray, QByteArray> headers();
|
||||
};
|
||||
|
||||
@@ -295,6 +295,7 @@ void PropagateUploadFileNG::startNextChunk()
|
||||
connect(job, &MoveJob::finishedSignal, this, &PropagateUploadFileNG::slotMoveJobFinished);
|
||||
connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
|
||||
propagator()->_activeJobList.append(this);
|
||||
adjustLastJobTimeout(job, fileSize);
|
||||
job->start();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,8 @@ void PropagateUploadFileV1::startNextChunk()
|
||||
connect(job, &PUTFileJob::uploadProgress, this, &PropagateUploadFileV1::slotUploadProgress);
|
||||
connect(job, &PUTFileJob::uploadProgress, device, &UploadDevice::slotJobUploadProgress);
|
||||
connect(job, &QObject::destroyed, this, &PropagateUploadFileCommon::slotJobDestroyed);
|
||||
if (isFinalChunk)
|
||||
adjustLastJobTimeout(job, fileSize);
|
||||
job->start();
|
||||
propagator()->_activeJobList.append(this);
|
||||
_currentChunk++;
|
||||
@@ -301,6 +303,7 @@ void PropagateUploadFileV1::slotPutFinished()
|
||||
qCWarning(lcPropagateUpload) << "Server does not support X-OC-MTime" << job->reply()->rawHeader("X-OC-MTime");
|
||||
// Well, the mtime was not set
|
||||
done(SyncFileItem::SoftError, "Server does not support X-OC-MTime");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WITH_TESTING
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user