mirror of
https://github.com/chylex/Nextcloud-Desktop.git
synced 2026-04-05 05:34:18 +02:00
Compare commits
334 Commits
v2.4.0-alp
...
v2.4.3-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dca63dc539 | ||
|
|
b5384c9ad7 | ||
|
|
8c77bfad93 | ||
|
|
235e314da7 | ||
|
|
628daeadfe | ||
|
|
2a55b20b8d | ||
|
|
6c818ac3c7 | ||
|
|
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 | ||
|
|
2192386d86 | ||
|
|
e694ffcd03 | ||
|
|
eff401d418 | ||
|
|
7049ccd7ab | ||
|
|
8fb2167fca | ||
|
|
d0b8528022 | ||
|
|
13d80beb83 | ||
|
|
06ce8dd8bd | ||
|
|
9a1ea67a35 | ||
|
|
e616f904b6 | ||
|
|
bff24ffd2f | ||
|
|
2cdc969cc5 | ||
|
|
b13fd786b6 | ||
|
|
89d55d0af8 | ||
|
|
d46335aba1 | ||
|
|
e417a4b159 | ||
|
|
b0f986c740 | ||
|
|
18091f99d4 | ||
|
|
3ae2071129 | ||
|
|
820ebf4b6e | ||
|
|
161f6baba6 | ||
|
|
89d7dbf2cf | ||
|
|
b1972e56fa | ||
|
|
7d10f9829e | ||
|
|
d2723bc0ac | ||
|
|
a0d43f8ba6 | ||
|
|
37c5612aee | ||
|
|
f2e73c0887 | ||
|
|
aca36cc1ac | ||
|
|
9af6e29f42 | ||
|
|
d6db77cce8 | ||
|
|
71d2e3de74 | ||
|
|
a7e07239e3 | ||
|
|
3608114ec1 | ||
|
|
c9d5a9cea2 | ||
|
|
354cdfdbc1 | ||
|
|
4dbe9d4113 | ||
|
|
7963a8322d | ||
|
|
6ac44f05cd | ||
|
|
64a84fda38 | ||
|
|
8eec79680c | ||
|
|
0968d74dad | ||
|
|
876a1c5140 | ||
|
|
1455636169 | ||
|
|
7ea9a5ca4e | ||
|
|
2d2ec2a576 | ||
|
|
e4612ca5d4 | ||
|
|
15af5878b6 | ||
|
|
c36043a175 | ||
|
|
ee63b36ed3 | ||
|
|
35d28294cd | ||
|
|
c6bd3ab31a | ||
|
|
9c7ee6ef85 | ||
|
|
f3ea375083 | ||
|
|
15b02547e8 | ||
|
|
13b1f8b33e | ||
|
|
ccd32c04a9 | ||
|
|
df19b20d07 | ||
|
|
f41c9fbb7f | ||
|
|
a0e50670de | ||
|
|
984631d807 | ||
|
|
a9761a8976 | ||
|
|
84d8624e03 | ||
|
|
9866010b4c | ||
|
|
a3c1052cae | ||
|
|
726cbc160c | ||
|
|
bf39343920 | ||
|
|
af24b4132d | ||
|
|
d339b68715 | ||
|
|
eadc791795 | ||
|
|
408cf11b2b | ||
|
|
e3ba8d3209 | ||
|
|
17b1c83ae5 | ||
|
|
b2a8ffc577 | ||
|
|
e2711224ed | ||
|
|
2ac7e0200a | ||
|
|
e6b971b316 | ||
|
|
111bb485ec | ||
|
|
e10775d34f | ||
|
|
30957479a3 | ||
|
|
b4ab53dd32 | ||
|
|
a314eeb892 | ||
|
|
644ddf318c | ||
|
|
f598ac89ac | ||
|
|
3f7b3ca962 | ||
|
|
104c6edcde | ||
|
|
94b673dc8e | ||
|
|
096cd348f0 | ||
|
|
01c2ffe2ae | ||
|
|
0a4370236d | ||
|
|
fe8d2b397e | ||
|
|
92e90f9c55 | ||
|
|
3eb2642b11 | ||
|
|
99192a6dec | ||
|
|
cffc1fd1c4 | ||
|
|
b8f7c6daae | ||
|
|
cd07865da6 | ||
|
|
315e38e814 | ||
|
|
95b90271b6 | ||
|
|
90befac901 | ||
|
|
22f71ce17e |
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
|
|
||||||
@@ -199,7 +199,7 @@ if(BUILD_CLIENT)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(ZLIB)
|
find_package(ZLIB REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT DEFINED APPLICATION_ICON_NAME)
|
if (NOT DEFINED APPLICATION_ICON_NAME)
|
||||||
@@ -243,6 +243,7 @@ set(WITH_TESTING ${UNIT_TESTING})
|
|||||||
if(BUILD_CLIENT)
|
if(BUILD_CLIENT)
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
if(NOT BUILD_LIBRARIES_ONLY)
|
if(NOT BUILD_LIBRARIES_ONLY)
|
||||||
|
add_subdirectory(man)
|
||||||
add_subdirectory(doc)
|
add_subdirectory(doc)
|
||||||
add_subdirectory(doc/dev)
|
add_subdirectory(doc/dev)
|
||||||
if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin)
|
if(IS_DIRECTORY ${CMAKE_SOURCE_DIR}/admin)
|
||||||
|
|||||||
137
ChangeLog
137
ChangeLog
@@ -1,75 +1,149 @@
|
|||||||
ChangeLog
|
ChangeLog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
version 2.4.0 (2017-1X-XX)
|
version 2.4.3 (2018-08-xx)
|
||||||
* OAuth2 authentication support by opening external browser
|
* Windows: Don't ignore files with FILE_ATTRIBUTE_TEMPORARY (#6696, #6610)
|
||||||
* Sync Issues: More functional error view including filters and conflicts (#5516)
|
* OAuth2: Fix infinite loop when the refresh token is expired
|
||||||
|
* Windows MSI: Fix crash in the auto updater
|
||||||
|
* Nautilus: Guard against None state (#6643)
|
||||||
|
|
||||||
|
version 2.4.2 (2018-07-18)
|
||||||
|
* 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 support for multiple public link shares (#5655)
|
||||||
* Sharing: Add option to copy/email private links (#5627, #5023)
|
* Sharing: Add option to copy/email private links (#5023, #5627)
|
||||||
* Sharing: Add option "show file listing" (#5837)
|
* Sharing: Add option "show file listing" (#5837)
|
||||||
* Sharing: Show warning that links are public
|
* Sharing: Show warning that links are public (#5747)
|
||||||
* Sharing: Many UI improvements
|
* Sharing: Sharing dialog redesign: multiple share links support (#5695)
|
||||||
* Sharing: Make "can edit" partially checked sometimes (#5642)
|
* Sharing: Make "can edit" partially checked sometimes (#5642)
|
||||||
|
* Sharing: Trigger a sync for folder affected by a change of sharing (#6098)
|
||||||
* Wizard: Never propose an existing folder for syncing (#5597)
|
* Wizard: Never propose an existing folder for syncing (#5597)
|
||||||
* Wizard: Don't show last page anymore, go to settings directly (#5726)
|
* Wizard: Don't show last page anymore, go to settings directly (#5726)
|
||||||
* Wizard: Handle url-shortener redirects #5954
|
* Wizard: Handle url-shortener redirects (#5954)
|
||||||
* Gui: Display the user server avatar
|
* Wizard: Resolve url/ redirects only if url/status.php not found (#5954)
|
||||||
|
* Wizard: Add explanation text when server error is shown (#6157)
|
||||||
|
* Wizard: Update the window size on high dpi screen (#6156)
|
||||||
|
* Wizard: Don't report confusing error message (#6116)
|
||||||
|
* Gui: Display the user server avatar (#5482)
|
||||||
|
* Gui: Use display name of user, not internal name
|
||||||
* Server URL: Update configuration in case of permanent redirection (#5972)
|
* Server URL: Update configuration in case of permanent redirection (#5972)
|
||||||
* Gui: Allow to add multiple sync folder connection of the same folder
|
* Gui: Allow to add multiple sync folder connection of the same folder (#6032)
|
||||||
|
* Tray Menu: More detailed status messages
|
||||||
|
* Tray Menu: Shibboleth: raise the browser when clicking on the tray (#6105)
|
||||||
|
* Activity: Link errors from the account tab, allow filtering by account/folder (#5861)
|
||||||
|
* Activity: Present conflicts more prominently (#5894)
|
||||||
|
* Activity: Allow sorting the columns in issues and protocol tabs (#6093, #6086)
|
||||||
* Selective Sync: Open sub folder context menu (#5596)
|
* Selective Sync: Open sub folder context menu (#5596)
|
||||||
* Selective Sync: Skip excluded folders when reading db
|
* Selective Sync: Skip excluded folders when reading db (#5772)
|
||||||
* Selective Sync: Remove local files of unselected folder despite other modified files (#5783)
|
* Selective Sync: Remove local files of unselected folder despite other modified files (#5783)
|
||||||
* Excludes: remove .htaccess form list of excluded files
|
* Excludes: Remove .htaccess form list of excluded files (#5701)
|
||||||
* Excludes: Hardcode desktop.ini
|
* Excludes: Hardcode desktop.ini
|
||||||
* Excludes: Allow escaping "#" (#6012)
|
* Excludes: Allow escaping "#" (#6012)
|
||||||
* Excludes: Use faster matching via QRegularExpression
|
* Excludes: Use faster matching via QRegularExpression (#6063)
|
||||||
* Discovery: Increase the MAX_DEPTH and show deep folders as ignored
|
* Discovery: Increase the MAX_DEPTH and show deep folders as ignored (#1067)
|
||||||
* Discovery: General speed improvements
|
* Discovery: General speed improvements
|
||||||
* Downloads: Remove empty temporary if disk space full (#5746)
|
* Downloads: Remove empty temporary if disk space full (#5746)
|
||||||
* Downloads: Re-trigger folder discovery on 404
|
* Downloads: Read Content-MD5 header for object store setups
|
||||||
* Quota: PropagateUpload: Model of remote quota, avoid some uploads (#5537)
|
* Checksums: Add global disable environment variable (#5017)
|
||||||
* When creating explorer favorite use more specific windows functions (#5690)
|
* Quota: PropagateUpload: Model of remote quota, avoid some uploads (#5537)
|
||||||
* Create favorite also in folder wizard (#455)
|
* Create favorite also in folder wizard (#455)
|
||||||
* Windows: Use the application icon for the Windows 8 sidebar favorite (#2446)
|
* Windows: Use the application icon for the Windows 8 sidebar favorite (#2446, #5690)
|
||||||
* macOS: Finder sidebar icon (#296)
|
* macOS: Finder sidebar icon (#296)
|
||||||
* Overlay Icons: Consider also the "shared by me" as shared (#4788)
|
* Overlay Icons: Consider also the "shared by me" as shared (#4788)
|
||||||
|
* Overlay Icons: Update right after sharing (#6115)
|
||||||
* Overlay Icons: Fix different case paths not matching (#5257)
|
* Overlay Icons: Fix different case paths not matching (#5257)
|
||||||
|
* Overlay Icons: Detect changes in the shared flag (#6098)
|
||||||
* Windows Overlay Icons: Potential hang fixes
|
* Windows Overlay Icons: Potential hang fixes
|
||||||
* Linux Overlay Icons: fix branded nemo and caja shell integration (#5966)
|
* Linux Overlay Icons: fix branded nemo and caja shell integration (#5966)
|
||||||
* Http credentials: Fix behavior for bad password (#5989)
|
* Credentials: Fix behavior for bad password (#5989)
|
||||||
* Credentials: Use per-account keychain entries (#5830)
|
* Credentials: Don't create empty client cert keychain entries (#5752)
|
||||||
|
* Credentials: Namespace windows cred keys (#6125)
|
||||||
|
* Credentials: Use per-account keychain entries (#5830, #6126)
|
||||||
* AccountSettings: Triggering log in re-ask about previously rejected certificates (#5819)
|
* AccountSettings: Triggering log in re-ask about previously rejected certificates (#5819)
|
||||||
* Added owncloudcmd bandwidth limit parameter (#5707)
|
* owncloudcmd: Added bandwidth limit parameter (#5707)
|
||||||
|
* owncloudcmd: Fix timestamps, Fix --logdebug
|
||||||
* AccountSettings: Sync with clean discovery on Ctrl-F6 (#5666)
|
* AccountSettings: Sync with clean discovery on Ctrl-F6 (#5666)
|
||||||
* Sync: Dynamic sizing of chunks in chunked uploads for improved big file upload performance
|
* Sync: Dynamic sizing of chunks in chunked uploads for improved big file upload performance (#5852)
|
||||||
* Sync: Introduce overall errors that are not tied to a file (#5746)
|
* Sync: Introduce overall errors that are not tied to a file (#5746)
|
||||||
* Sync: Better messaging for 507 Insufficient Storage (#5537)
|
* Sync: Better messaging for 507 Insufficient Storage (#5537)
|
||||||
* Sync: Create conflicts by comparing the hash of files with identical mtime/size (#5589)
|
* 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: Blacklist: Don't let errors become warnings (#5516)
|
||||||
* Sync: Check etag again after active sync (#4116)
|
* Sync: Check etag again after active sync (#4116)
|
||||||
|
* 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)
|
||||||
* SyncJournalDB: Fall back to DELETE journal mode if WAL mode does not seem to work (#5723)
|
* SyncJournalDB: Fall back to DELETE journal mode if WAL mode does not seem to work (#5723)
|
||||||
|
* SyncJournalDB: Don't crash if the db file is readonly (#6050)
|
||||||
|
* SyncJournalDB: DB close error is not fatal
|
||||||
* Fix at least one memory leak
|
* Fix at least one memory leak
|
||||||
* Documentation improvements
|
* Documentation improvements
|
||||||
* Logging improvements (with Qt logging categories), new --logdebug parameter
|
* Logging improvements (With Qt logging categories) (#5671)
|
||||||
|
* Logging filtering per account (#5672)
|
||||||
* Crash fixes
|
* Crash fixes
|
||||||
* Test improvements
|
* Test improvements
|
||||||
* Small UI layout fixes
|
* Small UI layout fixes
|
||||||
|
* Performance improvements
|
||||||
* Maintenance Mode: Detect maintenance mode (#4485)
|
* Maintenance Mode: Detect maintenance mode (#4485)
|
||||||
* Maintenance Mode: Add a 1 to 5 min reconnection delay (#5872)
|
* Maintenance Mode: Add a 1 to 5 min reconnection delay (#5872)
|
||||||
* HTTP: Send a unique X-Request-ID with each request (#5853)
|
* HTTP: Send a unique X-Request-ID with each request (#5853)
|
||||||
* HTTP: Support HTTP2 when built and running with Qt 5.9.x (Official packages still on Qt 5.6.x)
|
* HTTP: Support HTTP2 when built and running with Qt 5.9.x (Official packages still on Qt 5.6.x) (#5659)
|
||||||
* owncloudcmd: Don't start if connection or auth fails (#5692)
|
* owncloudcmd: Don't start if connection or auth fails (#5692)
|
||||||
* csync: Switch build from C to C++
|
* csync: Switch build from C to C++ (#6033)
|
||||||
* csync: Refactor a lot to use common data structures to save memory and memory copying
|
* csync: Refactor a lot to use common data structures to save memory and memory copying
|
||||||
* csync: Switch some data structures to Qt data structures
|
* csync: Switch some data structures to Qt data structures
|
||||||
* csync: Switch to using upper layer SyncJournalDB
|
* csync: Switch to using upper layer SyncJournalDB (#6087)
|
||||||
* Switch 3rdparty/json usage to Qt5's QJson (#5710)
|
* Switch 3rdparty/json usage to Qt5's QJson (#5710)
|
||||||
* OpenSSL: Don't require directly, only via Qt
|
* OpenSSL: Don't require directly, only via Qt (#5833)
|
||||||
* Remove iconv dependency, use Qt for file system locale encoding/decoding (emoji filename support on macOS)
|
* Remove iconv dependency, use Qt for file system locale encoding/decoding (emoji filename support on macOS) (#5875)
|
||||||
* Compilation: Remove Qt 4 code
|
* Compilation: Remove Qt 4 code (#6025, #5702, #5505)
|
||||||
* Harmonize source code style with clang-format
|
* Harmonize source code style with clang-format (#5732)
|
||||||
* Switch over to Qt 5 function pointer signal/slot syntax
|
* Switch over to Qt 5 function pointer signal/slot syntax (#6041)
|
||||||
* Updater: Rudimentary support for beta channel
|
* Compile with stack-smashing protection
|
||||||
|
* Updater: Rudimentary support for beta channel (#6048)
|
||||||
|
|
||||||
|
version 2.3.4 (2017-11-02)
|
||||||
|
* Checksums: Use addData function to avoid endless loop CPU load issues with Office files
|
||||||
|
* Packaging: Require ZLIB
|
||||||
|
|
||||||
version 2.3.3 (2017-08-29)
|
version 2.3.3 (2017-08-29)
|
||||||
* Chunking NG: Don't use old chunking on new DAV endpoint (#5855)
|
* Chunking NG: Don't use old chunking on new DAV endpoint (#5855)
|
||||||
@@ -80,6 +154,7 @@ version 2.3.3 (2017-08-29)
|
|||||||
* Overlay Icons: Fix potential hangs on Windows
|
* 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: Don't use ._ as filename pattern if that does not work because of SMB storage settings (#5844)
|
||||||
* SyncJournalDB: Log reason for sqlite3 opening errors
|
* 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)
|
* Switch Linux build also to Qt 5.6.2 (#5470)
|
||||||
* Stopped maintaining Qt 4 buildability
|
* 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 )
|
option( WITH_CRASHREPORTER "Build crashreporter" OFF )
|
||||||
set( CRASHREPORTER_SUBMIT_URL "https://crash-reports.owncloud.com/submit" CACHE string "URL for crash reporter" )
|
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
|
# ownCloud Desktop Client
|
||||||
|
|
||||||
[](https://jenkins.owncloud.org/job/owncloud-client/job/client/job/master/)
|
[](https://drone.owncloud.com/owncloud/client)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
set( MIRALL_VERSION_MAJOR 2 )
|
set( MIRALL_VERSION_MAJOR 2 )
|
||||||
set( MIRALL_VERSION_MINOR 4 )
|
set( MIRALL_VERSION_MINOR 4 )
|
||||||
set( MIRALL_VERSION_PATCH 0 )
|
set( MIRALL_VERSION_PATCH 3 )
|
||||||
set( MIRALL_VERSION_YEAR 2017 )
|
set( MIRALL_VERSION_YEAR 2018 )
|
||||||
set( MIRALL_SOVERSION 0 )
|
set( MIRALL_SOVERSION 0 )
|
||||||
|
|
||||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||||
set( MIRALL_VERSION_SUFFIX "alpha1") #e.g. beta1, beta2, rc1
|
set( MIRALL_VERSION_SUFFIX "rc1") #e.g. beta1, beta2, rc1
|
||||||
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
endif( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||||
|
|
||||||
if( NOT DEFINED MIRALL_VERSION_BUILD )
|
if( NOT DEFINED MIRALL_VERSION_BUILD )
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ else()
|
|||||||
set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "0")
|
set(MAC_INSTALLER_DO_CUSTOM_BACKGROUND "0")
|
||||||
endif()
|
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(create_mac_pkg.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/create_mac.sh)
|
||||||
configure_file(macosx.pkgproj ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
configure_file(macosx.pkgproj ${CMAKE_CURRENT_BINARY_DIR}/macosx.pkgproj)
|
||||||
configure_file(pre_install.sh.cmake ${CMAKE_CURRENT_BINARY_DIR}/pre_install.sh)
|
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
|
prjfile=$build_path/admin/osx/macosx.pkgproj
|
||||||
|
|
||||||
# The name of the installer package
|
# 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="$installer.pkg"
|
||||||
installer_file_tar="$installer.pkg.tar"
|
installer_file_tar="$installer.pkg.tar"
|
||||||
installer_file_tar_bz2="$installer.pkg.tar.bz2"
|
installer_file_tar_bz2="$installer.pkg.tar.bz2"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import subprocess
|
|||||||
import commands
|
import commands
|
||||||
import sys
|
import sys
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
def QueryQMake(attrib):
|
def QueryQMake(attrib):
|
||||||
return subprocess.check_output([qmake_path, '-query', attrib]).rstrip('\n')
|
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')
|
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)];
|
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_libraries = []
|
||||||
fixed_frameworks = []
|
fixed_frameworks = []
|
||||||
@@ -334,9 +337,19 @@ def FindQtPlugin(name):
|
|||||||
for binary in binaries:
|
for binary in binaries:
|
||||||
FixBinary(binary)
|
FixBinary(binary)
|
||||||
|
|
||||||
|
|
||||||
|
if LooseVersion(qt_version) >= LooseVersion("5.10.0"):
|
||||||
|
QT_PLUGINS.append('styles/libqmacstyle.dylib')
|
||||||
for plugin in QT_PLUGINS:
|
for plugin in QT_PLUGINS:
|
||||||
FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
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:
|
if len(sys.argv) <= 2:
|
||||||
print 'Will run %d commands:' % len(commands)
|
print 'Will run %d commands:' % len(commands)
|
||||||
for command in commands:
|
for command in commands:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Vis versjonsmerknader"
|
|||||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "Fant ${APPLICATION_EXECUTABLE}-prosess(er) som må stoppes.$\nVil du at installasjonsprogrammet skal stoppe dem for deg?"
|
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "Fant ${APPLICATION_EXECUTABLE}-prosess(er) som må stoppes.$\nVil du at installasjonsprogrammet skal stoppe dem for deg?"
|
||||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Terminerer ${APPLICATION_EXECUTABLE}-prosesser."
|
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Terminerer ${APPLICATION_EXECUTABLE}-prosesser."
|
||||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "Fant ikke prosess som skulle termineres!"
|
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "Fant ikke prosess som skulle termineres!"
|
||||||
StrCpy $PageReinstall_NEW_Field_1 "En eldre versjon av ${APPLICATION_NAME} er installert på systemet ditt. Det anbefales at du avnistallerer den versjonen før installering av ny versjon. Velg hva du vil gjøre og klikk Neste for å fortsette."
|
StrCpy $PageReinstall_NEW_Field_1 "En eldre versjon av ${APPLICATION_NAME} er installert på systemet ditt. Det anbefales at du avinstallerer den versjonen før installering av ny versjon. Velg hva du vil gjøre og klikk Neste for å fortsette."
|
||||||
StrCpy $PageReinstall_NEW_Field_2 "Avinstaller før installering"
|
StrCpy $PageReinstall_NEW_Field_2 "Avinstaller før installering"
|
||||||
StrCpy $PageReinstall_NEW_Field_3 "Ikke avinstaller"
|
StrCpy $PageReinstall_NEW_Field_3 "Ikke avinstaller"
|
||||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Allerede installert"
|
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Allerede installert"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Auto-generated - do not modify
|
# Auto-generated - do not modify
|
||||||
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Mostrar las notas de la versión"
|
StrCpy $MUI_FINISHPAGE_SHOWREADME_TEXT_STRING "Mostrar las notas de la versión"
|
||||||
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "El/los proceso/s ${APPLICATION_EXECUTABLE} debe/n ser detenidos.$\n¿Quiere que el instalador lo haga por usted?"
|
StrCpy $ConfirmEndProcess_MESSAGEBOX_TEXT "El/los proceso(s) ${APPLICATION_EXECUTABLE} debe(n) ser detenido(s).$\n¿Quiere que el instalador lo haga por usted?"
|
||||||
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Deteniendo el/los proceso/s ${APPLICATION_EXECUTABLE}."
|
StrCpy $ConfirmEndProcess_KILLING_PROCESSES_TEXT "Deteniendo el/los proceso(s) ${APPLICATION_EXECUTABLE}."
|
||||||
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "¡Proceso a finalizar no encontrado!"
|
StrCpy $ConfirmEndProcess_KILL_NOT_FOUND_TEXT "¡Proceso a finalizar no encontrado!"
|
||||||
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} se encuentra instalada en el sistema. Se recomienda de instalar la versión actual antes de instalar la nueva. Seleccione la operacion deseada y haga click en Siguiente para continuar."
|
StrCpy $PageReinstall_NEW_Field_1 "Una versión anterior de ${APPLICATION_NAME} se encuentra instalada en el sistema. Se recomienda desinstalar la versión actual antes de instalar la nueva. Seleccione la operacion deseada y haga click en Siguiente para continuar."
|
||||||
StrCpy $PageReinstall_NEW_Field_2 "Desinstalar antes de instalar"
|
StrCpy $PageReinstall_NEW_Field_2 "Desinstalar antes de instalar"
|
||||||
StrCpy $PageReinstall_NEW_Field_3 "No desinstalar"
|
StrCpy $PageReinstall_NEW_Field_3 "No desinstalar"
|
||||||
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Ya está instalado"
|
StrCpy $PageReinstall_NEW_MUI_HEADER_TEXT_TITLE "Ya está instalado"
|
||||||
@@ -17,13 +17,13 @@ StrCpy $PageReinstall_SAME_MUI_HEADER_TEXT_SUBTITLE "Elija la opcion de mantenim
|
|||||||
StrCpy $SEC_APPLICATION_DETAILS "Instalando ${APPLICATION_NAME} esenciales."
|
StrCpy $SEC_APPLICATION_DETAILS "Instalando ${APPLICATION_NAME} esenciales."
|
||||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integración para Windows Explorer"
|
StrCpy $OPTION_SECTION_SC_SHELL_EXT_SECTION "Integración para Windows Explorer"
|
||||||
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Instalando la integración para Windows Explorer"
|
StrCpy $OPTION_SECTION_SC_SHELL_EXT_DetailPrint "Instalando la integración para Windows Explorer"
|
||||||
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Acceso directo al programa Menú de Inicio"
|
StrCpy $OPTION_SECTION_SC_START_MENU_SECTION "Acceso directo al programa en Menú de Inicio"
|
||||||
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "Añadiendo accesos directos para ${APPLICATION_NAME} en el Menú de Inicio."
|
StrCpy $OPTION_SECTION_SC_START_MENU_DetailPrint "Añadiendo accesos directos para ${APPLICATION_NAME} en el Menú de Inicio."
|
||||||
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Acceso directo de Escritorio"
|
StrCpy $OPTION_SECTION_SC_DESKTOP_SECTION "Acceso directo de Escritorio"
|
||||||
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Creando accesos directos de escritorio"
|
StrCpy $OPTION_SECTION_SC_DESKTOP_DetailPrint "Creando accesos directos de escritorio"
|
||||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atajo de accceso rápido"
|
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_SECTION "Atajo de acceso rápido"
|
||||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Creando un Acceso Directo al Lanzador Rápido"
|
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_DetailPrint "Creando un Acceso Directo al Lanzador Rápido"
|
||||||
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} esencial."
|
StrCpy $OPTION_SECTION_SC_APPLICATION_Desc "${APPLICATION_NAME} esenciales."
|
||||||
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Acceso Directo de ${APPLICATION_NAME}"
|
StrCpy $OPTION_SECTION_SC_START_MENU_Desc "Acceso Directo de ${APPLICATION_NAME}"
|
||||||
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Acceso Directo de Escritorio para ${APPLICATION_NAME}"
|
StrCpy $OPTION_SECTION_SC_DESKTOP_Desc "Acceso Directo de Escritorio para ${APPLICATION_NAME}"
|
||||||
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Lanzador Rápido de Accesos Director para ${APPLICATION_NAME}."
|
StrCpy $OPTION_SECTION_SC_QUICK_LAUNCH_Desc "Lanzador Rápido de Accesos Director para ${APPLICATION_NAME}."
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 25 KiB |
@@ -27,11 +27,9 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>@MIRALL_VERSION_STRING@</string>
|
<string>@MIRALL_VERSION_STRING@</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>(C) 2014-2016 @APPLICATION_VENDOR@</string>
|
<string>(C) 2014-2018 @APPLICATION_VENDOR@</string>
|
||||||
<key>SUShowReleaseNotes</key>
|
<key>SUShowReleaseNotes</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSMinimumBundleVersion</key>
|
|
||||||
<string>10.7.0</string>
|
|
||||||
<key>SUPublicDSAKeyFile</key>
|
<key>SUPublicDSAKeyFile</key>
|
||||||
<string>dsa_pub.pem</string>
|
<string>dsa_pub.pem</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ if(SPHINX_FOUND)
|
|||||||
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
|
set(SPHINX_CACHE_DIR "${CMAKE_CURRENT_BINARY_DIR}/_doctrees")
|
||||||
# HTML output directory
|
# HTML output directory
|
||||||
set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html")
|
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_PDF_DIR "${CMAKE_CURRENT_BINARY_DIR}/latex")
|
||||||
set(SPHINX_QCH_DIR "${CMAKE_CURRENT_BINARY_DIR}/qthelp")
|
set(SPHINX_QCH_DIR "${CMAKE_CURRENT_BINARY_DIR}/qthelp")
|
||||||
set(SPHINX_HTMLHELP_DIR "${CMAKE_CURRENT_BINARY_DIR}/htmlhelp")
|
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_PDF_DIR} DESTINATION ${APPLICATION_DOC_DIR} OPTIONAL)
|
||||||
install(DIRECTORY ${SPHINX_QCH_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)
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/conf.py.in" conf.py @ONLY)
|
||||||
|
|
||||||
if(WITH_DOC)
|
if(WITH_DOC)
|
||||||
@@ -73,11 +70,6 @@ if(SPHINX_FOUND)
|
|||||||
-D html_theme=owncloud_com
|
-D html_theme=owncloud_com
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
${SPHINX_HTML_DIR}/com )
|
${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
|
## Building CHM files requires HTML Help Workshop. Since it requires wine
|
||||||
## with special dependencies, it's impossible to write a cmake check for it.
|
## with special dependencies, it's impossible to write a cmake check for it.
|
||||||
@@ -92,4 +84,4 @@ if(SPHINX_FOUND)
|
|||||||
${SPHINX_HTMLHELP_DIR} )
|
${SPHINX_HTMLHELP_DIR} )
|
||||||
add_custom_target( doc-chm pushd ${SPHINX_HTMLHELP_DIR}; ${MSHTML_COMPILER} *.hhp; popd
|
add_custom_target( doc-chm pushd ${SPHINX_HTMLHELP_DIR}; ${MSHTML_COMPILER} *.hhp; popd
|
||||||
DEPENDS doc-chm-sphinx )
|
DEPENDS doc-chm-sphinx )
|
||||||
endif(SPHINX_FOUND)
|
endif(SPHINX_FOUND)
|
||||||
@@ -15,7 +15,7 @@ Configuration File
|
|||||||
.. include:: conffile.rst
|
.. include:: conffile.rst
|
||||||
|
|
||||||
Environment Variables
|
Environment Variables
|
||||||
------------------
|
---------------------
|
||||||
.. index:: env vars
|
.. index:: env vars
|
||||||
.. include:: envvars.rst
|
.. include:: envvars.rst
|
||||||
|
|
||||||
|
|||||||
@@ -62,4 +62,4 @@ Some interesting values that can be set on the configuration file are:
|
|||||||
| | | ``2`` for No Proxy. |
|
| | | ``2`` for No Proxy. |
|
||||||
+ + +--------------------------------------------------------------------------------------------------------+
|
+ + +--------------------------------------------------------------------------------------------------------+
|
||||||
| | | ``3`` for HTTP(S) Proxy. |
|
| | | ``3`` for HTTP(S) Proxy. |
|
||||||
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
+---------------------------------+---------------+--------------------------------------------------------------------------------------------------------+
|
||||||
|
|||||||
24
doc/faq.rst
24
doc/faq.rst
@@ -1,7 +1,7 @@
|
|||||||
FAQ
|
FAQ
|
||||||
===
|
===
|
||||||
|
|
||||||
Some files are continuously uploaded to the server, even when they are not modified.
|
Some Files Are Continuously Uploaded to the Server, Even When They Are Not Modified.
|
||||||
------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------
|
||||||
|
|
||||||
It is possible that another program is changing the modification date of the file.
|
It is possible that another program is changing the modification date of the file.
|
||||||
@@ -11,15 +11,17 @@ continually changes all files, unless you remove
|
|||||||
from the windows registry.
|
from the windows registry.
|
||||||
See http://petersteier.wordpress.com/2011/10/22/windows-indexer-changes-modification-dates-of-eml-files/ for more information.
|
See http://petersteier.wordpress.com/2011/10/22/windows-indexer-changes-modification-dates-of-eml-files/ for more information.
|
||||||
|
|
||||||
Syncing breaks when attempting to sync deeper than 50 sub-directories, but the sync client does not report an error (RC=0)
|
Syncing Stops When Attempting To Sync Deeper Than 100 Sub-directories.
|
||||||
--------------------------------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
The sync client has been intentionally limited to sync no deeper than
|
The sync client has been intentionally limited to sync no deeper than 100
|
||||||
fifty sub-directories, to help prevent memory problems.
|
sub-directories. The hard limit exists to guard against bugs with cycles
|
||||||
Unfortunately, it, *currently*, does not report an error when this occurs.
|
like symbolic link loops.
|
||||||
However, a UI notification is planned for a future release of ownCloud.
|
When a deeply nested directory is excluded from synchronization it will be
|
||||||
|
listed with other ignored files and directories in the "Not synced" tab of
|
||||||
|
the "Activity" pane.
|
||||||
|
|
||||||
I want to move my local sync folder
|
I Want To Move My Local Sync Folder
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
The ownCloud desktop client does not provide a way to change the local sync directory.
|
The ownCloud desktop client does not provide a way to change the local sync directory.
|
||||||
@@ -29,20 +31,20 @@ Specifically, you have to:
|
|||||||
1. Remove the existing connection which syncs to the wrong directory
|
1. Remove the existing connection which syncs to the wrong directory
|
||||||
2. Add a new connection which syncs to the desired directory
|
2. Add a new connection which syncs to the desired directory
|
||||||
|
|
||||||
.. image:: images/setup/ownCloud-remove_existing_connection.png
|
.. figure:: images/setup/ownCloud-remove_existing_connection.png
|
||||||
:alt: Remove an existing connection
|
:alt: Remove an existing connection
|
||||||
|
|
||||||
To do so, in the client UI, which you can see above, click the "**Account**" drop-down menu and then click "Remove".
|
To do so, in the client UI, which you can see above, click the "**Account**" drop-down menu and then click "Remove".
|
||||||
This will display a "**Confirm Account Removal**" dialog window.
|
This will display a "**Confirm Account Removal**" dialog window.
|
||||||
|
|
||||||
.. image:: images/setup/ownCloud-remove_existing_connection_confirmation_dialog.png
|
.. figure:: images/setup/ownCloud-remove_existing_connection_confirmation_dialog.png
|
||||||
:alt: Remove existing connection confirmation dialog
|
:alt: Remove existing connection confirmation dialog
|
||||||
|
|
||||||
If you're sure, click "**Remove connection**".
|
If you're sure, click "**Remove connection**".
|
||||||
|
|
||||||
Then, click the Account drop-down menu again, and this time click "**Add new**".
|
Then, click the Account drop-down menu again, and this time click "**Add new**".
|
||||||
|
|
||||||
.. image:: images/setup/ownCloud-replacement_connection_wizard.png
|
.. figure:: images/setup/ownCloud-replacement_connection_wizard.png
|
||||||
:alt: Replacement connection wizard
|
:alt: Replacement connection wizard
|
||||||
|
|
||||||
This opens the ownCloud Connection Wizard, which you can see above, *but* with an extra option.
|
This opens the ownCloud Connection Wizard, which you can see above, *but* with an extra option.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Installing the Desktop Synchronization Client
|
|||||||
=============================================
|
=============================================
|
||||||
|
|
||||||
You can download the latest version of the ownCloud Desktop Synchronization
|
You can download the latest version of the ownCloud Desktop Synchronization
|
||||||
Client from the `ownCloud download page <https://owncloud.org/install/>`_.
|
Client from the `ownCloud download page`_.
|
||||||
There are clients for Linux, Mac OS X, and Microsoft Windows.
|
There are clients for Linux, Mac OS X, and Microsoft Windows.
|
||||||
|
|
||||||
Installation on Mac OS X and Windows is the same as for any software
|
Installation on Mac OS X and Windows is the same as for any software
|
||||||
@@ -58,3 +58,7 @@ synchronizing your files.
|
|||||||
Web GUI, and one to open your local ownCloud folder
|
Web GUI, and one to open your local ownCloud folder
|
||||||
|
|
||||||
Click the Finish button, and you're all done.
|
Click the Finish button, and you're all done.
|
||||||
|
|
||||||
|
.. Links
|
||||||
|
|
||||||
|
.. _ownCloud download page: https://owncloud.com/download/#desktop-clients
|
||||||
|
|||||||
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,185 @@ GenericName=Folder Sync
|
|||||||
Icon=@APPLICATION_EXECUTABLE@
|
Icon=@APPLICATION_EXECUTABLE@
|
||||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||||
X-GNOME-Autostart-Delay=3
|
X-GNOME-Autostart-Delay=3
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
|
|
||||||
@@ -266,6 +445,11 @@ Icon[it]=@APPLICATION_EXECUTABLE@
|
|||||||
Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
||||||
GenericName[ko]=폴더 동기화
|
GenericName[ko]=폴더 동기화
|
||||||
Name[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
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
|
Comment[hu_HU]=@APPLICATION_NAME@ asztali szinkronizációs kliens
|
||||||
GenericName[hu_HU]=Könyvtár szinkronizálás
|
GenericName[hu_HU]=Könyvtár szinkronizálás
|
||||||
Name[hu_HU]=@APPLICATION_NAME@ asztali szinkr. kliens
|
Name[hu_HU]=@APPLICATION_NAME@ asztali szinkr. kliens
|
||||||
|
|||||||
@@ -15,8 +15,13 @@
|
|||||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
# for more details.
|
# for more details.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
python3 = sys.version_info[0] >= 3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import urllib
|
import urllib
|
||||||
|
if python3:
|
||||||
|
import urllib.parse
|
||||||
import socket
|
import socket
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
@@ -29,12 +34,15 @@ from gi.repository import GObject, Nautilus
|
|||||||
appname = 'ownCloud'
|
appname = 'ownCloud'
|
||||||
|
|
||||||
print("Initializing "+appname+"-client-nautilus extension")
|
print("Initializing "+appname+"-client-nautilus extension")
|
||||||
|
print("Using python version {}".format(sys.version_info))
|
||||||
|
|
||||||
def get_local_path(url):
|
def get_local_path(url):
|
||||||
if url[0:7] == 'file://':
|
if url[0:7] == 'file://':
|
||||||
url = url[7:]
|
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():
|
def get_runtime_dir():
|
||||||
"""Returns the value of $XDG_RUNTIME_DIR, a directory path.
|
"""Returns the value of $XDG_RUNTIME_DIR, a directory path.
|
||||||
@@ -56,7 +64,7 @@ class SocketConnect(GObject.GObject):
|
|||||||
self._watch_id = 0
|
self._watch_id = 0
|
||||||
self._sock = None
|
self._sock = None
|
||||||
self._listeners = [self._update_registered_paths]
|
self._listeners = [self._update_registered_paths]
|
||||||
self._remainder = ''
|
self._remainder = ''.encode('utf-8')
|
||||||
self.nautilusVFSFile_table = {} # not needed in this object actually but shared
|
self.nautilusVFSFile_table = {} # not needed in this object actually but shared
|
||||||
# all over the other objects.
|
# all over the other objects.
|
||||||
|
|
||||||
@@ -74,7 +82,7 @@ class SocketConnect(GObject.GObject):
|
|||||||
# print("Server command: " + cmd)
|
# print("Server command: " + cmd)
|
||||||
if self.connected:
|
if self.connected:
|
||||||
try:
|
try:
|
||||||
self._sock.send(cmd)
|
self._sock.send(cmd.encode('utf-8'))
|
||||||
except:
|
except:
|
||||||
print("Sending failed.")
|
print("Sending failed.")
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
@@ -89,18 +97,15 @@ class SocketConnect(GObject.GObject):
|
|||||||
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
|
sock_file = os.path.join(get_runtime_dir(), appname, "socket")
|
||||||
try:
|
try:
|
||||||
print("Socket File: " + sock_file)
|
|
||||||
self._sock.connect(sock_file) # fails if sock_file doesn't exist
|
self._sock.connect(sock_file) # fails if sock_file doesn't exist
|
||||||
self.connected = True
|
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)
|
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')
|
self.sendCommand('GET_STRINGS:\n')
|
||||||
|
|
||||||
return False # Don't run again
|
return False # Don't run again
|
||||||
except Exception as e:
|
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
|
except Exception as e: # Bad habbit
|
||||||
print("Connect could not be established, try again later.")
|
print("Connect could not be established, try again later.")
|
||||||
self._sock.close()
|
self._sock.close()
|
||||||
@@ -113,24 +118,24 @@ class SocketConnect(GObject.GObject):
|
|||||||
# Prepend the remaining data from last call
|
# Prepend the remaining data from last call
|
||||||
if len(self._remainder) > 0:
|
if len(self._remainder) > 0:
|
||||||
data = self._remainder + data
|
data = self._remainder + data
|
||||||
self._remainder = ''
|
self._remainder = ''.encode('utf-8')
|
||||||
|
|
||||||
if len(data) > 0:
|
if len(data) > 0:
|
||||||
# Remember the remainder for next round
|
# Remember the remainder for next round
|
||||||
lastNL = data.rfind('\n');
|
lastNL = data.rfind('\n'.encode('utf-8'));
|
||||||
if lastNL > -1 and lastNL < len(data):
|
if lastNL > -1 and lastNL < len(data):
|
||||||
self._remainder = data[lastNL+1:]
|
self._remainder = data[lastNL+1:]
|
||||||
data = data[:lastNL]
|
data = data[:lastNL]
|
||||||
|
|
||||||
for l in data.split('\n'):
|
for l in data.split('\n'.encode('utf-8')):
|
||||||
self._handle_server_response(l)
|
self._handle_server_response(l.decode('utf-8'))
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True # Run again
|
return True # Run again
|
||||||
|
|
||||||
def _handle_server_response(self, line):
|
def _handle_server_response(self, line):
|
||||||
print("Server response: " + line)
|
# print("Server response: " + line)
|
||||||
parts = line.split(':')
|
parts = line.split(':')
|
||||||
action = parts[0]
|
action = parts[0]
|
||||||
args = parts[1:]
|
args = parts[1:]
|
||||||
@@ -209,8 +214,8 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
|
|||||||
# and we definitely don't want to show them for IGNORED.
|
# and we definitely don't want to show them for IGNORED.
|
||||||
shareable = False
|
shareable = False
|
||||||
state = entry['state']
|
state = entry['state']
|
||||||
state_ok = state.startswith('OK')
|
state_ok = state and state.startswith('OK')
|
||||||
state_sync = state.startswith('SYNC')
|
state_sync = state and state.startswith('SYNC')
|
||||||
if state_ok:
|
if state_ok:
|
||||||
shareable = True
|
shareable = True
|
||||||
elif state_sync and isDir:
|
elif state_sync and isDir:
|
||||||
@@ -257,11 +262,11 @@ class MenuExtension(GObject.GObject, Nautilus.MenuProvider):
|
|||||||
|
|
||||||
def context_menu_action(self, menu, action, file):
|
def context_menu_action(self, menu, action, file):
|
||||||
filename = get_local_path(file.get_uri())
|
filename = get_local_path(file.get_uri())
|
||||||
print("Context menu: " + action + ' ' + filename)
|
# print("Context menu: " + action + ' ' + filename)
|
||||||
socketConnect.sendCommand(action + ":" + filename + "\n")
|
socketConnect.sendCommand(action + ":" + filename + "\n")
|
||||||
|
|
||||||
|
|
||||||
class SyncStateExtension(GObject.GObject, Nautilus.ColumnProvider, Nautilus.InfoProvider):
|
class SyncStateExtension(GObject.GObject, Nautilus.InfoProvider):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
GObject.GObject.__init__(self)
|
GObject.GObject.__init__(self)
|
||||||
|
|
||||||
|
|||||||
6094
src/3rdparty/sqlite3/sqlite3.c
vendored
6094
src/3rdparty/sqlite3/sqlite3.c
vendored
File diff suppressed because it is too large
Load Diff
189
src/3rdparty/sqlite3/sqlite3.h
vendored
189
src/3rdparty/sqlite3/sqlite3.h
vendored
@@ -115,15 +115,17 @@ extern "C" {
|
|||||||
** a string which identifies a particular check-in of SQLite
|
** a string which identifies a particular check-in of SQLite
|
||||||
** within its configuration management system. ^The SQLITE_SOURCE_ID
|
** within its configuration management system. ^The SQLITE_SOURCE_ID
|
||||||
** string contains the date and time of the check-in (UTC) and a SHA1
|
** string contains the date and time of the check-in (UTC) and a SHA1
|
||||||
** or SHA3-256 hash of the entire source tree.
|
** or SHA3-256 hash of the entire source tree. If the source code has
|
||||||
|
** been edited in any way since it was last checked in, then the last
|
||||||
|
** four hexadecimal digits of the hash may be modified.
|
||||||
**
|
**
|
||||||
** See also: [sqlite3_libversion()],
|
** See also: [sqlite3_libversion()],
|
||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.20.1"
|
#define SQLITE_VERSION "3.21.0"
|
||||||
#define SQLITE_VERSION_NUMBER 3020001
|
#define SQLITE_VERSION_NUMBER 3021000
|
||||||
#define SQLITE_SOURCE_ID "2017-08-24 16:21:36 8d3a7ea6c5690d6b7c3767558f4f01b511c55463e3f9e64506801fe9b74dce34"
|
#define SQLITE_SOURCE_ID "2017-10-24 18:55:49 1a584e499906b5c87ec7d43d4abce641fdf017c42125b083109bc77c4de48827"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
@@ -139,7 +141,7 @@ extern "C" {
|
|||||||
**
|
**
|
||||||
** <blockquote><pre>
|
** <blockquote><pre>
|
||||||
** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
|
** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
|
||||||
** assert( strcmp(sqlite3_sourceid(),SQLITE_SOURCE_ID)==0 );
|
** assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
|
||||||
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
|
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
|
||||||
** </pre></blockquote>)^
|
** </pre></blockquote>)^
|
||||||
**
|
**
|
||||||
@@ -149,9 +151,11 @@ extern "C" {
|
|||||||
** function is provided for use in DLLs since DLL users usually do not have
|
** function is provided for use in DLLs since DLL users usually do not have
|
||||||
** direct access to string constants within the DLL. ^The
|
** direct access to string constants within the DLL. ^The
|
||||||
** sqlite3_libversion_number() function returns an integer equal to
|
** sqlite3_libversion_number() function returns an integer equal to
|
||||||
** [SQLITE_VERSION_NUMBER]. ^The sqlite3_sourceid() function returns
|
** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns
|
||||||
** a pointer to a string constant whose value is the same as the
|
** a pointer to a string constant whose value is the same as the
|
||||||
** [SQLITE_SOURCE_ID] C preprocessor macro.
|
** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built
|
||||||
|
** using an edited copy of [the amalgamation], then the last four characters
|
||||||
|
** of the hash might be different from [SQLITE_SOURCE_ID].)^
|
||||||
**
|
**
|
||||||
** See also: [sqlite_version()] and [sqlite_source_id()].
|
** See also: [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
@@ -432,7 +436,7 @@ SQLITE_API int sqlite3_exec(
|
|||||||
#define SQLITE_FULL 13 /* Insertion failed because database is full */
|
#define SQLITE_FULL 13 /* Insertion failed because database is full */
|
||||||
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
|
#define SQLITE_CANTOPEN 14 /* Unable to open the database file */
|
||||||
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
|
#define SQLITE_PROTOCOL 15 /* Database lock protocol error */
|
||||||
#define SQLITE_EMPTY 16 /* Not used */
|
#define SQLITE_EMPTY 16 /* Internal use only */
|
||||||
#define SQLITE_SCHEMA 17 /* The database schema changed */
|
#define SQLITE_SCHEMA 17 /* The database schema changed */
|
||||||
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
|
#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */
|
||||||
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
|
#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */
|
||||||
@@ -494,6 +498,9 @@ SQLITE_API int sqlite3_exec(
|
|||||||
#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8))
|
#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8))
|
||||||
#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8))
|
#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8))
|
||||||
#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8))
|
#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8))
|
||||||
|
#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8))
|
||||||
|
#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8))
|
||||||
|
#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8))
|
||||||
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
|
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
|
||||||
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
|
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
|
||||||
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
|
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
|
||||||
@@ -580,6 +587,11 @@ SQLITE_API int sqlite3_exec(
|
|||||||
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
|
** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on
|
||||||
** read-only media and cannot be changed even by processes with
|
** read-only media and cannot be changed even by processes with
|
||||||
** elevated privileges.
|
** elevated privileges.
|
||||||
|
**
|
||||||
|
** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
|
||||||
|
** filesystem supports doing multiple write operations atomically when those
|
||||||
|
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
|
||||||
|
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_IOCAP_ATOMIC 0x00000001
|
#define SQLITE_IOCAP_ATOMIC 0x00000001
|
||||||
#define SQLITE_IOCAP_ATOMIC512 0x00000002
|
#define SQLITE_IOCAP_ATOMIC512 0x00000002
|
||||||
@@ -595,6 +607,7 @@ SQLITE_API int sqlite3_exec(
|
|||||||
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
|
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
|
||||||
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
|
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
|
||||||
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
|
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
|
||||||
|
#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: File Locking Levels
|
** CAPI3REF: File Locking Levels
|
||||||
@@ -729,6 +742,7 @@ struct sqlite3_file {
|
|||||||
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
|
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
|
||||||
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
|
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
|
||||||
** <li> [SQLITE_IOCAP_IMMUTABLE]
|
** <li> [SQLITE_IOCAP_IMMUTABLE]
|
||||||
|
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
|
||||||
** </ul>
|
** </ul>
|
||||||
**
|
**
|
||||||
** The SQLITE_IOCAP_ATOMIC property means that all writes of
|
** The SQLITE_IOCAP_ATOMIC property means that all writes of
|
||||||
@@ -1012,6 +1026,40 @@ struct sqlite3_io_methods {
|
|||||||
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
|
** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by
|
||||||
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
|
** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for
|
||||||
** this opcode.
|
** this opcode.
|
||||||
|
**
|
||||||
|
** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]]
|
||||||
|
** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then
|
||||||
|
** the file descriptor is placed in "batch write mode", which
|
||||||
|
** means all subsequent write operations will be deferred and done
|
||||||
|
** atomically at the next [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. Systems
|
||||||
|
** that do not support batch atomic writes will return SQLITE_NOTFOUND.
|
||||||
|
** ^Following a successful SQLITE_FCNTL_BEGIN_ATOMIC_WRITE and prior to
|
||||||
|
** the closing [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] or
|
||||||
|
** [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE], SQLite will make
|
||||||
|
** no VFS interface calls on the same [sqlite3_file] file descriptor
|
||||||
|
** except for calls to the xWrite method and the xFileControl method
|
||||||
|
** with [SQLITE_FCNTL_SIZE_HINT].
|
||||||
|
**
|
||||||
|
** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]]
|
||||||
|
** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write
|
||||||
|
** operations since the previous successful call to
|
||||||
|
** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically.
|
||||||
|
** This file control returns [SQLITE_OK] if and only if the writes were
|
||||||
|
** all performed successfully and have been committed to persistent storage.
|
||||||
|
** ^Regardless of whether or not it is successful, this file control takes
|
||||||
|
** the file descriptor out of batch write mode so that all subsequent
|
||||||
|
** write operations are independent.
|
||||||
|
** ^SQLite will never invoke SQLITE_FCNTL_COMMIT_ATOMIC_WRITE without
|
||||||
|
** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
|
||||||
|
**
|
||||||
|
** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]]
|
||||||
|
** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write
|
||||||
|
** operations since the previous successful call to
|
||||||
|
** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back.
|
||||||
|
** ^This file control takes the file descriptor out of batch write mode
|
||||||
|
** so that all subsequent write operations are independent.
|
||||||
|
** ^SQLite will never invoke SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE without
|
||||||
|
** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE].
|
||||||
** </ul>
|
** </ul>
|
||||||
*/
|
*/
|
||||||
#define SQLITE_FCNTL_LOCKSTATE 1
|
#define SQLITE_FCNTL_LOCKSTATE 1
|
||||||
@@ -1043,6 +1091,9 @@ struct sqlite3_io_methods {
|
|||||||
#define SQLITE_FCNTL_JOURNAL_POINTER 28
|
#define SQLITE_FCNTL_JOURNAL_POINTER 28
|
||||||
#define SQLITE_FCNTL_WIN32_GET_HANDLE 29
|
#define SQLITE_FCNTL_WIN32_GET_HANDLE 29
|
||||||
#define SQLITE_FCNTL_PDB 30
|
#define SQLITE_FCNTL_PDB 30
|
||||||
|
#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31
|
||||||
|
#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32
|
||||||
|
#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33
|
||||||
|
|
||||||
/* deprecated names */
|
/* deprecated names */
|
||||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||||
@@ -1613,6 +1664,16 @@ struct sqlite3_mem_methods {
|
|||||||
** routines with a wrapper that simulations memory allocation failure or
|
** routines with a wrapper that simulations memory allocation failure or
|
||||||
** tracks memory usage, for example. </dd>
|
** tracks memory usage, for example. </dd>
|
||||||
**
|
**
|
||||||
|
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
|
||||||
|
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
|
||||||
|
** type int, interpreted as a boolean, which if true provides a hint to
|
||||||
|
** SQLite that it should avoid large memory allocations if possible.
|
||||||
|
** SQLite will run faster if it is free to make large memory allocations,
|
||||||
|
** but some application might prefer to run slower in exchange for
|
||||||
|
** guarantees about memory fragmentation that are possible if large
|
||||||
|
** allocations are avoided. This hint is normally off.
|
||||||
|
** </dd>
|
||||||
|
**
|
||||||
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
|
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
|
||||||
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
|
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
|
||||||
** interpreted as a boolean, which enables or disables the collection of
|
** interpreted as a boolean, which enables or disables the collection of
|
||||||
@@ -1630,25 +1691,7 @@ struct sqlite3_mem_methods {
|
|||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt>
|
** [[SQLITE_CONFIG_SCRATCH]] <dt>SQLITE_CONFIG_SCRATCH</dt>
|
||||||
** <dd> ^The SQLITE_CONFIG_SCRATCH option specifies a static memory buffer
|
** <dd> The SQLITE_CONFIG_SCRATCH option is no longer used.
|
||||||
** that SQLite can use for scratch memory. ^(There are three arguments
|
|
||||||
** to SQLITE_CONFIG_SCRATCH: A pointer an 8-byte
|
|
||||||
** aligned memory buffer from which the scratch allocations will be
|
|
||||||
** drawn, the size of each scratch allocation (sz),
|
|
||||||
** and the maximum number of scratch allocations (N).)^
|
|
||||||
** The first argument must be a pointer to an 8-byte aligned buffer
|
|
||||||
** of at least sz*N bytes of memory.
|
|
||||||
** ^SQLite will not use more than one scratch buffers per thread.
|
|
||||||
** ^SQLite will never request a scratch buffer that is more than 6
|
|
||||||
** times the database page size.
|
|
||||||
** ^If SQLite needs needs additional
|
|
||||||
** scratch memory beyond what is provided by this configuration option, then
|
|
||||||
** [sqlite3_malloc()] will be used to obtain the memory needed.<p>
|
|
||||||
** ^When the application provides any amount of scratch memory using
|
|
||||||
** SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary large
|
|
||||||
** [sqlite3_malloc|heap allocations].
|
|
||||||
** This can help [Robson proof|prevent memory allocation failures] due to heap
|
|
||||||
** fragmentation in low-memory embedded systems.
|
|
||||||
** </dd>
|
** </dd>
|
||||||
**
|
**
|
||||||
** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt>
|
** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt>
|
||||||
@@ -1684,8 +1727,7 @@ struct sqlite3_mem_methods {
|
|||||||
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
|
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
|
||||||
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
|
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
|
||||||
** that SQLite will use for all of its dynamic memory allocation needs
|
** that SQLite will use for all of its dynamic memory allocation needs
|
||||||
** beyond those provided for by [SQLITE_CONFIG_SCRATCH] and
|
** beyond those provided for by [SQLITE_CONFIG_PAGECACHE].
|
||||||
** [SQLITE_CONFIG_PAGECACHE].
|
|
||||||
** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
|
** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled
|
||||||
** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
|
** with either [SQLITE_ENABLE_MEMSYS3] or [SQLITE_ENABLE_MEMSYS5] and returns
|
||||||
** [SQLITE_ERROR] if invoked otherwise.
|
** [SQLITE_ERROR] if invoked otherwise.
|
||||||
@@ -1878,7 +1920,7 @@ struct sqlite3_mem_methods {
|
|||||||
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
|
#define SQLITE_CONFIG_SERIALIZED 3 /* nil */
|
||||||
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
|
#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */
|
||||||
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
|
#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */
|
||||||
#define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */
|
#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */
|
||||||
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
|
#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */
|
||||||
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
|
#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */
|
||||||
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
|
#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */
|
||||||
@@ -1899,6 +1941,7 @@ struct sqlite3_mem_methods {
|
|||||||
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
|
#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */
|
||||||
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
|
#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */
|
||||||
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
|
#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */
|
||||||
|
#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Database Connection Configuration Options
|
** CAPI3REF: Database Connection Configuration Options
|
||||||
@@ -3099,10 +3142,10 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*);
|
|||||||
** ^If [URI filename] interpretation is enabled, and the filename argument
|
** ^If [URI filename] interpretation is enabled, and the filename argument
|
||||||
** begins with "file:", then the filename is interpreted as a URI. ^URI
|
** begins with "file:", then the filename is interpreted as a URI. ^URI
|
||||||
** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is
|
** filename interpretation is enabled if the [SQLITE_OPEN_URI] flag is
|
||||||
** set in the fourth argument to sqlite3_open_v2(), or if it has
|
** set in the third argument to sqlite3_open_v2(), or if it has
|
||||||
** been enabled globally using the [SQLITE_CONFIG_URI] option with the
|
** been enabled globally using the [SQLITE_CONFIG_URI] option with the
|
||||||
** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
|
** [sqlite3_config()] method or by the [SQLITE_USE_URI] compile-time option.
|
||||||
** As of SQLite version 3.7.7, URI filename interpretation is turned off
|
** URI filename interpretation is turned off
|
||||||
** by default, but future releases of SQLite might enable URI filename
|
** by default, but future releases of SQLite might enable URI filename
|
||||||
** interpretation by default. See "[URI filenames]" for additional
|
** interpretation by default. See "[URI filenames]" for additional
|
||||||
** information.
|
** information.
|
||||||
@@ -3776,8 +3819,9 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*);
|
|||||||
** implementation of [application-defined SQL functions] are protected.
|
** implementation of [application-defined SQL functions] are protected.
|
||||||
** ^The sqlite3_value object returned by
|
** ^The sqlite3_value object returned by
|
||||||
** [sqlite3_column_value()] is unprotected.
|
** [sqlite3_column_value()] is unprotected.
|
||||||
** Unprotected sqlite3_value objects may only be used with
|
** Unprotected sqlite3_value objects may only be used as arguments
|
||||||
** [sqlite3_result_value()] and [sqlite3_bind_value()].
|
** to [sqlite3_result_value()], [sqlite3_bind_value()], and
|
||||||
|
** [sqlite3_value_dup()].
|
||||||
** The [sqlite3_value_blob | sqlite3_value_type()] family of
|
** The [sqlite3_value_blob | sqlite3_value_type()] family of
|
||||||
** interfaces require protected sqlite3_value objects.
|
** interfaces require protected sqlite3_value objects.
|
||||||
*/
|
*/
|
||||||
@@ -4199,7 +4243,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
|
|||||||
** other than [SQLITE_ROW] before any subsequent invocation of
|
** other than [SQLITE_ROW] before any subsequent invocation of
|
||||||
** sqlite3_step(). Failure to reset the prepared statement using
|
** sqlite3_step(). Failure to reset the prepared statement using
|
||||||
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
|
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
|
||||||
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
|
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
|
||||||
** sqlite3_step() began
|
** sqlite3_step() began
|
||||||
** calling [sqlite3_reset()] automatically in this circumstance rather
|
** calling [sqlite3_reset()] automatically in this circumstance rather
|
||||||
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
|
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
|
||||||
@@ -6203,15 +6247,20 @@ struct sqlite3_index_info {
|
|||||||
** an operator that is part of a constraint term in the wHERE clause of
|
** an operator that is part of a constraint term in the wHERE clause of
|
||||||
** a query that uses a [virtual table].
|
** a query that uses a [virtual table].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_INDEX_CONSTRAINT_EQ 2
|
#define SQLITE_INDEX_CONSTRAINT_EQ 2
|
||||||
#define SQLITE_INDEX_CONSTRAINT_GT 4
|
#define SQLITE_INDEX_CONSTRAINT_GT 4
|
||||||
#define SQLITE_INDEX_CONSTRAINT_LE 8
|
#define SQLITE_INDEX_CONSTRAINT_LE 8
|
||||||
#define SQLITE_INDEX_CONSTRAINT_LT 16
|
#define SQLITE_INDEX_CONSTRAINT_LT 16
|
||||||
#define SQLITE_INDEX_CONSTRAINT_GE 32
|
#define SQLITE_INDEX_CONSTRAINT_GE 32
|
||||||
#define SQLITE_INDEX_CONSTRAINT_MATCH 64
|
#define SQLITE_INDEX_CONSTRAINT_MATCH 64
|
||||||
#define SQLITE_INDEX_CONSTRAINT_LIKE 65
|
#define SQLITE_INDEX_CONSTRAINT_LIKE 65
|
||||||
#define SQLITE_INDEX_CONSTRAINT_GLOB 66
|
#define SQLITE_INDEX_CONSTRAINT_GLOB 66
|
||||||
#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
|
#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
|
||||||
|
#define SQLITE_INDEX_CONSTRAINT_NE 68
|
||||||
|
#define SQLITE_INDEX_CONSTRAINT_ISNOT 69
|
||||||
|
#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70
|
||||||
|
#define SQLITE_INDEX_CONSTRAINT_ISNULL 71
|
||||||
|
#define SQLITE_INDEX_CONSTRAINT_IS 72
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Register A Virtual Table Implementation
|
** CAPI3REF: Register A Virtual Table Implementation
|
||||||
@@ -6963,7 +7012,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||||||
#define SQLITE_TESTCTRL_RESERVE 14
|
#define SQLITE_TESTCTRL_RESERVE 14
|
||||||
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
#define SQLITE_TESTCTRL_OPTIMIZATIONS 15
|
||||||
#define SQLITE_TESTCTRL_ISKEYWORD 16
|
#define SQLITE_TESTCTRL_ISKEYWORD 16
|
||||||
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17
|
#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */
|
||||||
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
|
#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18
|
||||||
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
|
#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */
|
||||||
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
|
#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19
|
||||||
@@ -7022,8 +7071,7 @@ SQLITE_API int sqlite3_status64(
|
|||||||
** <dd>This parameter is the current amount of memory checked out
|
** <dd>This parameter is the current amount of memory checked out
|
||||||
** using [sqlite3_malloc()], either directly or indirectly. The
|
** using [sqlite3_malloc()], either directly or indirectly. The
|
||||||
** figure includes calls made to [sqlite3_malloc()] by the application
|
** figure includes calls made to [sqlite3_malloc()] by the application
|
||||||
** and internal memory usage by the SQLite library. Scratch memory
|
** and internal memory usage by the SQLite library. Auxiliary page-cache
|
||||||
** controlled by [SQLITE_CONFIG_SCRATCH] and auxiliary page-cache
|
|
||||||
** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in
|
** memory controlled by [SQLITE_CONFIG_PAGECACHE] is not included in
|
||||||
** this parameter. The amount returned is the sum of the allocation
|
** this parameter. The amount returned is the sum of the allocation
|
||||||
** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^
|
** sizes as reported by the xSize method in [sqlite3_mem_methods].</dd>)^
|
||||||
@@ -7061,29 +7109,14 @@ SQLITE_API int sqlite3_status64(
|
|||||||
** *pHighwater parameter to [sqlite3_status()] is of interest.
|
** *pHighwater parameter to [sqlite3_status()] is of interest.
|
||||||
** The value written into the *pCurrent parameter is undefined.</dd>)^
|
** The value written into the *pCurrent parameter is undefined.</dd>)^
|
||||||
**
|
**
|
||||||
** [[SQLITE_STATUS_SCRATCH_USED]] ^(<dt>SQLITE_STATUS_SCRATCH_USED</dt>
|
** [[SQLITE_STATUS_SCRATCH_USED]] <dt>SQLITE_STATUS_SCRATCH_USED</dt>
|
||||||
** <dd>This parameter returns the number of allocations used out of the
|
** <dd>No longer used.</dd>
|
||||||
** [scratch memory allocator] configured using
|
|
||||||
** [SQLITE_CONFIG_SCRATCH]. The value returned is in allocations, not
|
|
||||||
** in bytes. Since a single thread may only have one scratch allocation
|
|
||||||
** outstanding at time, this parameter also reports the number of threads
|
|
||||||
** using scratch memory at the same time.</dd>)^
|
|
||||||
**
|
**
|
||||||
** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt>
|
** [[SQLITE_STATUS_SCRATCH_OVERFLOW]] ^(<dt>SQLITE_STATUS_SCRATCH_OVERFLOW</dt>
|
||||||
** <dd>This parameter returns the number of bytes of scratch memory
|
** <dd>No longer used.</dd>
|
||||||
** allocation which could not be satisfied by the [SQLITE_CONFIG_SCRATCH]
|
|
||||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The values
|
|
||||||
** returned include overflows because the requested allocation was too
|
|
||||||
** larger (that is, because the requested allocation was larger than the
|
|
||||||
** "sz" parameter to [SQLITE_CONFIG_SCRATCH]) and because no scratch buffer
|
|
||||||
** slots were available.
|
|
||||||
** </dd>)^
|
|
||||||
**
|
**
|
||||||
** [[SQLITE_STATUS_SCRATCH_SIZE]] ^(<dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
|
** [[SQLITE_STATUS_SCRATCH_SIZE]] <dt>SQLITE_STATUS_SCRATCH_SIZE</dt>
|
||||||
** <dd>This parameter records the largest memory allocation request
|
** <dd>No longer used.</dd>
|
||||||
** handed to [scratch memory allocator]. Only the value returned in the
|
|
||||||
** *pHighwater parameter to [sqlite3_status()] is of interest.
|
|
||||||
** The value written into the *pCurrent parameter is undefined.</dd>)^
|
|
||||||
**
|
**
|
||||||
** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt>
|
** [[SQLITE_STATUS_PARSER_STACK]] ^(<dt>SQLITE_STATUS_PARSER_STACK</dt>
|
||||||
** <dd>The *pHighwater parameter records the deepest parser stack.
|
** <dd>The *pHighwater parameter records the deepest parser stack.
|
||||||
@@ -7096,12 +7129,12 @@ SQLITE_API int sqlite3_status64(
|
|||||||
#define SQLITE_STATUS_MEMORY_USED 0
|
#define SQLITE_STATUS_MEMORY_USED 0
|
||||||
#define SQLITE_STATUS_PAGECACHE_USED 1
|
#define SQLITE_STATUS_PAGECACHE_USED 1
|
||||||
#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2
|
#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2
|
||||||
#define SQLITE_STATUS_SCRATCH_USED 3
|
#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */
|
||||||
#define SQLITE_STATUS_SCRATCH_OVERFLOW 4
|
#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */
|
||||||
#define SQLITE_STATUS_MALLOC_SIZE 5
|
#define SQLITE_STATUS_MALLOC_SIZE 5
|
||||||
#define SQLITE_STATUS_PARSER_STACK 6
|
#define SQLITE_STATUS_PARSER_STACK 6
|
||||||
#define SQLITE_STATUS_PAGECACHE_SIZE 7
|
#define SQLITE_STATUS_PAGECACHE_SIZE 7
|
||||||
#define SQLITE_STATUS_SCRATCH_SIZE 8
|
#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */
|
||||||
#define SQLITE_STATUS_MALLOC_COUNT 9
|
#define SQLITE_STATUS_MALLOC_COUNT 9
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -9198,8 +9231,8 @@ SQLITE_API int sqlite3session_diff(
|
|||||||
*/
|
*/
|
||||||
SQLITE_API int sqlite3session_patchset(
|
SQLITE_API int sqlite3session_patchset(
|
||||||
sqlite3_session *pSession, /* Session object */
|
sqlite3_session *pSession, /* Session object */
|
||||||
int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */
|
int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */
|
||||||
void **ppPatchset /* OUT: Buffer containing changeset */
|
void **ppPatchset /* OUT: Buffer containing patchset */
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -9966,12 +9999,12 @@ SQLITE_API int sqlite3changeset_apply(
|
|||||||
**
|
**
|
||||||
** <table border=1 style="margin-left:8ex;margin-right:8ex">
|
** <table border=1 style="margin-left:8ex;margin-right:8ex">
|
||||||
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
|
** <tr><th>Streaming function<th>Non-streaming equivalent</th>
|
||||||
** <tr><td>sqlite3changeset_apply_str<td>[sqlite3changeset_apply]
|
** <tr><td>sqlite3changeset_apply_strm<td>[sqlite3changeset_apply]
|
||||||
** <tr><td>sqlite3changeset_concat_str<td>[sqlite3changeset_concat]
|
** <tr><td>sqlite3changeset_concat_strm<td>[sqlite3changeset_concat]
|
||||||
** <tr><td>sqlite3changeset_invert_str<td>[sqlite3changeset_invert]
|
** <tr><td>sqlite3changeset_invert_strm<td>[sqlite3changeset_invert]
|
||||||
** <tr><td>sqlite3changeset_start_str<td>[sqlite3changeset_start]
|
** <tr><td>sqlite3changeset_start_strm<td>[sqlite3changeset_start]
|
||||||
** <tr><td>sqlite3session_changeset_str<td>[sqlite3session_changeset]
|
** <tr><td>sqlite3session_changeset_strm<td>[sqlite3session_changeset]
|
||||||
** <tr><td>sqlite3session_patchset_str<td>[sqlite3session_patchset]
|
** <tr><td>sqlite3session_patchset_strm<td>[sqlite3session_patchset]
|
||||||
** </table>
|
** </table>
|
||||||
**
|
**
|
||||||
** Non-streaming functions that accept changesets (or patchsets) as input
|
** Non-streaming functions that accept changesets (or patchsets) as input
|
||||||
|
|||||||
@@ -8,20 +8,29 @@ if(NOT TOKEN_AUTH_ONLY)
|
|||||||
find_package(Qt5Keychain REQUIRED)
|
find_package(Qt5Keychain REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(NOT WIN32)
|
||||||
# Enable DEP & ASLR
|
if(NOT (CMAKE_SYSTEM_PROCESSOR MATCHES "^(alpha|parisc|hppa)"))
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
if((CMAKE_CXX_COMPILER_ID MATCHES "GNU") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9))
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--nxcompat -Wl,--dynamicbase")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
|
||||||
elseif(UNIX AND NOT APPLE)
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector --param=ssp-buffer-size=4")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector-strong")
|
else()
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong")
|
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)
|
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER)
|
||||||
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
if(CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)")
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2")
|
||||||
endif()
|
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_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")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ include_directories(${CMAKE_SOURCE_DIR}/src/csync
|
|||||||
include_directories(${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer)
|
include_directories(${CMAKE_SOURCE_DIR}/src/3rdparty/qtokenizer)
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pie -fPIE")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pie -fPIE")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT BUILD_LIBRARIES_ONLY)
|
if(NOT BUILD_LIBRARIES_ONLY)
|
||||||
add_executable(${cmd_NAME} ${cmd_SRC})
|
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
|
set_target_properties(${cmd_NAME} PROPERTIES
|
||||||
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
|
RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} )
|
||||||
set_target_properties(${cmd_NAME} PROPERTIES
|
set_target_properties(${cmd_NAME} PROPERTIES
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "netrcparser.h"
|
#include "netrcparser.h"
|
||||||
|
#include "libsync/logger.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@@ -189,7 +190,7 @@ void help()
|
|||||||
std::cout << " --downlimit [n] Limit the download speed of files to n KB/s" << std::endl;
|
std::cout << " --downlimit [n] Limit the download speed of files to n KB/s" << std::endl;
|
||||||
std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
|
std::cout << " -h Sync hidden files,do not ignore them" << std::endl;
|
||||||
std::cout << " --version, -v Display version and exit" << std::endl;
|
std::cout << " --version, -v Display version and exit" << std::endl;
|
||||||
std::cout << " --debug More verbose logging" << std::endl;
|
std::cout << " --logdebug More verbose logging" << std::endl;
|
||||||
std::cout << "" << std::endl;
|
std::cout << "" << std::endl;
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
@@ -267,6 +268,9 @@ void parseOptions(const QStringList &app_args, CmdOptions *options)
|
|||||||
options->uplimit = it.next().toInt() * 1000;
|
options->uplimit = it.next().toInt() * 1000;
|
||||||
} else if (option == "--downlimit" && !it.peekNext().startsWith("-")) {
|
} else if (option == "--downlimit" && !it.peekNext().startsWith("-")) {
|
||||||
options->downlimit = it.next().toInt() * 1000;
|
options->downlimit = it.next().toInt() * 1000;
|
||||||
|
} else if (option == "--logdebug") {
|
||||||
|
Logger::instance()->setLogFile("-");
|
||||||
|
Logger::instance()->setLogDebug(true);
|
||||||
} else {
|
} else {
|
||||||
help();
|
help();
|
||||||
}
|
}
|
||||||
@@ -330,6 +334,8 @@ int main(int argc, char **argv)
|
|||||||
csync_set_log_level(options.silent ? 1 : 11);
|
csync_set_log_level(options.silent ? 1 : 11);
|
||||||
if (options.silent) {
|
if (options.silent) {
|
||||||
qInstallMsgHandler(nullMessageHandler);
|
qInstallMsgHandler(nullMessageHandler);
|
||||||
|
} else {
|
||||||
|
qSetMessagePattern("%{time MM-dd hh:mm:ss:zzz} [ %{type} %{category} ]%{if-debug}\t[ %{function} ]%{endif}:\t%{message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountPtr account = Account::create();
|
AccountPtr account = Account::create();
|
||||||
@@ -414,46 +420,6 @@ int main(int argc, char **argv)
|
|||||||
folder.chop(1);
|
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()) {
|
if (!options.proxy.isNull()) {
|
||||||
QString host;
|
QString host;
|
||||||
int port = 0;
|
int port = 0;
|
||||||
@@ -471,16 +437,58 @@ restart_sync:
|
|||||||
|
|
||||||
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
||||||
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, host, port));
|
QNetworkProxy::setApplicationProxy(QNetworkProxy(QNetworkProxy::HttpProxy, host, port));
|
||||||
|
} else {
|
||||||
|
qFatal("Could not read httpproxy. The proxy should have the format \"http://hostname:port\".");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
clientProxy.setupQtProxyFromConfig();
|
clientProxy.setupQtProxyFromConfig();
|
||||||
QString url(options.target_url);
|
}
|
||||||
if (url.startsWith("owncloud")) {
|
|
||||||
url.remove(0, 8);
|
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
|
||||||
url = QString("http%1").arg(url);
|
|
||||||
|
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;
|
QStringList selectiveSyncList;
|
||||||
if (!options.unsyncedfolders.isEmpty()) {
|
if (!options.unsyncedfolders.isEmpty()) {
|
||||||
QFile f(options.unsyncedfolders);
|
QFile f(options.unsyncedfolders);
|
||||||
|
|||||||
@@ -90,6 +90,21 @@ QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &
|
|||||||
return header;
|
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)
|
bool parseChecksumHeader(const QByteArray &header, QByteArray *type, QByteArray *checksum)
|
||||||
{
|
{
|
||||||
if (header.isEmpty()) {
|
if (header.isEmpty()) {
|
||||||
@@ -120,7 +135,7 @@ QByteArray parseChecksumHeaderType(const QByteArray &header)
|
|||||||
|
|
||||||
bool uploadChecksumEnabled()
|
bool uploadChecksumEnabled()
|
||||||
{
|
{
|
||||||
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD").isEmpty();
|
static bool enabled = qEnvironmentVariableIsEmpty("OWNCLOUD_DISABLE_CHECKSUM_UPLOAD");
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +148,12 @@ QByteArray contentChecksumType()
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool checksumComputationEnabled()
|
||||||
|
{
|
||||||
|
static bool enabled = qgetenv("OWNCLOUD_DISABLE_CHECKSUM_COMPUTATIONS").isEmpty();
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
ComputeChecksum::ComputeChecksum(QObject *parent)
|
ComputeChecksum::ComputeChecksum(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -150,6 +171,8 @@ QByteArray ComputeChecksum::checksumType() const
|
|||||||
|
|
||||||
void ComputeChecksum::start(const QString &filePath)
|
void ComputeChecksum::start(const QString &filePath)
|
||||||
{
|
{
|
||||||
|
qCInfo(lcChecksums) << "Computing" << checksumType() << "checksum of" << filePath << "in a thread";
|
||||||
|
|
||||||
// Calculate the checksum in a different thread first.
|
// Calculate the checksum in a different thread first.
|
||||||
connect(&_watcher, &QFutureWatcherBase::finished,
|
connect(&_watcher, &QFutureWatcherBase::finished,
|
||||||
this, &ComputeChecksum::slotCalculationDone,
|
this, &ComputeChecksum::slotCalculationDone,
|
||||||
@@ -159,6 +182,11 @@ void ComputeChecksum::start(const QString &filePath)
|
|||||||
|
|
||||||
QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray &checksumType)
|
QByteArray ComputeChecksum::computeNow(const QString &filePath, const QByteArray &checksumType)
|
||||||
{
|
{
|
||||||
|
if (!checksumComputationEnabled()) {
|
||||||
|
qCWarning(lcChecksums) << "Checksum computation disabled by environment variable";
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
if (checksumType == checkSumMD5C) {
|
if (checksumType == checkSumMD5C) {
|
||||||
return FileSystem::calcMd5(filePath);
|
return FileSystem::calcMd5(filePath);
|
||||||
} else if (checksumType == checkSumSHA1C) {
|
} else if (checksumType == checkSumSHA1C) {
|
||||||
@@ -237,6 +265,7 @@ QByteArray CSyncChecksumHook::hook(const QByteArray &path, const QByteArray &oth
|
|||||||
if (type.isEmpty())
|
if (type.isEmpty())
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
qCInfo(lcChecksums) << "Computing" << type << "checksum of" << path << "in the csync hook";
|
||||||
QByteArray checksum = ComputeChecksum::computeNow(QString::fromUtf8(path), type);
|
QByteArray checksum = ComputeChecksum::computeNow(QString::fromUtf8(path), type);
|
||||||
if (checksum.isNull()) {
|
if (checksum.isNull()) {
|
||||||
qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path;
|
qCWarning(lcChecksums) << "Failed to compute checksum" << type << "for" << path;
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ static const char checkSumAdlerC[] = "Adler32";
|
|||||||
|
|
||||||
class SyncJournalDb;
|
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.
|
/// Creates a checksum header from type and value.
|
||||||
OCSYNC_EXPORT QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum);
|
OCSYNC_EXPORT QByteArray makeChecksumHeader(const QByteArray &checksumType, const QByteArray &checksum);
|
||||||
|
|
||||||
|
|||||||
@@ -358,26 +358,19 @@ QString FileSystem::fileSystemForPath(const QString &path)
|
|||||||
|
|
||||||
#define BUFSIZE qint64(500 * 1024) // 500 KiB
|
#define BUFSIZE qint64(500 * 1024) // 500 KiB
|
||||||
|
|
||||||
static QByteArray readToCrypto(const QString &filename, QCryptographicHash::Algorithm algo)
|
static QByteArray readToCrypto( const QString& filename, QCryptographicHash::Algorithm algo )
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
const qint64 bufSize = qMin(BUFSIZE, file.size() + 1);
|
QByteArray arr;
|
||||||
QByteArray buf(bufSize, Qt::Uninitialized);
|
QCryptographicHash crypto( algo );
|
||||||
QByteArray arr;
|
|
||||||
QCryptographicHash crypto(algo);
|
|
||||||
|
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
qint64 size;
|
if (crypto.addData(&file)) {
|
||||||
while (!file.atEnd()) {
|
arr = crypto.result().toHex();
|
||||||
size = file.read(buf.data(), bufSize);
|
}
|
||||||
if (size > 0) {
|
}
|
||||||
crypto.addData(buf.data(), size);
|
return arr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
arr = crypto.result().toHex();
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray FileSystem::calcMd5(const QString &filename)
|
QByteArray FileSystem::calcMd5(const QString &filename)
|
||||||
{
|
{
|
||||||
@@ -485,4 +478,22 @@ bool FileSystem::isLnkFile(const QString &filename)
|
|||||||
return filename.endsWith(".lnk");
|
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
|
} // namespace OCC
|
||||||
|
|||||||
@@ -141,8 +141,16 @@ namespace FileSystem {
|
|||||||
*/
|
*/
|
||||||
bool OCSYNC_EXPORT isFileLocked(const QString &fileName);
|
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);
|
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
|
* 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
|
* string. That means that it prepends a \\?\ (unless already UNC) and converts
|
||||||
|
|||||||
@@ -85,24 +85,32 @@ bool SqlDatabase::openHelper(const QString &filename, int sqliteFlags)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SqlDatabase::checkDb()
|
SqlDatabase::CheckDbResult SqlDatabase::checkDb()
|
||||||
{
|
{
|
||||||
// quick_check can fail with a disk IO error when diskspace is low
|
// quick_check can fail with a disk IO error when diskspace is low
|
||||||
SqlQuery quick_check(*this);
|
SqlQuery quick_check(*this);
|
||||||
quick_check.prepare("PRAGMA quick_check;", /*allow_failure=*/true);
|
|
||||||
|
if (quick_check.prepare("PRAGMA quick_check;", /*allow_failure=*/true) != SQLITE_OK) {
|
||||||
|
qCWarning(lcSql) << "Error preparing quick_check on database";
|
||||||
|
_errId = quick_check.errorId();
|
||||||
|
_error = quick_check.error();
|
||||||
|
return CheckDbResult::CantPrepare;
|
||||||
|
}
|
||||||
if (!quick_check.exec()) {
|
if (!quick_check.exec()) {
|
||||||
qCWarning(lcSql) << "Error running quick_check on database";
|
qCWarning(lcSql) << "Error running quick_check on database";
|
||||||
return false;
|
_errId = quick_check.errorId();
|
||||||
|
_error = quick_check.error();
|
||||||
|
return CheckDbResult::CantExec;
|
||||||
}
|
}
|
||||||
|
|
||||||
quick_check.next();
|
quick_check.next();
|
||||||
QString result = quick_check.stringValue(0);
|
QString result = quick_check.stringValue(0);
|
||||||
if (result != "ok") {
|
if (result != "ok") {
|
||||||
qCWarning(lcSql) << "quick_check returned failure:" << result;
|
qCWarning(lcSql) << "quick_check returned failure:" << result;
|
||||||
return false;
|
return CheckDbResult::NotOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return CheckDbResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SqlDatabase::openOrCreateReadWrite(const QString &filename)
|
bool SqlDatabase::openOrCreateReadWrite(const QString &filename)
|
||||||
@@ -115,13 +123,25 @@ bool SqlDatabase::openOrCreateReadWrite(const QString &filename)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkDb()) {
|
auto checkResult = checkDb();
|
||||||
// When disk space is low, checking the db may fail even though it's fine.
|
if (checkResult != CheckDbResult::Ok) {
|
||||||
qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath());
|
if (checkResult == CheckDbResult::CantPrepare) {
|
||||||
if (freeSpace != -1 && freeSpace < 1000000) {
|
// When disk space is low, preparing may fail even though the db is fine.
|
||||||
qCWarning(lcSql) << "Consistency check failed, disk space is low, aborting" << freeSpace;
|
// Typically CANTOPEN or IOERR.
|
||||||
close();
|
qint64 freeSpace = Utility::freeDiskSpace(QFileInfo(filename).dir().absolutePath());
|
||||||
return false;
|
if (freeSpace != -1 && freeSpace < 1000000) {
|
||||||
|
qCWarning(lcSql) << "Can't prepare consistency check and disk space is low:" << freeSpace;
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even when there's enough disk space, it might very well be that the
|
||||||
|
// file is on a read-only filesystem and can't be opened because of that.
|
||||||
|
if (_errId == SQLITE_CANTOPEN) {
|
||||||
|
qCWarning(lcSql) << "Can't open db to prepare consistency check, aborting";
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qCCritical(lcSql) << "Consistency check failed, removing broken db" << filename;
|
qCCritical(lcSql) << "Consistency check failed, removing broken db" << filename;
|
||||||
@@ -144,7 +164,7 @@ bool SqlDatabase::openReadOnly(const QString &filename)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkDb()) {
|
if (checkDb() != CheckDbResult::Ok) {
|
||||||
qCWarning(lcSql) << "Consistency check failed in readonly mode, giving up" << filename;
|
qCWarning(lcSql) << "Consistency check failed in readonly mode, giving up" << filename;
|
||||||
close();
|
close();
|
||||||
return false;
|
return false;
|
||||||
@@ -164,8 +184,8 @@ void SqlDatabase::close()
|
|||||||
{
|
{
|
||||||
if (_db) {
|
if (_db) {
|
||||||
SQLITE_DO(sqlite3_close(_db));
|
SQLITE_DO(sqlite3_close(_db));
|
||||||
// Fatal because reopening an unclosed db might be problematic.
|
if (_errId != SQLITE_OK)
|
||||||
ENFORCE(_errId == SQLITE_OK, "Error when closing DB");
|
qCWarning(lcSql) << "Closing database failed" << _error;
|
||||||
_db = 0;
|
_db = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,6 +344,7 @@ void SqlQuery::bindValue(int pos, const QVariant &value)
|
|||||||
break;
|
break;
|
||||||
case QVariant::UInt:
|
case QVariant::UInt:
|
||||||
case QVariant::LongLong:
|
case QVariant::LongLong:
|
||||||
|
case QVariant::ULongLong:
|
||||||
res = sqlite3_bind_int64(_stmt, pos, value.toLongLong());
|
res = sqlite3_bind_int64(_stmt, pos, value.toLongLong());
|
||||||
break;
|
break;
|
||||||
case QVariant::DateTime: {
|
case QVariant::DateTime: {
|
||||||
|
|||||||
@@ -48,8 +48,15 @@ public:
|
|||||||
sqlite3 *sqliteDb();
|
sqlite3 *sqliteDb();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class CheckDbResult {
|
||||||
|
Ok,
|
||||||
|
CantPrepare,
|
||||||
|
CantExec,
|
||||||
|
NotOk,
|
||||||
|
};
|
||||||
|
|
||||||
bool openHelper(const QString &filename, int sqliteFlags);
|
bool openHelper(const QString &filename, int sqliteFlags);
|
||||||
bool checkDb();
|
CheckDbResult checkDb();
|
||||||
|
|
||||||
sqlite3 *_db;
|
sqlite3 *_db;
|
||||||
QString _error; // last error string
|
QString _error; // last error string
|
||||||
|
|||||||
@@ -32,6 +32,13 @@
|
|||||||
|
|
||||||
#include "common/c_jhash.h"
|
#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 {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcDb, "sync.database", QtInfoMsg)
|
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)
|
static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &query)
|
||||||
{
|
{
|
||||||
rec._path = query.baValue(0);
|
rec._path = query.baValue(0);
|
||||||
rec._inode = query.intValue(1);
|
rec._inode = query.int64Value(1);
|
||||||
rec._modtime = query.int64Value(2);
|
rec._modtime = query.int64Value(2);
|
||||||
rec._type = query.intValue(3);
|
rec._type = query.intValue(3);
|
||||||
rec._etag = query.baValue(4);
|
rec._etag = query.baValue(4);
|
||||||
@@ -58,7 +65,7 @@ static void fillFileRecordFromGetQuery(SyncJournalFileRecord &rec, SqlQuery &que
|
|||||||
|
|
||||||
static QString defaultJournalMode(const QString &dbPath)
|
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
|
// See #2693: Some exFAT file systems seem unable to cope with the
|
||||||
// WAL journaling mode. They work fine with DELETE.
|
// WAL journaling mode. They work fine with DELETE.
|
||||||
QString fileSystem = FileSystem::fileSystemForPath(dbPath);
|
QString fileSystem = FileSystem::fileSystemForPath(dbPath);
|
||||||
@@ -67,6 +74,11 @@ static QString defaultJournalMode(const QString &dbPath)
|
|||||||
qCInfo(lcDb) << "Filesystem contains FAT - using DELETE journal mode";
|
qCInfo(lcDb) << "Filesystem contains FAT - using DELETE journal mode";
|
||||||
return "DELETE";
|
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
|
#else
|
||||||
Q_UNUSED(dbPath)
|
Q_UNUSED(dbPath)
|
||||||
#endif
|
#endif
|
||||||
@@ -264,6 +276,11 @@ bool SyncJournalDb::sqlFail(const QString &log, const SqlQuery &query)
|
|||||||
bool SyncJournalDb::checkConnect()
|
bool SyncJournalDb::checkConnect()
|
||||||
{
|
{
|
||||||
if (_db.isOpen()) {
|
if (_db.isOpen()) {
|
||||||
|
if (!QFile::exists(_dbFile)) {
|
||||||
|
qCWarning(lcDb) << "Database open, but file file" + _dbFile + " does not exist";
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +567,7 @@ bool SyncJournalDb::checkConnect()
|
|||||||
_getFilesBelowPathQuery.reset(new SqlQuery(_db));
|
_getFilesBelowPathQuery.reset(new SqlQuery(_db));
|
||||||
if (_getFilesBelowPathQuery->prepare(
|
if (_getFilesBelowPathQuery->prepare(
|
||||||
GET_FILE_RECORD_QUERY
|
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);
|
return sqlFail("prepare _getFilesBelowPathQuery", *_getFilesBelowPathQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,7 +637,7 @@ bool SyncJournalDb::checkConnect()
|
|||||||
}
|
}
|
||||||
|
|
||||||
_deleteFileRecordRecursively.reset(new SqlQuery(_db));
|
_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);
|
return sqlFail("prepare _deleteFileRecordRecursively", *_deleteFileRecordRecursively);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1094,15 +1111,10 @@ bool SyncJournalDb::getFileRecordByInode(quint64 inode, SyncJournalFileRecord *r
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SyncJournalDb::getFileRecordByFileId(const QByteArray &fileId, SyncJournalFileRecord *rec)
|
bool SyncJournalDb::getFileRecordsByFileId(const QByteArray &fileId, const std::function<void(const SyncJournalFileRecord &)> &rowCallback)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&_mutex);
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
// Reset the output var in case the caller is reusing it.
|
|
||||||
Q_ASSERT(rec);
|
|
||||||
rec->_path.clear();
|
|
||||||
Q_ASSERT(!rec->isValid());
|
|
||||||
|
|
||||||
if (fileId.isEmpty() || _metadataTableIsEmpty)
|
if (fileId.isEmpty() || _metadataTableIsEmpty)
|
||||||
return true; // no error, yet nothing found (rec->isValid() == false)
|
return true; // no error, yet nothing found (rec->isValid() == false)
|
||||||
|
|
||||||
@@ -1116,8 +1128,10 @@ bool SyncJournalDb::getFileRecordByFileId(const QByteArray &fileId, SyncJournalF
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_getFileRecordQueryByFileId->next()) {
|
while (_getFileRecordQueryByFileId->next()) {
|
||||||
fillFileRecordFromGetQuery(*rec, *_getFileRecordQueryByFileId);
|
SyncJournalFileRecord rec;
|
||||||
|
fillFileRecordFromGetQuery(rec, *_getFileRecordQueryByFileId);
|
||||||
|
rowCallback(rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -1783,9 +1797,8 @@ void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
SqlQuery query(_db);
|
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(1, path);
|
||||||
query.bindValue(2, path);
|
|
||||||
query.exec();
|
query.exec();
|
||||||
|
|
||||||
// We also need to remove the ETags so the update phase refreshes the directory paths
|
// We also need to remove the ETags so the update phase refreshes the directory paths
|
||||||
@@ -1795,25 +1808,28 @@ void SyncJournalDb::avoidRenamesOnNextSync(const QByteArray &path)
|
|||||||
|
|
||||||
void SyncJournalDb::avoidReadFromDbOnNextSync(const QByteArray &fileName)
|
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);
|
QMutexLocker locker(&_mutex);
|
||||||
|
|
||||||
if (!checkConnect()) {
|
if (!checkConnect()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash
|
||||||
|
auto argument = fileName;
|
||||||
|
if (argument.endsWith('/'))
|
||||||
|
argument.chop(1);
|
||||||
|
|
||||||
SqlQuery query(_db);
|
SqlQuery query(_db);
|
||||||
// This query will match entries for which the path is a prefix of fileName
|
// This query will match entries for which the path is a prefix of fileName
|
||||||
// Note: CSYNC_FTW_TYPE_DIR == 2
|
// Note: CSYNC_FTW_TYPE_DIR == 2
|
||||||
query.prepare("UPDATE metadata SET md5='_invalid_' WHERE ?1 LIKE(path||'/%') AND type == 2;");
|
query.prepare("UPDATE metadata SET md5='_invalid_' WHERE " IS_PREFIX_PATH_OR_EQUAL("path", "?1") " AND type == 2;");
|
||||||
query.bindValue(1, fileName);
|
query.bindValue(1, argument);
|
||||||
query.exec();
|
query.exec();
|
||||||
|
|
||||||
// Prevent future overwrite of the etag for this sync
|
// Prevent future overwrite of the etags of this folder and all
|
||||||
_avoidReadFromDbOnNextSyncFilter.append(fileName);
|
// parent folders for this sync
|
||||||
|
argument.append('/');
|
||||||
|
_avoidReadFromDbOnNextSyncFilter.append(argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyncJournalDb::forceRemoteDiscoveryNextSync()
|
void SyncJournalDb::forceRemoteDiscoveryNextSync()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public:
|
|||||||
bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); }
|
bool getFileRecord(const QString &filename, SyncJournalFileRecord *rec) { return getFileRecord(filename.toUtf8(), rec); }
|
||||||
bool getFileRecord(const QByteArray &filename, SyncJournalFileRecord *rec);
|
bool getFileRecord(const QByteArray &filename, SyncJournalFileRecord *rec);
|
||||||
bool getFileRecordByInode(quint64 inode, SyncJournalFileRecord *rec);
|
bool getFileRecordByInode(quint64 inode, SyncJournalFileRecord *rec);
|
||||||
bool getFileRecordByFileId(const QByteArray &fileId, SyncJournalFileRecord *rec);
|
bool getFileRecordsByFileId(const QByteArray &fileId, const std::function<void(const SyncJournalFileRecord &)> &rowCallback);
|
||||||
bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
|
bool getFilesBelowPath(const QByteArray &path, const std::function<void(const SyncJournalFileRecord&)> &rowCallback);
|
||||||
bool setFileRecord(const SyncJournalFileRecord &record);
|
bool setFileRecord(const SyncJournalFileRecord &record);
|
||||||
|
|
||||||
@@ -158,15 +158,16 @@ public:
|
|||||||
void setSelectiveSyncList(SelectiveSyncListType type, const QStringList &list);
|
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
|
* Make sure that on the next sync fileName and its parents are discovered from the server.
|
||||||
* get the info 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
|
* That causes a metadata difference and a resulting discovery from the remote for the
|
||||||
* affected folders.
|
* affected folders.
|
||||||
*
|
*
|
||||||
* Since folders in the selective sync list will not be rediscovered (csync_ftw,
|
* 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.
|
* child items in the db will be ignored when reading a remote tree from the database.
|
||||||
*/
|
*/
|
||||||
void avoidReadFromDbOnNextSync(const QString &fileName) { avoidReadFromDbOnNextSync(fileName.toUtf8()); }
|
void avoidReadFromDbOnNextSync(const QString &fileName) { avoidReadFromDbOnNextSync(fileName.toUtf8()); }
|
||||||
@@ -268,6 +269,8 @@ private:
|
|||||||
/* This is the list of paths we called avoidReadFromDbOnNextSync on.
|
/* 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
|
* 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
|
* that would write the etag and would void the purpose of avoidReadFromDbOnNextSync
|
||||||
|
*
|
||||||
|
* The contained paths have a trailing /.
|
||||||
*/
|
*/
|
||||||
QList<QByteArray> _avoidReadFromDbOnNextSyncFilter;
|
QList<QByteArray> _avoidReadFromDbOnNextSyncFilter;
|
||||||
|
|
||||||
|
|||||||
@@ -579,7 +579,7 @@ bool Utility::isConflictFile(const char *name)
|
|||||||
|
|
||||||
bool Utility::shouldUploadConflictFiles()
|
bool Utility::shouldUploadConflictFiles()
|
||||||
{
|
{
|
||||||
static bool uploadConflictFiles = qgetenv("OWNCLOUD_UPLOAD_CONFLICT_FILES").toInt() != 0;
|
static bool uploadConflictFiles = qEnvironmentVariableIntValue("OWNCLOUD_UPLOAD_CONFLICT_FILES") != 0;
|
||||||
return uploadConflictFiles;
|
return uploadConflictFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ bool hasLaunchOnStartup_private(const QString &)
|
|||||||
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
||||||
CFURLRef itemUrlRef = NULL;
|
CFURLRef itemUrlRef = NULL;
|
||||||
|
|
||||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
|
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
|
||||||
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
||||||
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
||||||
returnValue = true;
|
returnValue = true;
|
||||||
@@ -100,7 +100,7 @@ void setLaunchOnStartup_private(const QString &appName, const QString &guiName,
|
|||||||
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(itemsArray, i);
|
||||||
CFURLRef itemUrlRef = NULL;
|
CFURLRef itemUrlRef = NULL;
|
||||||
|
|
||||||
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr) {
|
if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) {
|
||||||
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
CFStringRef itemUrlString = CFURLGetString(itemUrlRef);
|
||||||
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) {
|
||||||
LSSharedFileListItemRemove(loginItems, item); // remove it!
|
LSSharedFileListItemRemove(loginItems, item); // remove it!
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
PROJECT( CrashReporter )
|
PROJECT( CrashReporter )
|
||||||
cmake_policy(SET CMP0017 NEW)
|
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
|
# TODO: differentiate release channel
|
||||||
# if(BUILD_RELEASE)
|
# if(BUILD_RELEASE)
|
||||||
# set(CRASHREPORTER_RELEASE_CHANNEL "release")
|
# set(CRASHREPORTER_RELEASE_CHANNEL "release")
|
||||||
@@ -15,9 +8,30 @@ qt_add_resources( crashreporter_RC_RCC ${crashreporter_RC} )
|
|||||||
set(CRASHREPORTER_RELEASE_CHANNEL "nightly")
|
set(CRASHREPORTER_RELEASE_CHANNEL "nightly")
|
||||||
# endif()
|
# 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
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CrashReporterConfig.h.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/CrashReporterConfig.h)
|
${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}
|
include_directories(${CMAKE_CURRENT_BINARY_DIR}
|
||||||
"../3rdparty/libcrashreporter-qt/src/"
|
"../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);
|
csync_gettime(&start);
|
||||||
ctx->current = LOCAL_REPLICA;
|
ctx->current = LOCAL_REPLICA;
|
||||||
|
|
||||||
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "## Starting local discovery ##");
|
||||||
|
|
||||||
rc = csync_ftw(ctx, ctx->local.uri, csync_walker, MAX_DEPTH);
|
rc = csync_ftw(ctx, ctx->local.uri, csync_walker, MAX_DEPTH);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
if(ctx->status_code == CSYNC_STATUS_OK) {
|
if(ctx->status_code == CSYNC_STATUS_OK) {
|
||||||
@@ -104,6 +106,8 @@ int csync_update(CSYNC *ctx) {
|
|||||||
csync_gettime(&start);
|
csync_gettime(&start);
|
||||||
ctx->current = REMOTE_REPLICA;
|
ctx->current = REMOTE_REPLICA;
|
||||||
|
|
||||||
|
CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "## Starting remote discovery ##");
|
||||||
|
|
||||||
rc = csync_ftw(ctx, "", csync_walker, MAX_DEPTH);
|
rc = csync_ftw(ctx, "", csync_walker, MAX_DEPTH);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
if(ctx->status_code == CSYNC_STATUS_OK) {
|
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()) {
|
if (other_file_it == other_tree->cend()) {
|
||||||
/* Check the renamed path as well. */
|
/* 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)
|
if (renamed_path != cur->path)
|
||||||
other_file_it = other_tree->find(renamed_path);
|
other_file_it = other_tree->find(renamed_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (other_file_it == other_tree->cend()) {
|
if (other_file_it == other_tree->cend()) {
|
||||||
/* Check the source path as well. */
|
/* 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)
|
if (renamed_path != cur->path)
|
||||||
other_file_it = other_tree->find(renamed_path);
|
other_file_it = other_tree->find(renamed_path);
|
||||||
}
|
}
|
||||||
@@ -314,6 +318,9 @@ int csync_s::reinitialize() {
|
|||||||
local.files.clear();
|
local.files.clear();
|
||||||
remote.files.clear();
|
remote.files.clear();
|
||||||
|
|
||||||
|
renames.folder_renamed_from.clear();
|
||||||
|
renames.folder_renamed_to.clear();
|
||||||
|
|
||||||
status = CSYNC_STATUS_INIT;
|
status = CSYNC_STATUS_INIT;
|
||||||
SAFE_FREE(error_string);
|
SAFE_FREE(error_string);
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ enum csync_status_codes_e {
|
|||||||
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED,
|
CSYNC_STATUS_INDIVIDUAL_STAT_FAILED,
|
||||||
CSYNC_STATUS_FORBIDDEN,
|
CSYNC_STATUS_FORBIDDEN,
|
||||||
CSYNC_STATUS_INDIVIDUAL_TOO_DEEP,
|
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;
|
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_LONG_FILENAME,
|
||||||
CSYNC_FILE_EXCLUDE_HIDDEN,
|
CSYNC_FILE_EXCLUDE_HIDDEN,
|
||||||
CSYNC_FILE_EXCLUDE_STAT_FAILED,
|
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;
|
typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE;
|
||||||
|
|
||||||
|
|||||||
@@ -63,18 +63,6 @@ static csync_file_stat_t *_csync_check_ignored(csync_s::FileMap *tree, const Byt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Returns true if we're reasonably certain that hash equality
|
|
||||||
* for the header means content equality.
|
|
||||||
*
|
|
||||||
* Cryptographic safety is not required - this is mainly
|
|
||||||
* intended to rule out checksums like Adler32 that are not intended for
|
|
||||||
* hashing and have high likelihood of collision with particular inputs.
|
|
||||||
*/
|
|
||||||
static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
|
||||||
{
|
|
||||||
return strncmp(checksum_header, "SHA1:", 5) == 0
|
|
||||||
|| strncmp(checksum_header, "MD5:", 4) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main function in the reconcile pass.
|
* The main function in the reconcile pass.
|
||||||
@@ -104,14 +92,17 @@ static bool _csync_is_collision_safe_hash(const char *checksum_header)
|
|||||||
* source and the destination, have been changed, the newer file wins.
|
* source and the destination, have been changed, the newer file wins.
|
||||||
*/
|
*/
|
||||||
static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
||||||
|
csync_s::FileMap *our_tree = nullptr;
|
||||||
csync_s::FileMap *other_tree = nullptr;
|
csync_s::FileMap *other_tree = nullptr;
|
||||||
|
|
||||||
/* we need the opposite tree! */
|
/* we need the opposite tree! */
|
||||||
switch (ctx->current) {
|
switch (ctx->current) {
|
||||||
case LOCAL_REPLICA:
|
case LOCAL_REPLICA:
|
||||||
|
our_tree = &ctx->local.files;
|
||||||
other_tree = &ctx->remote.files;
|
other_tree = &ctx->remote.files;
|
||||||
break;
|
break;
|
||||||
case REMOTE_REPLICA:
|
case REMOTE_REPLICA:
|
||||||
|
our_tree = &ctx->remote.files;
|
||||||
other_tree = &ctx->local.files;
|
other_tree = &ctx->local.files;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -122,7 +113,7 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||||||
|
|
||||||
if (!other) {
|
if (!other) {
|
||||||
/* Check the renamed path as well. */
|
/* 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) {
|
if (!other) {
|
||||||
/* Check if it is ignored */
|
/* Check if it is ignored */
|
||||||
@@ -152,65 +143,123 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||||||
cur->instruction = CSYNC_INSTRUCTION_REMOVE;
|
cur->instruction = CSYNC_INSTRUCTION_REMOVE;
|
||||||
break;
|
break;
|
||||||
case CSYNC_INSTRUCTION_EVAL_RENAME: {
|
case CSYNC_INSTRUCTION_EVAL_RENAME: {
|
||||||
OCC::SyncJournalFileRecord base;
|
// By default, the EVAL_RENAME decays into a NEW
|
||||||
if(ctx->current == LOCAL_REPLICA ) {
|
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||||
/* use the old name to find the "other" node */
|
|
||||||
ctx->statedb->getFileRecordByInode(cur->inode, &base);
|
bool processedRename = false;
|
||||||
qCDebug(lcReconcile, "Finding opposite temp through inode %" PRIu64 ": %s",
|
auto renameCandidateProcessing = [&](const QByteArray &basePath) {
|
||||||
cur->inode, base.isValid() ? "true":"false");
|
if (processedRename)
|
||||||
} else {
|
return;
|
||||||
ASSERT( ctx->current == REMOTE_REPLICA );
|
if (basePath.isEmpty())
|
||||||
ctx->statedb->getFileRecordByFileId(cur->file_id, &base);
|
return;
|
||||||
qCDebug(lcReconcile, "Finding opposite temp through file ID %s: %s",
|
|
||||||
cur->file_id.constData(), base.isValid() ? "true":"false");
|
|
||||||
}
|
|
||||||
|
|
||||||
if( base.isValid() ) {
|
|
||||||
/* First, check that the file is NOT in our tree (another file with the same name was added) */
|
/* First, check that the file is NOT in our tree (another file with the same name was added) */
|
||||||
csync_s::FileMap *our_tree = ctx->current == REMOTE_REPLICA ? &ctx->remote.files : &ctx->local.files;
|
if (our_tree->findFile(basePath)) {
|
||||||
if (our_tree->findFile(base._path)) {
|
other = nullptr;
|
||||||
qCDebug(lcReconcile, "Origin found in our tree : %s", base._path.constData());
|
qCInfo(lcReconcile, "Origin found in our tree : %s", basePath.constData());
|
||||||
} else {
|
} else {
|
||||||
/* Find the temporar file in the other tree.
|
/* 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
|
* 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.
|
* is not longer existing there, maybe because it was renamed or deleted.
|
||||||
* The journal is cleaned up later after propagation.
|
* The journal is cleaned up later after propagation.
|
||||||
*/
|
*/
|
||||||
other = other_tree->findFile(base._path);
|
other = other_tree->findFile(basePath);
|
||||||
qCDebug(lcReconcile, "Temporary opposite (%s) %s",
|
qCInfo(lcReconcile, "Rename origin in other tree (%s) %s",
|
||||||
base._path.constData() , other ? "found": "not found" );
|
basePath.constData(), other ? "found" : "not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!other) {
|
if(!other) {
|
||||||
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
// Stick with the NEW
|
||||||
} else if (other->instruction == CSYNC_INSTRUCTION_NONE
|
return;
|
||||||
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|
} else if (other->instruction == CSYNC_INSTRUCTION_RENAME) {
|
||||||
|| cur->type == CSYNC_FTW_TYPE_DIR) {
|
// Some other EVAL_RENAME already claimed other.
|
||||||
|
// We do nothing: maybe a different candidate for
|
||||||
|
// other is found as well?
|
||||||
|
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
|
||||||
|
// have either NONE or UPDATE_METADATA if the remote file is safe to
|
||||||
|
// move.
|
||||||
|
// In the remote replica, REMOVE is also valid (local has already
|
||||||
|
// been reconciled). NONE can still happen if the whole parent dir
|
||||||
|
// was set to REMOVE by the local reconcile.
|
||||||
|
|| other->instruction == CSYNC_INSTRUCTION_NONE
|
||||||
|
|| other->instruction == CSYNC_INSTRUCTION_UPDATE_METADATA
|
||||||
|
|| other->instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||||
|
qCInfo(lcReconcile, "Switching %s to RENAME to %s",
|
||||||
|
other->path.constData(), cur->path.constData());
|
||||||
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
||||||
other->rename_path = cur->path;
|
other->rename_path = cur->path;
|
||||||
if( !cur->file_id.isEmpty() ) {
|
if( !cur->file_id.isEmpty() ) {
|
||||||
other->file_id = cur->file_id;
|
other->file_id = cur->file_id;
|
||||||
}
|
}
|
||||||
|
if (ctx->current == LOCAL_REPLICA) {
|
||||||
|
// Keep the local mtime.
|
||||||
|
other->modtime = cur->modtime;
|
||||||
|
}
|
||||||
other->inode = cur->inode;
|
other->inode = cur->inode;
|
||||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||||
} else if (other->instruction == CSYNC_INSTRUCTION_REMOVE) {
|
// We have consumed 'other': exit this loop to not consume another one.
|
||||||
other->instruction = CSYNC_INSTRUCTION_RENAME;
|
processedRename = true;
|
||||||
other->rename_path = cur->path;
|
} 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
|
||||||
|
// since the parent directory rename will already deal with it.
|
||||||
|
|
||||||
if( !cur->file_id.isEmpty() ) {
|
// Local: The remote reconcile will be able to deal with this.
|
||||||
other->file_id = cur->file_id;
|
// Remote: The local replica has already dealt with this.
|
||||||
}
|
// See the EVAL_RENAME case when other was found directly.
|
||||||
other->inode = cur->inode;
|
qCInfo(lcReconcile, "File in a renamed directory, other side's instruction: %d",
|
||||||
|
other->instruction);
|
||||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
||||||
} else if (other->instruction == CSYNC_INSTRUCTION_NEW) {
|
|
||||||
qCDebug(lcReconcile, "OOOO=> NEW detected in other tree!");
|
|
||||||
cur->instruction = CSYNC_INSTRUCTION_CONFLICT;
|
|
||||||
} else {
|
} else {
|
||||||
assert(other->type != CSYNC_FTW_TYPE_DIR);
|
// This can, for instance, happen when there was a local change in other
|
||||||
cur->instruction = CSYNC_INSTRUCTION_NONE;
|
// and the instruction in the local tree is NEW while cur has EVAL_RENAME
|
||||||
other->instruction = CSYNC_INSTRUCTION_SYNC;
|
// due to a remote move of the same file. In these scenarios we just
|
||||||
|
// want the instruction to stay NEW.
|
||||||
|
qCInfo(lcReconcile, "Other already has instruction %d",
|
||||||
|
other->instruction);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ctx->current == LOCAL_REPLICA) {
|
||||||
|
/* use the old name to find the "other" node */
|
||||||
|
OCC::SyncJournalFileRecord base;
|
||||||
|
qCInfo(lcReconcile, "Finding rename origin through inode %" PRIu64 "",
|
||||||
|
cur->inode);
|
||||||
|
ctx->statedb->getFileRecordByInode(cur->inode, &base);
|
||||||
|
renameCandidateProcessing(base._path);
|
||||||
|
} else {
|
||||||
|
ASSERT(ctx->current == REMOTE_REPLICA);
|
||||||
|
|
||||||
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -263,14 +312,14 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||||||
//
|
//
|
||||||
// In older client versions we always treated these cases as a
|
// In older client versions we always treated these cases as a
|
||||||
// non-conflict. This behavior is preserved in case the server
|
// non-conflict. This behavior is preserved in case the server
|
||||||
// doesn't provide a suitable content hash.
|
// doesn't provide a content checksum.
|
||||||
//
|
//
|
||||||
// When it does have one, however, we do create a job, but the job
|
// When it does have one, however, we do create a job, but the job
|
||||||
// will compare hashes and avoid the download if they are equal.
|
// will compare hashes and avoid the download if possible.
|
||||||
const char *remoteChecksumHeader =
|
QByteArray remoteChecksumHeader =
|
||||||
(ctx->current == REMOTE_REPLICA ? cur->checksumHeader : other->checksumHeader);
|
(ctx->current == REMOTE_REPLICA ? cur->checksumHeader : other->checksumHeader);
|
||||||
if (remoteChecksumHeader) {
|
if (!remoteChecksumHeader.isEmpty()) {
|
||||||
is_conflict |= _csync_is_collision_safe_hash(remoteChecksumHeader);
|
is_conflict = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SO: If there is no checksum, we can have !is_conflict here
|
// SO: If there is no checksum, we can have !is_conflict here
|
||||||
@@ -310,10 +359,19 @@ static int _csync_merge_algorithm_visitor(csync_file_stat_t *cur, CSYNC * ctx) {
|
|||||||
break;
|
break;
|
||||||
case CSYNC_INSTRUCTION_IGNORE:
|
case CSYNC_INSTRUCTION_IGNORE:
|
||||||
cur->instruction = CSYNC_INSTRUCTION_IGNORE;
|
cur->instruction = CSYNC_INSTRUCTION_IGNORE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Ensure we're not leaving discovery-only instructions
|
||||||
|
// in place. This can happen, for instance, when other's
|
||||||
|
// instruction is EVAL_RENAME because the parent dir was renamed.
|
||||||
|
// NEW is safer than EVAL because it will end up with
|
||||||
|
// propagation unless it's changed by something, and EVAL and
|
||||||
|
// NEW are treated equivalently during reconcile.
|
||||||
|
if (cur->instruction == CSYNC_INSTRUCTION_EVAL)
|
||||||
|
cur->instruction = CSYNC_INSTRUCTION_NEW;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ void csync_rename_record(CSYNC* ctx, const QByteArray &from, const QByteArray &t
|
|||||||
ctx->renames.folder_renamed_from[to] = from;
|
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())
|
if (ctx->renames.folder_renamed_to.empty())
|
||||||
return path;
|
return path;
|
||||||
@@ -50,11 +50,25 @@ QByteArray csync_rename_adjust_path(CSYNC* ctx, const QByteArray &path)
|
|||||||
return 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())
|
if (ctx->renames.folder_renamed_from.empty())
|
||||||
return path;
|
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);
|
auto it = ctx->renames.folder_renamed_from.find(p);
|
||||||
if (it != ctx->renames.folder_renamed_from.end()) {
|
if (it != ctx->renames.folder_renamed_from.end()) {
|
||||||
QByteArray rep = it->second + path.mid(p.length());
|
QByteArray rep = it->second + path.mid(p.length());
|
||||||
|
|||||||
@@ -22,10 +22,19 @@
|
|||||||
|
|
||||||
#include "csync.h"
|
#include "csync.h"
|
||||||
|
|
||||||
/* Return the final destination path of a given patch in case of renames */
|
/* 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);
|
*
|
||||||
|
* 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 */
|
/* 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);
|
void OCSYNC_EXPORT csync_rename_record(CSYNC *ctx, const QByteArray &from, const QByteArray &to);
|
||||||
/* Return the amount of renamed item recorded */
|
/* Return the amount of renamed item recorded */
|
||||||
bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);
|
bool OCSYNC_EXPORT csync_rename_count(CSYNC *ctx);
|
||||||
|
|||||||
@@ -46,6 +46,8 @@
|
|||||||
#include "common/utility.h"
|
#include "common/utility.h"
|
||||||
#include "common/asserts.h"
|
#include "common/asserts.h"
|
||||||
|
|
||||||
|
#include <QtCore/QTextCodec>
|
||||||
|
|
||||||
// Needed for PRIu64 on MinGW in C++ mode.
|
// Needed for PRIu64 on MinGW in C++ mode.
|
||||||
#define __STDC_FORMAT_MACROS
|
#define __STDC_FORMAT_MACROS
|
||||||
#include <inttypes.h>
|
#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.
|
* This code should probably be in csync_exclude, but it does not have the fs parameter.
|
||||||
* Keep it here for now */
|
* Keep it here for now */
|
||||||
if (ctx->ignore_hidden_files && (fs->is_hidden)) {
|
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;
|
excluded = CSYNC_FILE_EXCLUDE_HIDDEN;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* File is ignored because it's matched by a user- or system exclude pattern. */
|
/* 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) {
|
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) {
|
||||||
return 1;
|
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->type == CSYNC_FTW_TYPE_FILE ) {
|
||||||
if (fs->modtime == 0) {
|
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! */
|
/* we have an update! */
|
||||||
qCInfo(lcUpdate, "Database entry found, compare: %" PRId64 " <-> %" PRId64
|
qCInfo(lcUpdate, "Database entry found, compare: %" PRId64 " <-> %" PRId64
|
||||||
", etag: %s <-> %s, inode: %" 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),
|
((int64_t) fs->modtime), ((int64_t) base._modtime),
|
||||||
fs->etag.constData(), base._etag.constData(), (uint64_t) fs->inode, (uint64_t) base._inode,
|
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) {
|
if (ctx->current == REMOTE_REPLICA && fs->etag != base._etag) {
|
||||||
fs->instruction = CSYNC_INSTRUCTION_EVAL;
|
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;
|
checksumIdentical = fs->checksumHeader == base._checksumHeader;
|
||||||
}
|
}
|
||||||
if (checksumIdentical) {
|
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;
|
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||||
goto out;
|
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
|
* The metadata comparison ensure that we fetch all the file id or permission when
|
||||||
* upgrading owncloud
|
* 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;
|
ctx->remote.read_from_db = true;
|
||||||
}
|
}
|
||||||
/* If it was remembered in the db that the remote dir has ignored files, store
|
/* 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) {
|
if (metadata_differ) {
|
||||||
/* file id or permissions has changed. Which means we need to update them in the DB. */
|
/* 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;
|
fs->instruction = CSYNC_INSTRUCTION_UPDATE_METADATA;
|
||||||
} else {
|
} else {
|
||||||
fs->instruction = CSYNC_INSTRUCTION_NONE;
|
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 {
|
} else {
|
||||||
/* check if it's a file and has been renamed */
|
/* check if it's a file and has been renamed */
|
||||||
if (ctx->current == LOCAL_REPLICA) {
|
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;
|
OCC::SyncJournalFileRecord base;
|
||||||
if(!ctx->statedb->getFileRecordByInode(fs->inode, &base)) {
|
if(!ctx->statedb->getFileRecordByInode(fs->inode, &base)) {
|
||||||
@@ -265,8 +285,8 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
|||||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||||
|
|
||||||
bool isRename =
|
bool isRename =
|
||||||
base.isValid() && base._inode == fs->inode && base._type == fs->type
|
base.isValid() && base._type == fs->type
|
||||||
&& (base._modtime == fs->modtime || fs->type == CSYNC_FTW_TYPE_DIR)
|
&& ((base._modtime == fs->modtime && base._fileSize == fs->size) || fs->type == CSYNC_FTW_TYPE_DIR)
|
||||||
#ifdef NO_RENAME_EXTENSION
|
#ifdef NO_RENAME_EXTENSION
|
||||||
&& _csync_sameextension(base._path, fs->path)
|
&& _csync_sameextension(base._path, fs->path)
|
||||||
#endif
|
#endif
|
||||||
@@ -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,
|
_rel_to_abs(ctx, fs->path), base._checksumHeader,
|
||||||
ctx->callbacks.checksum_userdata);
|
ctx->callbacks.checksum_userdata);
|
||||||
if (!fs->checksumHeader.isEmpty()) {
|
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;
|
isRename = fs->checksumHeader == base._checksumHeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRename) {
|
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 */
|
/* inode found so the file has been renamed */
|
||||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||||
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
||||||
@@ -296,42 +316,63 @@ static int _csync_detect_update(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> f
|
|||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
qCInfo(lcUpdate, "Checking for rename based on fileid %s", fs->file_id.constData());
|
||||||
|
|
||||||
/* Remote Replica Rename check */
|
/* Remote Replica Rename check */
|
||||||
OCC::SyncJournalFileRecord base;
|
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
||||||
if(!ctx->statedb->getFileRecordByFileId(fs->file_id, &base)) {
|
|
||||||
|
bool done = false;
|
||||||
|
auto renameCandidateProcessing = [&](const OCC::SyncJournalFileRecord &base) {
|
||||||
|
if (done)
|
||||||
|
return;
|
||||||
|
if (!base.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Some things prohibit rename detection entirely.
|
||||||
|
// Since we don't do the same checks again in reconcile, we can't
|
||||||
|
// just skip the candidate, but have to give up completely.
|
||||||
|
if (base._type != fs->type) {
|
||||||
|
qCWarning(lcUpdate, "file types different, not a rename");
|
||||||
|
done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fs->type != CSYNC_FTW_TYPE_DIR && base._etag != fs->etag) {
|
||||||
|
/* File with different etag, don't do a rename, but download the file again */
|
||||||
|
qCWarning(lcUpdate, "file etag different, not a rename");
|
||||||
|
done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record directory renames
|
||||||
|
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
||||||
|
// If the same folder was already renamed by a different entry,
|
||||||
|
// skip to the next candidate
|
||||||
|
if (ctx->renames.folder_renamed_to.count(base._path) > 0) {
|
||||||
|
qCWarning(lcUpdate, "folder already has a rename entry, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
csync_rename_record(ctx, base._path, fs->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCInfo(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
||||||
|
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
||||||
|
done = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ctx->statedb->getFileRecordsByFileId(fs->file_id, renameCandidateProcessing)) {
|
||||||
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
|
ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (base.isValid()) { /* tmp existing at all */
|
|
||||||
if (base._type != fs->type) {
|
|
||||||
qCWarning(lcUpdate, "file types different is not!");
|
|
||||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
qCDebug(lcUpdate, "remote rename detected based on fileid %s --> %s", base._path.constData(), fs->path.constData());
|
|
||||||
fs->instruction = CSYNC_INSTRUCTION_EVAL_RENAME;
|
|
||||||
if (fs->type == CSYNC_FTW_TYPE_DIR) {
|
|
||||||
csync_rename_record(ctx, base._path, fs->path);
|
|
||||||
} else {
|
|
||||||
if( base._etag != fs->etag ) {
|
|
||||||
/* CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "ETags are different!"); */
|
|
||||||
/* File with different etag, don't do a rename, but download the file again */
|
|
||||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
} else {
|
if (fs->instruction == CSYNC_INSTRUCTION_NEW
|
||||||
/* file not found in statedb */
|
&& fs->type == CSYNC_FTW_TYPE_DIR
|
||||||
fs->instruction = CSYNC_INSTRUCTION_NEW;
|
&& ctx->current == REMOTE_REPLICA
|
||||||
|
&& ctx->callbacks.checkSelectiveSyncNewFolderHook) {
|
||||||
if (fs->type == CSYNC_FTW_TYPE_DIR && ctx->current == REMOTE_REPLICA && ctx->callbacks.checkSelectiveSyncNewFolderHook) {
|
if (ctx->callbacks.checkSelectiveSyncNewFolderHook(ctx->callbacks.update_callback_userdata, fs->path, fs->remotePerm)) {
|
||||||
if (ctx->callbacks.checkSelectiveSyncNewFolderHook(ctx->callbacks.update_callback_userdata, fs->path, fs->remotePerm)) {
|
return 1;
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
goto out;
|
|
||||||
}
|
}
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,6 +397,8 @@ out:
|
|||||||
fs->error_status = CSYNC_STATUS_INDIVIDUAL_STAT_FAILED;
|
fs->error_status = CSYNC_STATUS_INDIVIDUAL_STAT_FAILED;
|
||||||
} else if (excluded == CSYNC_FILE_EXCLUDE_CONFLICT) {
|
} else if (excluded == CSYNC_FILE_EXCLUDE_CONFLICT) {
|
||||||
fs->error_status = CSYNC_STATUS_INDIVIDUAL_IS_CONFLICT_FILE;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,11 +462,11 @@ int csync_walker(CSYNC *ctx, std::unique_ptr<csync_file_stat_t> fs) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CSYNC_FTW_TYPE_SLINK:
|
case CSYNC_FTW_TYPE_SLINK:
|
||||||
qCDebug(lcUpdate, "symlink: %s - not supported", fs->path.constData());
|
qCInfo(lcUpdate, "symlink: %s - not supported", fs->path.constData());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
qCInfo(lcUpdate, "item: %s - item type %d not iterated", fs->path.constData(), fs->type);
|
||||||
return 0;
|
return 0;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = _csync_detect_update(ctx, std::move(fs));
|
rc = _csync_detect_update(ctx, std::move(fs));
|
||||||
@@ -444,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.
|
* their correct etags again and we don't run into this case.
|
||||||
*/
|
*/
|
||||||
if( rec._etag == "_invalid_") {
|
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 = rec._path;
|
||||||
skipbase += '/';
|
skipbase += '/';
|
||||||
return;
|
return;
|
||||||
@@ -466,7 +509,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
|||||||
* without a full remote discovery being triggered. */
|
* without a full remote discovery being triggered. */
|
||||||
CSYNC_EXCLUDE_TYPE excluded = csync_excluded_traversal(ctx, st->path, st->type);
|
CSYNC_EXCLUDE_TYPE excluded = csync_excluded_traversal(ctx, st->path, st->type);
|
||||||
if (excluded != CSYNC_NOT_EXCLUDED) {
|
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
|
if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE
|
||||||
|| excluded == CSYNC_FILE_SILENTLY_EXCLUDED) {
|
|| excluded == CSYNC_FILE_SILENTLY_EXCLUDED) {
|
||||||
@@ -485,7 +528,7 @@ static bool fill_tree_from_db(CSYNC *ctx, const char *uri)
|
|||||||
ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR;
|
ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR;
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -616,7 +659,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||||||
* local stat function.
|
* local stat function.
|
||||||
*/
|
*/
|
||||||
if( filename[0] == '.' ) {
|
if( filename[0] == '.' ) {
|
||||||
if (filename == ".sys.admin#recall#") { /* recall file shall not be ignored (#4420) */
|
if (filename != ".sys.admin#recall#") { /* recall file shall not be ignored (#4420) */
|
||||||
dirent->is_hidden = true;
|
dirent->is_hidden = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,7 +724,7 @@ int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
csync_vio_closedir(ctx, dh);
|
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;
|
return rc;
|
||||||
|
|
||||||
|
|||||||
@@ -211,3 +211,10 @@ time_t oc_httpdate_parse( const char *date ) {
|
|||||||
result = timegm(&gmt);
|
result = timegm(&gmt);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool csync_is_collision_safe_hash(const QByteArray &checksum_header)
|
||||||
|
{
|
||||||
|
return checksum_header.startsWith("SHA1:")
|
||||||
|
|| checksum_header.startsWith("MD5:");
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,4 +31,14 @@ const char OCSYNC_EXPORT *csync_instruction_str(enum csync_instructions_e instr)
|
|||||||
void OCSYNC_EXPORT csync_memstat_check(void);
|
void OCSYNC_EXPORT csync_memstat_check(void);
|
||||||
|
|
||||||
bool OCSYNC_EXPORT csync_file_locked_or_open( const char *dir, const char *fname);
|
bool OCSYNC_EXPORT csync_file_locked_or_open( const char *dir, const char *fname);
|
||||||
|
|
||||||
|
/* Returns true if we're reasonably certain that hash equality
|
||||||
|
* for the header means content equality.
|
||||||
|
*
|
||||||
|
* Cryptographic safety is not required - this is mainly
|
||||||
|
* intended to rule out checksums like Adler32 that are not intended for
|
||||||
|
* hashing and have high likelihood of collision with particular inputs.
|
||||||
|
*/
|
||||||
|
bool OCSYNC_EXPORT csync_is_collision_safe_hash(const QByteArray &checksum_header);
|
||||||
|
|
||||||
#endif /* _CSYNC_UTIL_H */
|
#endif /* _CSYNC_UTIL_H */
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ std::unique_ptr<csync_file_stat_t> csync_vio_local_readdir(csync_vio_handle_t *d
|
|||||||
}
|
}
|
||||||
} else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DEVICE
|
} else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DEVICE
|
||||||
|| handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE
|
|| handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE
|
||||||
|| handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) {
|
) {
|
||||||
file_stat->type = CSYNC_FTW_TYPE_SKIP;
|
file_stat->type = CSYNC_FTW_TYPE_SKIP;
|
||||||
} else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
} else if (handle->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
file_stat->type = CSYNC_FTW_TYPE_DIR;
|
file_stat->type = CSYNC_FTW_TYPE_DIR;
|
||||||
|
|||||||
@@ -129,13 +129,14 @@ set(updater_SRCS
|
|||||||
IF( APPLE )
|
IF( APPLE )
|
||||||
list(APPEND client_SRCS cocoainitializer_mac.mm)
|
list(APPEND client_SRCS cocoainitializer_mac.mm)
|
||||||
list(APPEND client_SRCS settingsdialogmac.cpp)
|
list(APPEND client_SRCS settingsdialogmac.cpp)
|
||||||
|
list(REMOVE_ITEM client_SRCS settingsdialog.cpp)
|
||||||
list(APPEND client_SRCS socketapisocket_mac.mm)
|
list(APPEND client_SRCS socketapisocket_mac.mm)
|
||||||
list(APPEND client_SRCS systray.mm)
|
list(APPEND client_SRCS systray.mm)
|
||||||
|
|
||||||
if(SPARKLE_FOUND)
|
if(SPARKLE_FOUND)
|
||||||
# Define this, we need to check in updater.cpp
|
# Define this, we need to check in updater.cpp
|
||||||
add_definitions( -DHAVE_SPARKLE )
|
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()
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
@@ -242,8 +243,9 @@ if (NOT NO_SHIBBOLETH)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pie -fPIE")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pie -fPIE")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
|
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE)
|
||||||
|
|||||||
@@ -263,7 +263,6 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_model->classify(index) == FolderStatusModel::SubFolder) {
|
if (_model->classify(index) == FolderStatusModel::SubFolder) {
|
||||||
QTreeView *tv = ui->_folderList;
|
|
||||||
QMenu *menu = new QMenu(tv);
|
QMenu *menu = new QMenu(tv);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
@@ -275,8 +274,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||||||
ac->setEnabled(false);
|
ac->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->exec(QCursor::pos());
|
menu->popup(tv->mapToGlobal(pos));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +289,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
|||||||
auto folderMan = FolderMan::instance();
|
auto folderMan = FolderMan::instance();
|
||||||
|
|
||||||
QMenu *menu = new QMenu(tv);
|
QMenu *menu = new QMenu(tv);
|
||||||
|
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
QAction *ac = menu->addAction(tr("Open folder"));
|
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"));
|
ac = menu->addAction(tr("Remove folder sync connection"));
|
||||||
connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder);
|
connect(ac, &QAction::triggered, this, &AccountSettings::slotRemoveCurrentFolder);
|
||||||
menu->exec(tv->mapToGlobal(pos));
|
menu->popup(tv->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
|
void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
|
||||||
@@ -638,7 +637,11 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
Utility::escape(safeUrl.toString()));
|
Utility::escape(safeUrl.toString()));
|
||||||
QString serverWithUser = server;
|
QString serverWithUser = server;
|
||||||
if (AbstractCredentials *cred = account->credentials()) {
|
if (AbstractCredentials *cred = account->credentials()) {
|
||||||
serverWithUser = tr("%1 as <i>%2</i>").arg(server, Utility::escape(cred->user()));
|
QString user = account->davDisplayName();
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
user = cred->user();
|
||||||
|
}
|
||||||
|
serverWithUser = tr("%1 as <i>%2</i>").arg(server, Utility::escape(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == AccountState::Connected) {
|
if (state == AccountState::Connected) {
|
||||||
|
|||||||
@@ -312,10 +312,10 @@ void AccountState::slotInvalidCredentials()
|
|||||||
|
|
||||||
if (account()->credentials()->ready()) {
|
if (account()->credentials()->ready()) {
|
||||||
account()->credentials()->invalidateToken();
|
account()->credentials()->invalidateToken();
|
||||||
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
|
}
|
||||||
if (creds->refreshAccessToken())
|
if (auto creds = qobject_cast<HttpCredentials *>(account()->credentials())) {
|
||||||
return;
|
if (creds->refreshAccessToken())
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
account()->credentials()->askFromUser();
|
account()->credentials()->askFromUser();
|
||||||
}
|
}
|
||||||
@@ -361,23 +361,4 @@ std::unique_ptr<QSettings> AccountState::settings()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AccountState::shortDisplayNameForSettings(int width) const
|
|
||||||
{
|
|
||||||
QString user = account()->credentials()->user();
|
|
||||||
QString host = account()->url().host();
|
|
||||||
int port = account()->url().port();
|
|
||||||
if (port > 0 && port != 80 && port != 443) {
|
|
||||||
host.append(QLatin1Char(':'));
|
|
||||||
host.append(QString::number(port));
|
|
||||||
}
|
|
||||||
if (width > 0) {
|
|
||||||
QFont f;
|
|
||||||
QFontMetrics fm(f);
|
|
||||||
host = fm.elidedText(host, Qt::ElideMiddle, width);
|
|
||||||
user = fm.elidedText(user, Qt::ElideRight, width);
|
|
||||||
}
|
|
||||||
return user + QLatin1String("\n") + host;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -113,11 +113,6 @@ public:
|
|||||||
/** Returns a new settings object for this account, already in the right groups. */
|
/** Returns a new settings object for this account, already in the right groups. */
|
||||||
std::unique_ptr<QSettings> settings();
|
std::unique_ptr<QSettings> settings();
|
||||||
|
|
||||||
/** display name with two lines that is displayed in the settings
|
|
||||||
* If width is bigger than 0, the string will be ellided so it does not exceed that width
|
|
||||||
*/
|
|
||||||
QString shortDisplayNameForSettings(int width = 0) const;
|
|
||||||
|
|
||||||
/** Mark the timestamp when the last successful ETag check happened for
|
/** Mark the timestamp when the last successful ETag check happened for
|
||||||
* this account.
|
* this account.
|
||||||
* The checkConnectivity() method uses the timestamp to save a call to
|
* The checkConnectivity() method uses the timestamp to save a call to
|
||||||
|
|||||||
@@ -429,14 +429,13 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod
|
|||||||
}
|
}
|
||||||
|
|
||||||
endNotificationRequest(job->widget(), replyCode);
|
endNotificationRequest(job->widget(), replyCode);
|
||||||
// FIXME: remove the widget after a couple of seconds
|
|
||||||
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
||||||
|
|
||||||
// if the notification was successful start a timer that triggers
|
// if the notification was successful start a timer that triggers
|
||||||
// removal of the done widgets in a few seconds
|
// removal of the done widgets in a few seconds
|
||||||
// Add 200 millisecs to the predefined value to make sure that the timer in
|
// Add 200 millisecs to the predefined value to make sure that the timer in
|
||||||
// widget's method readyToClose() has elapsed.
|
// 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());
|
scheduleWidgetToRemove(job->widget());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,9 @@ void Application::slotownCloudWizardDone(int res)
|
|||||||
shouldSetAutoStart = shouldSetAutoStart
|
shouldSetAutoStart = shouldSetAutoStart
|
||||||
&& QCoreApplication::applicationDirPath().startsWith("/Applications/");
|
&& QCoreApplication::applicationDirPath().startsWith("/Applications/");
|
||||||
#endif
|
#endif
|
||||||
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), shouldSetAutoStart);
|
if (shouldSetAutoStart) {
|
||||||
|
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
||||||
|
}
|
||||||
|
|
||||||
_gui->slotShowSettings();
|
_gui->slotShowSettings();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,19 +30,21 @@ using namespace QKeychain;
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(lcHttpCredentialsGui, "sync.credentials.http.gui", QtInfoMsg)
|
||||||
|
|
||||||
void HttpCredentialsGui::askFromUser()
|
void HttpCredentialsGui::askFromUser()
|
||||||
{
|
{
|
||||||
// Unfortunately there's a bug that doesn't allow us to send the "is this
|
// This function can be called from AccountState::slotInvalidCredentials,
|
||||||
// OAuth2 or Basic auth?" GET request directly. Scheduling it for the event
|
// which (indirectly, through HttpCredentials::invalidateToken) schedules
|
||||||
// loop works though. See #5989.
|
// a cache wipe of the qnam. We can only execute a network job again once
|
||||||
QMetaObject::invokeMethod(this, "askFromUserAsync", Qt::QueuedConnection);
|
// the cache has been cleared, otherwise we'd interfere with the job.
|
||||||
|
QTimer::singleShot(100, this, &HttpCredentialsGui::askFromUserAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCredentialsGui::askFromUserAsync()
|
void HttpCredentialsGui::askFromUserAsync()
|
||||||
{
|
{
|
||||||
// First, we will check what kind of auth we need.
|
// First, we will check what kind of auth we need.
|
||||||
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
auto job = new DetermineAuthTypeJob(_account->sharedFromThis(), this);
|
||||||
job->setTimeout(30 * 1000);
|
|
||||||
QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
|
QObject::connect(job, &DetermineAuthTypeJob::authType, this, [this](DetermineAuthTypeJob::AuthType type) {
|
||||||
if (type == DetermineAuthTypeJob::OAuth) {
|
if (type == DetermineAuthTypeJob::OAuth) {
|
||||||
_asyncAuth.reset(new OAuth(_account, this));
|
_asyncAuth.reset(new OAuth(_account, this));
|
||||||
@@ -58,7 +60,8 @@ void HttpCredentialsGui::askFromUserAsync()
|
|||||||
// We will re-enter the event loop, so better wait the next iteration
|
// We will re-enter the event loop, so better wait the next iteration
|
||||||
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "showDialog", Qt::QueuedConnection);
|
||||||
} else {
|
} else {
|
||||||
// Network error? Unsupported auth type?
|
// Shibboleth?
|
||||||
|
qCWarning(lcHttpCredentialsGui) << "Bad http auth type:" << type;
|
||||||
emit asked();
|
emit asked();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ public:
|
|||||||
private slots:
|
private slots:
|
||||||
void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken);
|
void asyncAuthResult(OAuth::Result, const QString &user, const QString &accessToken, const QString &refreshToken);
|
||||||
void showDialog();
|
void showDialog();
|
||||||
|
void askFromUserAsync();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void authorisationLinkChanged();
|
void authorisationLinkChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_INVOKABLE void askFromUserAsync();
|
|
||||||
|
|
||||||
QScopedPointer<OAuth, QScopedPointerObjectDeleteLater<OAuth>> _asyncAuth;
|
QScopedPointer<OAuth, QScopedPointerObjectDeleteLater<OAuth>> _asyncAuth;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public:
|
|||||||
UserAgentWebPage(QObject *parent)
|
UserAgentWebPage(QObject *parent)
|
||||||
: QWebPage(parent)
|
: QWebPage(parent)
|
||||||
{
|
{
|
||||||
if (!qgetenv("OWNCLOUD_SHIBBOLETH_DEBUG").isEmpty()) {
|
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_SHIBBOLETH_DEBUG")) {
|
||||||
settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ ShibbolethWebView::ShibbolethWebView(AccountPtr account, QWidget *parent)
|
|||||||
setWindowTitle(tr("%1 - Authenticate").arg(Theme::instance()->appNameGUI()));
|
setWindowTitle(tr("%1 - Authenticate").arg(Theme::instance()->appNameGUI()));
|
||||||
|
|
||||||
// Debug view to display the cipher suite
|
// Debug view to display the cipher suite
|
||||||
if (!qgetenv("OWNCLOUD_SHIBBOLETH_DEBUG").isEmpty()) {
|
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_SHIBBOLETH_DEBUG")) {
|
||||||
// open an additional window to display some cipher debug info
|
// open an additional window to display some cipher debug info
|
||||||
QWebPage *debugPage = new UserAgentWebPage(this);
|
QWebPage *debugPage = new UserAgentWebPage(this);
|
||||||
debugPage->mainFrame()->load(QUrl("https://cc.dcsec.uni-hannover.de/"));
|
debugPage->mainFrame()->load(QUrl("https://cc.dcsec.uni-hannover.de/"));
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "creds/shibbolethcredentials.h"
|
#include "creds/shibbolethcredentials.h"
|
||||||
#include "shibboleth/shibbolethuserjob.h"
|
#include "shibboleth/shibbolethuserjob.h"
|
||||||
#include "creds/credentialscommon.h"
|
#include "creds/credentialscommon.h"
|
||||||
|
#include "creds/httpcredentialsgui.h"
|
||||||
|
|
||||||
#include "accessmanager.h"
|
#include "accessmanager.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
@@ -151,7 +152,31 @@ void ShibbolethCredentials::fetchFromKeychainHelper()
|
|||||||
|
|
||||||
void ShibbolethCredentials::askFromUser()
|
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)
|
bool ShibbolethCredentials::stillValid(QNetworkReply *reply)
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ void Folder::checkLocalPath()
|
|||||||
{
|
{
|
||||||
const QFileInfo fi(_definition.localPath);
|
const QFileInfo fi(_definition.localPath);
|
||||||
_canonicalLocalPath = fi.canonicalFilePath();
|
_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()) {
|
if (_canonicalLocalPath.isEmpty()) {
|
||||||
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
|
qCWarning(lcFolder) << "Broken symlink:" << _definition.localPath;
|
||||||
_canonicalLocalPath = _definition.localPath;
|
_canonicalLocalPath = _definition.localPath;
|
||||||
@@ -841,6 +845,11 @@ void Folder::slotTransmissionProgress(const ProgressInfo &pi)
|
|||||||
// a item is completed: count the errors and forward to the ProgressDispatcher
|
// a item is completed: count the errors and forward to the ProgressDispatcher
|
||||||
void Folder::slotItemCompleted(const SyncFileItemPtr &item)
|
void Folder::slotItemCompleted(const SyncFileItemPtr &item)
|
||||||
{
|
{
|
||||||
|
if (item->_instruction == CSYNC_INSTRUCTION_NONE || item->_instruction == CSYNC_INSTRUCTION_UPDATE_METADATA) {
|
||||||
|
// We only care about the updates that deserve to be shown in the UI
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// add new directories or remove gone away dirs to the watcher
|
// add new directories or remove gone away dirs to the watcher
|
||||||
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_NEW) {
|
if (item->isDirectory() && item->_instruction == CSYNC_INSTRUCTION_NEW) {
|
||||||
FolderMan::instance()->addMonitorPath(alias(), path() + item->_file);
|
FolderMan::instance()->addMonitorPath(alias(), path() + item->_file);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ QSize FolderStatusDelegate::sizeHint(const QStyleOptionViewItem &option,
|
|||||||
auto classif = static_cast<const FolderStatusModel *>(index.model())->classify(index);
|
auto classif = static_cast<const FolderStatusModel *>(index.model())->classify(index);
|
||||||
if (classif == FolderStatusModel::AddButton) {
|
if (classif == FolderStatusModel::AddButton) {
|
||||||
const int margins = aliasFm.height(); // same as 2*aliasMargin of paint
|
const int margins = aliasFm.height(); // same as 2*aliasMargin of paint
|
||||||
QFontMetrics fm(option.font);
|
QFontMetrics fm(qApp->font("QPushButton"));
|
||||||
QStyleOptionButton opt;
|
QStyleOptionButton opt;
|
||||||
static_cast<QStyleOption &>(opt) = option;
|
static_cast<QStyleOption &>(opt) = option;
|
||||||
opt.text = addFolderText();
|
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.setWidth(qMin(opt.rect.width(), hint.width()));
|
||||||
opt.rect.adjust(0, aliasMargin, 0, -aliasMargin);
|
opt.rect.adjust(0, aliasMargin, 0, -aliasMargin);
|
||||||
opt.rect = QStyle::visualRect(option.direction, option.rect, opt.rect);
|
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);
|
QApplication::style()->drawControl(QStyle::CE_PushButton, &opt, painter, option.widget);
|
||||||
|
painter->restore();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include <QWizardPage>
|
#include <QWizardPage>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <QEvent>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@@ -536,9 +537,11 @@ FolderWizard::FolderWizard(AccountPtr account, QWidget *parent)
|
|||||||
{
|
{
|
||||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
setPage(Page_Source, _folderWizardSourcePage);
|
setPage(Page_Source, _folderWizardSourcePage);
|
||||||
|
_folderWizardSourcePage->installEventFilter(this);
|
||||||
if (!Theme::instance()->singleSyncFolder()) {
|
if (!Theme::instance()->singleSyncFolder()) {
|
||||||
_folderWizardTargetPage = new FolderWizardRemotePath(account);
|
_folderWizardTargetPage = new FolderWizardRemotePath(account);
|
||||||
setPage(Page_Target, _folderWizardTargetPage);
|
setPage(Page_Target, _folderWizardTargetPage);
|
||||||
|
_folderWizardTargetPage->installEventFilter(this);
|
||||||
}
|
}
|
||||||
setPage(Page_SelectiveSync, _folderWizardSelectiveSyncPage);
|
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
|
} // end namespace
|
||||||
|
|||||||
@@ -149,6 +149,9 @@ public:
|
|||||||
explicit FolderWizard(AccountPtr account, QWidget *parent = 0);
|
explicit FolderWizard(AccountPtr account, QWidget *parent = 0);
|
||||||
~FolderWizard();
|
~FolderWizard();
|
||||||
|
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FolderWizardLocalPath *_folderWizardSourcePage;
|
FolderWizardLocalPath *_folderWizardSourcePage;
|
||||||
FolderWizardRemotePath *_folderWizardTargetPage;
|
FolderWizardRemotePath *_folderWizardTargetPage;
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
|
|
||||||
#include "updater/updater.h"
|
#include "updater/updater.h"
|
||||||
#include "updater/ocupdater.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 "ignorelisteditor.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -32,6 +36,7 @@
|
|||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QScopedValueRollback>
|
#include <QScopedValueRollback>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
@@ -88,6 +93,18 @@ GeneralSettings::GeneralSettings(QWidget *parent)
|
|||||||
|
|
||||||
// accountAdded means the wizard was finished and the wizard might change some options.
|
// accountAdded means the wizard was finished and the wizard might change some options.
|
||||||
connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings);
|
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()
|
GeneralSettings::~GeneralSettings()
|
||||||
@@ -117,22 +134,79 @@ void GeneralSettings::loadMiscSettings()
|
|||||||
|
|
||||||
void GeneralSettings::slotUpdateInfo()
|
void GeneralSettings::slotUpdateInfo()
|
||||||
{
|
{
|
||||||
// Note: the sparkle-updater is not an OCUpdater
|
if (ConfigFile().skipUpdateCheck() || !Updater::instance()) {
|
||||||
OCUpdater *updater = qobject_cast<OCUpdater *>(Updater::instance());
|
// updater disabled on compile
|
||||||
if (ConfigFile().skipUpdateCheck()) {
|
_ui->updatesGroupBox->setVisible(false);
|
||||||
updater = 0; // don't show update info if updates are disabled
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updater) {
|
// Note: the sparkle-updater is not an OCUpdater
|
||||||
connect(updater, &OCUpdater::downloadStateChanged, this, &GeneralSettings::slotUpdateInfo, Qt::UniqueConnection);
|
OCUpdater *ocupdater = qobject_cast<OCUpdater *>(Updater::instance());
|
||||||
connect(_ui->restartButton, &QAbstractButton::clicked, updater, &OCUpdater::slotStartInstaller, Qt::UniqueConnection);
|
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);
|
connect(_ui->restartButton, &QAbstractButton::clicked, qApp, &QApplication::quit, Qt::UniqueConnection);
|
||||||
_ui->updateStateLabel->setText(updater->statusString());
|
|
||||||
_ui->restartButton->setVisible(updater->downloadState() == OCUpdater::DownloadComplete);
|
_ui->updateStateLabel->setText(ocupdater->statusString());
|
||||||
} else {
|
_ui->restartButton->setVisible(ocupdater->downloadState() == OCUpdater::DownloadComplete);
|
||||||
// can't have those infos from sparkle currently
|
|
||||||
_ui->updatesGroupBox->setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
#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()
|
void GeneralSettings::saveMiscSettings()
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ private slots:
|
|||||||
void slotToggleLaunchOnStartup(bool);
|
void slotToggleLaunchOnStartup(bool);
|
||||||
void slotToggleOptionalDesktopNotifications(bool);
|
void slotToggleOptionalDesktopNotifications(bool);
|
||||||
void slotUpdateInfo();
|
void slotUpdateInfo();
|
||||||
|
void slotUpdateChannelChanged(int index);
|
||||||
void slotIgnoreFilesEditor();
|
void slotIgnoreFilesEditor();
|
||||||
void loadMiscSettings();
|
void loadMiscSettings();
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>785</width>
|
<width>785</width>
|
||||||
<height>523</height>
|
<height>533</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -74,48 +74,88 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Updates</string>
|
<string>Updates</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="updateStateLabel">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<property name="text">
|
<item>
|
||||||
<string/>
|
<widget class="QLabel" name="updateChannelLabel">
|
||||||
</property>
|
<property name="sizePolicy">
|
||||||
<property name="wordWrap">
|
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||||
<bool>true</bool>
|
<horstretch>0</horstretch>
|
||||||
</property>
|
<verstretch>0</verstretch>
|
||||||
<property name="openExternalLinks">
|
</sizepolicy>
|
||||||
<bool>true</bool>
|
</property>
|
||||||
</property>
|
<property name="text">
|
||||||
</widget>
|
<string>&Channel</string>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="buddy">
|
||||||
<widget class="QPushButton" name="restartButton">
|
<cstring>updateChannel</cstring>
|
||||||
<property name="sizePolicy">
|
</property>
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
</widget>
|
||||||
<horstretch>0</horstretch>
|
</item>
|
||||||
<verstretch>0</verstretch>
|
<item>
|
||||||
</sizepolicy>
|
<widget class="QComboBox" name="updateChannel">
|
||||||
</property>
|
<property name="sizePolicy">
|
||||||
<property name="text">
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
<string>&Restart && Update</string>
|
<horstretch>0</horstretch>
|
||||||
</property>
|
<verstretch>0</verstretch>
|
||||||
</widget>
|
</sizepolicy>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<property name="text">
|
||||||
<property name="orientation">
|
<string>stable</string>
|
||||||
<enum>Qt::Horizontal</enum>
|
</property>
|
||||||
</property>
|
</item>
|
||||||
<property name="sizeType">
|
<item>
|
||||||
<enum>QSizePolicy::Preferred</enum>
|
<property name="text">
|
||||||
</property>
|
<string>beta</string>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
</item>
|
||||||
<width>40</width>
|
</widget>
|
||||||
<height>20</height>
|
</item>
|
||||||
</size>
|
<item>
|
||||||
</property>
|
<widget class="QLabel" name="updateStateLabel">
|
||||||
</spacer>
|
<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>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@@ -247,7 +287,10 @@
|
|||||||
<tabstop>ignoredFilesButton</tabstop>
|
<tabstop>ignoredFilesButton</tabstop>
|
||||||
<tabstop>newFolderLimitCheckBox</tabstop>
|
<tabstop>newFolderLimitCheckBox</tabstop>
|
||||||
<tabstop>newFolderLimitSpinBox</tabstop>
|
<tabstop>newFolderLimitSpinBox</tabstop>
|
||||||
|
<tabstop>newExternalStorage</tabstop>
|
||||||
|
<tabstop>showInExplorerNavigationPaneCheckBox</tabstop>
|
||||||
<tabstop>crashreporterCheckBox</tabstop>
|
<tabstop>crashreporterCheckBox</tabstop>
|
||||||
|
<tabstop>updateChannel</tabstop>
|
||||||
<tabstop>restartButton</tabstop>
|
<tabstop>restartButton</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|||||||
@@ -38,6 +38,12 @@
|
|||||||
|
|
||||||
namespace OCC {
|
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)
|
IssuesWidget::IssuesWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, _ui(new Ui::IssuesWidget)
|
, _ui(new Ui::IssuesWidget)
|
||||||
@@ -96,6 +102,14 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
|||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
_ui->_treeWidget->setMinimumWidth(400);
|
_ui->_treeWidget->setMinimumWidth(400);
|
||||||
#endif
|
#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()
|
IssuesWidget::~IssuesWidget()
|
||||||
@@ -107,6 +121,16 @@ void IssuesWidget::showEvent(QShowEvent *ev)
|
|||||||
{
|
{
|
||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||||
|
|
||||||
|
// Sorting by section was newly enabled. But if we restore the header
|
||||||
|
// from a state where sorting was disabled, both of these flags will be
|
||||||
|
// false and sorting will be impossible!
|
||||||
|
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||||
|
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||||
|
|
||||||
|
// Switch back to "first important, then by time" ordering
|
||||||
|
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||||
|
|
||||||
QWidget::showEvent(ev);
|
QWidget::showEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +143,8 @@ void IssuesWidget::hideEvent(QHideEvent *ev)
|
|||||||
|
|
||||||
void IssuesWidget::cleanItems(const QString &folder)
|
void IssuesWidget::cleanItems(const QString &folder)
|
||||||
{
|
{
|
||||||
|
_ui->_treeWidget->setSortingEnabled(false);
|
||||||
|
|
||||||
// The issue list is a state, clear it and let the next sync fill it
|
// The issue list is a state, clear it and let the next sync fill it
|
||||||
// with ignored files and propagation errors.
|
// with ignored files and propagation errors.
|
||||||
int itemCnt = _ui->_treeWidget->topLevelItemCount();
|
int itemCnt = _ui->_treeWidget->topLevelItemCount();
|
||||||
@@ -129,6 +155,9 @@ void IssuesWidget::cleanItems(const QString &folder)
|
|||||||
delete item;
|
delete item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ui->_treeWidget->setSortingEnabled(true);
|
||||||
|
|
||||||
// update the tabtext
|
// update the tabtext
|
||||||
emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount()));
|
emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount()));
|
||||||
}
|
}
|
||||||
@@ -138,11 +167,17 @@ void IssuesWidget::addItem(QTreeWidgetItem *item)
|
|||||||
if (!item)
|
if (!item)
|
||||||
return;
|
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
|
// Insert item specific errors behind the others
|
||||||
|
int insertLoc = 0;
|
||||||
if (!item->text(1).isEmpty()) {
|
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()) {
|
if (_ui->_treeWidget->topLevelItem(i)->text(1).isEmpty()) {
|
||||||
insertLoc = i + 1;
|
insertLoc = i + 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -181,7 +216,7 @@ void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &p
|
|||||||
|
|
||||||
void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||||
{
|
{
|
||||||
if (!item->hasErrorStatus())
|
if (!item->showInIssuesTab())
|
||||||
return;
|
return;
|
||||||
QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item);
|
QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item);
|
||||||
if (!line)
|
if (!line)
|
||||||
@@ -240,7 +275,7 @@ bool IssuesWidget::shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAc
|
|||||||
const QString &filterFolderAlias) const
|
const QString &filterFolderAlias) const
|
||||||
{
|
{
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
auto status = item->data(0, Qt::UserRole);
|
auto status = item->data(3, Qt::UserRole);
|
||||||
visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored);
|
visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored);
|
||||||
visible &= (_ui->showWarnings->isChecked()
|
visible &= (_ui->showWarnings->isChecked()
|
||||||
|| (status != SyncFileItem::SoftError
|
|| (status != SyncFileItem::SoftError
|
||||||
@@ -368,13 +403,14 @@ void IssuesWidget::addError(const QString &folderAlias, const QString &message,
|
|||||||
|
|
||||||
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error);
|
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error);
|
||||||
|
|
||||||
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
|
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
|
||||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||||
|
twitem->setData(0, Qt::UserRole, timestamp);
|
||||||
twitem->setIcon(0, icon);
|
twitem->setIcon(0, icon);
|
||||||
twitem->setToolTip(0, longTimeStr);
|
twitem->setToolTip(0, longTimeStr);
|
||||||
twitem->setToolTip(3, message);
|
|
||||||
twitem->setData(0, Qt::UserRole, SyncFileItem::NormalError);
|
|
||||||
twitem->setData(2, Qt::UserRole, folderAlias);
|
twitem->setData(2, Qt::UserRole, folderAlias);
|
||||||
|
twitem->setToolTip(3, message);
|
||||||
|
twitem->setData(3, Qt::UserRole, SyncFileItem::NormalError);
|
||||||
|
|
||||||
addItem(twitem);
|
addItem(twitem);
|
||||||
addErrorWidget(twitem, message, category);
|
addErrorWidget(twitem, message, category);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "progressdispatcher.h"
|
#include "progressdispatcher.h"
|
||||||
#include "owncloudgui.h"
|
#include "owncloudgui.h"
|
||||||
@@ -84,6 +85,9 @@ private:
|
|||||||
/// Wipes all insufficient remote storgage blacklist entries
|
/// Wipes all insufficient remote storgage blacklist entries
|
||||||
void retryInsufficentRemoteStorageErrors(const QString &folderAlias);
|
void retryInsufficentRemoteStorageErrors(const QString &folderAlias);
|
||||||
|
|
||||||
|
/// Each insert disables sorting, this timer reenables it
|
||||||
|
QTimer _reenableSorting;
|
||||||
|
|
||||||
Ui::IssuesWidget *_ui;
|
Ui::IssuesWidget *_ui;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,6 +99,9 @@
|
|||||||
<property name="rootIsDecorated">
|
<property name="rootIsDecorated">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="columnCount">
|
<property name="columnCount">
|
||||||
<number>4</number>
|
<number>4</number>
|
||||||
</property>
|
</property>
|
||||||
@@ -124,6 +127,13 @@
|
|||||||
</column>
|
</column>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
// check a environment variable for core dumps
|
// check a environment variable for core dumps
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
if (!qgetenv("OWNCLOUD_CORE_DUMP").isEmpty()) {
|
if (!qEnvironmentVariableIsEmpty("OWNCLOUD_CORE_DUMP")) {
|
||||||
struct rlimit core_limit;
|
struct rlimit core_limit;
|
||||||
core_limit.rlim_cur = RLIM_INFINITY;
|
core_limit.rlim_cur = RLIM_INFINITY;
|
||||||
core_limit.rlim_max = RLIM_INFINITY;
|
core_limit.rlim_max = RLIM_INFINITY;
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode)
|
|||||||
|
|
||||||
QString timeStr = locale.toString(QTime::currentTime());
|
QString timeStr = locale.toString(QTime::currentTime());
|
||||||
|
|
||||||
// the ocs API returns stat code 100 if it succeeded.
|
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
|
||||||
if (statusCode != OCS_SUCCESS_STATUS_CODE) {
|
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
|
||||||
qCWarning(lcNotifications) << "Notification Request to Server failed, leave button visible.";
|
qCWarning(lcNotifications) << "Notification Request to Server failed, leave button visible.";
|
||||||
for (i = 0; i < _buttons.count(); i++) {
|
for (i = 0; i < _buttons.count(); i++) {
|
||||||
_buttons.at(i)->setEnabled(true);
|
_buttons.at(i)->setEnabled(true);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ OcsJob::OcsJob(AccountPtr account)
|
|||||||
: AbstractNetworkJob(account, "")
|
: AbstractNetworkJob(account, "")
|
||||||
{
|
{
|
||||||
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
|
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE);
|
||||||
|
_passStatusCodes.append(OCS_SUCCESS_STATUS_CODE_V2);
|
||||||
setIgnoreCredentialFailure(true);
|
setIgnoreCredentialFailure(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#define OCS_SUCCESS_STATUS_CODE 100
|
#define OCS_SUCCESS_STATUS_CODE 100
|
||||||
|
// Apparantly the v2.php URLs can return that
|
||||||
|
#define OCS_SUCCESS_STATUS_CODE_V2 200
|
||||||
|
|
||||||
class QJsonDocument;
|
class QJsonDocument;
|
||||||
|
|
||||||
|
|||||||
@@ -50,18 +50,13 @@ const char propertyAccountC[] = "oc_account";
|
|||||||
ownCloudGui::ownCloudGui(Application *parent)
|
ownCloudGui::ownCloudGui(Application *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, _tray(0)
|
, _tray(0)
|
||||||
,
|
|
||||||
#if defined(Q_OS_MAC)
|
#if defined(Q_OS_MAC)
|
||||||
_settingsDialog(new SettingsDialogMac(this))
|
, _settingsDialog(new SettingsDialogMac(this))
|
||||||
,
|
|
||||||
#else
|
#else
|
||||||
_settingsDialog(new SettingsDialog(this))
|
, _settingsDialog(new SettingsDialog(this))
|
||||||
,
|
|
||||||
#endif
|
#endif
|
||||||
_logBrowser(0)
|
, _logBrowser(0)
|
||||||
, _contextMenuVisibleOsx(false)
|
|
||||||
, _recentActionsMenu(0)
|
, _recentActionsMenu(0)
|
||||||
, _qdbusmenuWorkaround(false)
|
|
||||||
, _app(parent)
|
, _app(parent)
|
||||||
{
|
{
|
||||||
_tray = new Systray();
|
_tray = new Systray();
|
||||||
@@ -117,7 +112,7 @@ void ownCloudGui::slotOpenSettingsDialog()
|
|||||||
|
|
||||||
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
||||||
{
|
{
|
||||||
if (_qdbusmenuWorkaround) {
|
if (_workaroundFakeDoubleClick) {
|
||||||
static QElapsedTimer last_click;
|
static QElapsedTimer last_click;
|
||||||
if (last_click.isValid() && last_click.elapsed() < 200) {
|
if (last_click.isValid() && last_click.elapsed() < 200) {
|
||||||
return;
|
return;
|
||||||
@@ -207,13 +202,23 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
{
|
{
|
||||||
bool allSignedOut = true;
|
bool allSignedOut = true;
|
||||||
bool allPaused = true;
|
bool allPaused = true;
|
||||||
|
bool allDisconnected = true;
|
||||||
QVector<AccountStatePtr> problemAccounts;
|
QVector<AccountStatePtr> problemAccounts;
|
||||||
|
auto setStatusText = [&](const QString &text) {
|
||||||
|
// Don't overwrite the status if we're currently syncing
|
||||||
|
if (FolderMan::instance()->currentSyncFolder())
|
||||||
|
return;
|
||||||
|
_actionStatus->setText(text);
|
||||||
|
};
|
||||||
|
|
||||||
foreach (auto a, AccountManager::instance()->accounts()) {
|
foreach (auto a, AccountManager::instance()->accounts()) {
|
||||||
if (!a->isSignedOut()) {
|
if (!a->isSignedOut()) {
|
||||||
allSignedOut = false;
|
allSignedOut = false;
|
||||||
}
|
}
|
||||||
if (!a->isConnected()) {
|
if (!a->isConnected()) {
|
||||||
problemAccounts.append(a);
|
problemAccounts.append(a);
|
||||||
|
} else {
|
||||||
|
allDisconnected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (Folder *f, FolderMan::instance()->map()) {
|
foreach (Folder *f, FolderMan::instance()->map()) {
|
||||||
@@ -224,6 +229,11 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
|
|
||||||
if (!problemAccounts.empty()) {
|
if (!problemAccounts.empty()) {
|
||||||
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
||||||
|
if (allDisconnected) {
|
||||||
|
setStatusText(tr("Disconnected"));
|
||||||
|
} else {
|
||||||
|
setStatusText(tr("Disconnected from some accounts"));
|
||||||
|
}
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
// Windows has a 128-char tray tooltip length limit.
|
// Windows has a 128-char tray tooltip length limit.
|
||||||
QStringList accountNames;
|
QStringList accountNames;
|
||||||
@@ -250,10 +260,12 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
if (allSignedOut) {
|
if (allSignedOut) {
|
||||||
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
||||||
_tray->setToolTip(tr("Please sign in"));
|
_tray->setToolTip(tr("Please sign in"));
|
||||||
|
setStatusText(tr("Signed out"));
|
||||||
return;
|
return;
|
||||||
} else if (allPaused) {
|
} else if (allPaused) {
|
||||||
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible()));
|
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible()));
|
||||||
_tray->setToolTip(tr("Account synchronization is disabled"));
|
_tray->setToolTip(tr("Account synchronization is disabled"));
|
||||||
|
setStatusText(tr("Synchronization is paused"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,34 +273,40 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
QString trayMessage;
|
QString trayMessage;
|
||||||
FolderMan *folderMan = FolderMan::instance();
|
FolderMan *folderMan = FolderMan::instance();
|
||||||
Folder::Map map = folderMan->map();
|
Folder::Map map = folderMan->map();
|
||||||
SyncResult overallResult = FolderMan::accountStatus(map.values());
|
SyncResult::Status overallResult = FolderMan::accountStatus(map.values()).status();
|
||||||
|
|
||||||
// create the tray blob message, check if we have an defined state
|
// create the tray blob message, check if we have an defined state
|
||||||
if (overallResult.status() != SyncResult::Undefined) {
|
if (overallResult != SyncResult::Undefined && map.count() > 0) {
|
||||||
if (map.count() > 0) {
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
// Windows has a 128-char tray tooltip length limit.
|
// Windows has a 128-char tray tooltip length limit.
|
||||||
trayMessage = folderMan->statusToString(overallResult.status(), false);
|
trayMessage = folderMan->statusToString(overallResult, false);
|
||||||
#else
|
#else
|
||||||
QStringList allStatusStrings;
|
QStringList allStatusStrings;
|
||||||
foreach (Folder *folder, map.values()) {
|
foreach (Folder *folder, map.values()) {
|
||||||
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncPaused());
|
QString folderMessage = folderMan->statusToString(folder->syncResult().status(), folder->syncPaused());
|
||||||
allStatusStrings += tr("Folder %1: %2").arg(folder->shortGuiLocalPath(), folderMessage);
|
allStatusStrings += tr("Folder %1: %2").arg(folder->shortGuiLocalPath(), folderMessage);
|
||||||
}
|
|
||||||
trayMessage = allStatusStrings.join(QLatin1String("\n"));
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
trayMessage = tr("No sync folders configured.");
|
|
||||||
}
|
}
|
||||||
|
trayMessage = allStatusStrings.join(QLatin1String("\n"));
|
||||||
|
#endif
|
||||||
|
|
||||||
QIcon statusIcon = Theme::instance()->syncStateIcon(overallResult.status(), true, contextMenuVisible());
|
QIcon statusIcon = Theme::instance()->syncStateIcon(overallResult, true, contextMenuVisible());
|
||||||
_tray->setIcon(statusIcon);
|
_tray->setIcon(statusIcon);
|
||||||
_tray->setToolTip(trayMessage);
|
_tray->setToolTip(trayMessage);
|
||||||
|
|
||||||
|
if (overallResult == SyncResult::Success || overallResult == SyncResult::Problem) {
|
||||||
|
setStatusText(tr("Up to date"));
|
||||||
|
} else if (overallResult == SyncResult::Paused) {
|
||||||
|
setStatusText(tr("Synchronization is paused"));
|
||||||
|
} else {
|
||||||
|
setStatusText(tr("Error during synchronization"));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// undefined because there are no folders.
|
if (overallResult == SyncResult::Undefined)
|
||||||
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Problem, true, contextMenuVisible());
|
overallResult = SyncResult::Problem;
|
||||||
|
QIcon icon = Theme::instance()->syncStateIcon(overallResult, true, contextMenuVisible());
|
||||||
_tray->setIcon(icon);
|
_tray->setIcon(icon);
|
||||||
_tray->setToolTip(tr("There are no sync folders configured."));
|
_tray->setToolTip(tr("There are no sync folders configured."));
|
||||||
|
setStatusText(tr("No sync folders configured"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +344,7 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
|||||||
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
|
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();
|
auto alias = folder->alias();
|
||||||
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
|
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
|
||||||
}
|
}
|
||||||
@@ -358,17 +376,19 @@ void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *men
|
|||||||
|
|
||||||
void ownCloudGui::slotContextMenuAboutToShow()
|
void ownCloudGui::slotContextMenuAboutToShow()
|
||||||
{
|
{
|
||||||
// For some reason on OS X _contextMenu->isVisible returns always false
|
_contextMenuVisibleManual = true;
|
||||||
_contextMenuVisibleOsx = true;
|
|
||||||
|
|
||||||
// Update icon in sys tray, as it might change depending on the context menu state
|
// Update icon in sys tray, as it might change depending on the context menu state
|
||||||
slotComputeOverallSyncStatus();
|
slotComputeOverallSyncStatus();
|
||||||
|
|
||||||
|
if (!_workaroundNoAboutToShowUpdate) {
|
||||||
|
updateContextMenu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotContextMenuAboutToHide()
|
void ownCloudGui::slotContextMenuAboutToHide()
|
||||||
{
|
{
|
||||||
// For some reason on OS X _contextMenu->isVisible returns always false
|
_contextMenuVisibleManual = false;
|
||||||
_contextMenuVisibleOsx = false;
|
|
||||||
|
|
||||||
// Update icon in sys tray, as it might change depending on the context menu state
|
// Update icon in sys tray, as it might change depending on the context menu state
|
||||||
slotComputeOverallSyncStatus();
|
slotComputeOverallSyncStatus();
|
||||||
@@ -376,11 +396,11 @@ void ownCloudGui::slotContextMenuAboutToHide()
|
|||||||
|
|
||||||
bool ownCloudGui::contextMenuVisible() const
|
bool ownCloudGui::contextMenuVisible() const
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_MAC
|
// On some platforms isVisible doesn't work and always returns false,
|
||||||
return _contextMenuVisibleOsx;
|
// elsewhere aboutToHide is unreliable.
|
||||||
#else
|
if (_workaroundManualVisibility)
|
||||||
|
return _contextMenuVisibleManual;
|
||||||
return _contextMenu->isVisible();
|
return _contextMenu->isVisible();
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool minimalTrayMenu()
|
static bool minimalTrayMenu()
|
||||||
@@ -403,12 +423,36 @@ static bool updateWhileVisible()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static QByteArray forceQDBusTrayWorkaround()
|
static QByteArray envForceQDBusTrayWorkaround()
|
||||||
{
|
{
|
||||||
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
|
||||||
return var;
|
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()
|
void ownCloudGui::setupContextMenu()
|
||||||
{
|
{
|
||||||
if (_contextMenu) {
|
if (_contextMenu) {
|
||||||
@@ -431,51 +475,65 @@ void ownCloudGui::setupContextMenu()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enables workarounds for bugs introduced in Qt 5.5.0
|
auto applyEnvVariable = [](bool *sw, const QByteArray &value) {
|
||||||
// In particular QTBUG-47863 #3672 (tray menu fails to update and
|
if (value == "1")
|
||||||
// becomes unresponsive) and QTBUG-48068 #3722 (click signal is
|
*sw = true;
|
||||||
// emitted several times)
|
if (value == "0")
|
||||||
// The Qt version check intentionally uses 5.0.0 (where platformMenu()
|
*sw = false;
|
||||||
// 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
|
|
||||||
|
|
||||||
if (forceQDBusTrayWorkaround() == "1") {
|
// This is an old compound flag that people might still depend on
|
||||||
_qdbusmenuWorkaround = true;
|
bool qdbusmenuWorkarounds = false;
|
||||||
} else if (forceQDBusTrayWorkaround() == "0") {
|
applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround());
|
||||||
_qdbusmenuWorkaround = false;
|
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
|
#ifdef Q_OS_MAC
|
||||||
// https://bugreports.qt.io/browse/QTBUG-54633
|
// https://bugreports.qt.io/browse/QTBUG-54633
|
||||||
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
|
_workaroundNoAboutToShowUpdate = true;
|
||||||
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
|
_workaroundManualVisibility = true;
|
||||||
#else
|
|
||||||
connect(_contextMenu.data(), &QMenu::aboutToShow, this, &ownCloudGui::updateContextMenu);
|
|
||||||
#endif
|
#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.
|
// Populate the context menu now.
|
||||||
updateContextMenu();
|
updateContextMenu();
|
||||||
@@ -487,13 +545,21 @@ void ownCloudGui::updateContextMenu()
|
|||||||
return;
|
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)
|
// 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
|
// we need to hide and show the tray icon. We don't want to do that
|
||||||
// while it's visible!
|
// while it's visible!
|
||||||
if (contextMenuVisible()) {
|
if (contextMenuVisible()) {
|
||||||
if (!_workaroundBatchTrayUpdate.isActive()) {
|
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||||
_workaroundBatchTrayUpdate.start();
|
_delayedTrayUpdateTimer.start();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -550,11 +616,13 @@ void ownCloudGui::updateContextMenu()
|
|||||||
|
|
||||||
_contextMenu->addSeparator();
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
_contextMenu->addAction(_actionStatus);
|
||||||
if (isConfigured && atLeastOneConnected) {
|
if (isConfigured && atLeastOneConnected) {
|
||||||
_contextMenu->addAction(_actionStatus);
|
|
||||||
_contextMenu->addMenu(_recentActionsMenu);
|
_contextMenu->addMenu(_recentActionsMenu);
|
||||||
_contextMenu->addSeparator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
if (accountList.isEmpty()) {
|
if (accountList.isEmpty()) {
|
||||||
_contextMenu->addAction(_actionNewAccountWizard);
|
_contextMenu->addAction(_actionNewAccountWizard);
|
||||||
}
|
}
|
||||||
@@ -606,35 +674,30 @@ void ownCloudGui::updateContextMenu()
|
|||||||
}
|
}
|
||||||
_contextMenu->addAction(_actionQuit);
|
_contextMenu->addAction(_actionQuit);
|
||||||
|
|
||||||
if (_qdbusmenuWorkaround) {
|
if (_workaroundShowAndHideTray) {
|
||||||
_tray->show();
|
_tray->show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::updateContextMenuNeeded()
|
void ownCloudGui::updateContextMenuNeeded()
|
||||||
{
|
{
|
||||||
// For the workaround case updating while visible is impossible. Instead
|
// if it's visible and we can update live: update now
|
||||||
// occasionally update the menu when it's invisible.
|
if (contextMenuVisible() && updateWhileVisible()) {
|
||||||
if (_qdbusmenuWorkaround) {
|
// Note: don't update while visible on OSX
|
||||||
if (!_workaroundBatchTrayUpdate.isActive()) {
|
// https://bugreports.qt.io/browse/QTBUG-54845
|
||||||
_workaroundBatchTrayUpdate.start();
|
updateContextMenu();
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
// if we can't lazily update: update later
|
||||||
// https://bugreports.qt.io/browse/QTBUG-54845
|
if (_workaroundNoAboutToShowUpdate) {
|
||||||
// We cannot update on demand or while visible -> update when invisible.
|
// Note: don't update immediately even in the invisible case
|
||||||
if (!contextMenuVisible()) {
|
// as that can lead to extremely frequent menu updates
|
||||||
updateContextMenu();
|
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)
|
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
|
||||||
@@ -742,7 +805,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
|
|||||||
.arg(progress._currentDiscoveredFolder));
|
.arg(progress._currentDiscoveredFolder));
|
||||||
}
|
}
|
||||||
} else if (progress.status() == ProgressInfo::Done) {
|
} else if (progress.status() == ProgressInfo::Done) {
|
||||||
QTimer::singleShot(2000, this, &ownCloudGui::slotDisplayIdle);
|
QTimer::singleShot(2000, this, &ownCloudGui::slotComputeOverallSyncStatus);
|
||||||
}
|
}
|
||||||
if (progress.status() != ProgressInfo::Propagation) {
|
if (progress.status() != ProgressInfo::Propagation) {
|
||||||
return;
|
return;
|
||||||
@@ -812,11 +875,6 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotDisplayIdle()
|
|
||||||
{
|
|
||||||
_actionStatus->setText(tr("Up to date"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ownCloudGui::slotLogin()
|
void ownCloudGui::slotLogin()
|
||||||
{
|
{
|
||||||
if (auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC))) {
|
if (auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC))) {
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ public slots:
|
|||||||
void slotRemoveDestroyedShareDialogs();
|
void slotRemoveDestroyedShareDialogs();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotDisplayIdle();
|
|
||||||
void slotLogin();
|
void slotLogin();
|
||||||
void slotLogout();
|
void slotLogout();
|
||||||
void slotUnpauseAllFolders();
|
void slotUnpauseAllFolders();
|
||||||
@@ -120,14 +119,19 @@ private:
|
|||||||
// tray's menu
|
// tray's menu
|
||||||
QScopedPointer<QMenu> _contextMenu;
|
QScopedPointer<QMenu> _contextMenu;
|
||||||
|
|
||||||
// Manually tracking whether the context menu is visible, but only works
|
// Manually tracking whether the context menu is visible via aboutToShow
|
||||||
// on OSX because aboutToHide is not reliable everywhere.
|
// and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere
|
||||||
bool _contextMenuVisibleOsx;
|
// so this only gets used with _workaroundManualVisibility (when the tray's
|
||||||
|
// isVisible() is unreliable)
|
||||||
|
bool _contextMenuVisibleManual = false;
|
||||||
|
|
||||||
QMenu *_recentActionsMenu;
|
QMenu *_recentActionsMenu;
|
||||||
QVector<QMenu *> _accountMenus;
|
QVector<QMenu *> _accountMenus;
|
||||||
bool _qdbusmenuWorkaround;
|
bool _workaroundShowAndHideTray = false;
|
||||||
QTimer _workaroundBatchTrayUpdate;
|
bool _workaroundNoAboutToShowUpdate = false;
|
||||||
|
bool _workaroundFakeDoubleClick = false;
|
||||||
|
bool _workaroundManualVisibility = false;
|
||||||
|
QTimer _delayedTrayUpdateTimer;
|
||||||
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
|
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
|
||||||
|
|
||||||
QAction *_actionLogin;
|
QAction *_actionLogin;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
#include "wizard/owncloudwizardcommon.h"
|
#include "wizard/owncloudwizardcommon.h"
|
||||||
#include "wizard/owncloudwizard.h"
|
#include "wizard/owncloudwizard.h"
|
||||||
@@ -45,7 +46,7 @@ OwncloudSetupWizard::OwncloudSetupWizard(QObject *parent)
|
|||||||
, _remoteFolder()
|
, _remoteFolder()
|
||||||
{
|
{
|
||||||
connect(_ocWizard, &OwncloudWizard::determineAuthType,
|
connect(_ocWizard, &OwncloudWizard::determineAuthType,
|
||||||
this, &OwncloudSetupWizard::slotDetermineAuthType);
|
this, &OwncloudSetupWizard::slotCheckServer);
|
||||||
connect(_ocWizard, &OwncloudWizard::connectToOCUrl,
|
connect(_ocWizard, &OwncloudWizard::connectToOCUrl,
|
||||||
this, &OwncloudSetupWizard::slotConnectToOCUrl);
|
this, &OwncloudSetupWizard::slotConnectToOCUrl);
|
||||||
connect(_ocWizard, &OwncloudWizard::createLocalAndRemoteFolders,
|
connect(_ocWizard, &OwncloudWizard::createLocalAndRemoteFolders,
|
||||||
@@ -69,6 +70,7 @@ static QPointer<OwncloudSetupWizard> wiz = 0;
|
|||||||
void OwncloudSetupWizard::runWizard(QObject *obj, const char *amember, QWidget *parent)
|
void OwncloudSetupWizard::runWizard(QObject *obj, const char *amember, QWidget *parent)
|
||||||
{
|
{
|
||||||
if (!wiz.isNull()) {
|
if (!wiz.isNull()) {
|
||||||
|
bringWizardToFrontIfVisible();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +86,17 @@ bool OwncloudSetupWizard::bringWizardToFrontIfVisible()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wiz->_ocWizard->currentId() == WizardCommon::Page_ShibbolethCreds) {
|
||||||
|
// Try to find if there is a browser open and raise that instead (Issue #6105)
|
||||||
|
const auto allWindow = qApp->topLevelWidgets();
|
||||||
|
auto it = std::find_if(allWindow.cbegin(), allWindow.cend(), [](QWidget *w)
|
||||||
|
{ return QLatin1String(w->metaObject()->className()) == QLatin1String("OCC::ShibbolethWebView"); });
|
||||||
|
if (it != allWindow.cend()) {
|
||||||
|
ownCloudGui::raiseDialog(*it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ownCloudGui::raiseDialog(wiz->_ocWizard);
|
ownCloudGui::raiseDialog(wiz->_ocWizard);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -127,7 +140,7 @@ void OwncloudSetupWizard::startWizard()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// also checks if an installation is valid and determines auth type in a second step
|
// also checks if an installation is valid and determines auth type in a second step
|
||||||
void OwncloudSetupWizard::slotDetermineAuthType(const QString &urlString)
|
void OwncloudSetupWizard::slotCheckServer(const QString &urlString)
|
||||||
{
|
{
|
||||||
QString fixedUrl = urlString;
|
QString fixedUrl = urlString;
|
||||||
QUrl url = QUrl::fromUserInput(fixedUrl);
|
QUrl url = QUrl::fromUserInput(fixedUrl);
|
||||||
@@ -150,7 +163,7 @@ void OwncloudSetupWizard::slotDetermineAuthType(const QString &urlString)
|
|||||||
// We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings)
|
// We want to reset the QNAM proxy so that the global proxy settings are used (via ClientProxy settings)
|
||||||
account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
|
account->networkAccessManager()->setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
|
||||||
// use a queued invocation so we're as asynchronous as with the other code path
|
// use a queued invocation so we're as asynchronous as with the other code path
|
||||||
QMetaObject::invokeMethod(this, "slotContinueDetermineAuth", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "slotFindServer", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,20 +177,40 @@ void OwncloudSetupWizard::slotSystemProxyLookupDone(const QNetworkProxy &proxy)
|
|||||||
AccountPtr account = _ocWizard->account();
|
AccountPtr account = _ocWizard->account();
|
||||||
account->networkAccessManager()->setProxy(proxy);
|
account->networkAccessManager()->setProxy(proxy);
|
||||||
|
|
||||||
slotContinueDetermineAuth();
|
slotFindServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::slotContinueDetermineAuth()
|
void OwncloudSetupWizard::slotFindServer()
|
||||||
{
|
{
|
||||||
AccountPtr account = _ocWizard->account();
|
AccountPtr account = _ocWizard->account();
|
||||||
|
|
||||||
// Set fake credentials before we check what credential it actually is.
|
// Set fake credentials before we check what credential it actually is.
|
||||||
account->setCredentials(CredentialsFactory::create("dummy"));
|
account->setCredentials(CredentialsFactory::create("dummy"));
|
||||||
|
|
||||||
// Before we check the auth type, resolve any permanent redirect
|
// Determining the actual server URL can be a multi-stage process
|
||||||
// chain there might be. We cannot do this only on url/status.php
|
// 1. Check url/status.php with CheckServerJob
|
||||||
// in CheckServerJob, because things like url shorteners don't
|
// If that works we're done. In that case we don't check the
|
||||||
// redirect subpaths.
|
// url directly for redirects, see #5954.
|
||||||
|
// 2. Check the url for permanent redirects (like url shorteners)
|
||||||
|
// 3. Check redirected-url/status.php with CheckServerJob
|
||||||
|
|
||||||
|
// Step 1: Check url/status.php
|
||||||
|
CheckServerJob *job = new CheckServerJob(account, this);
|
||||||
|
job->setIgnoreCredentialFailure(true);
|
||||||
|
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotFoundServer);
|
||||||
|
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotFindServerBehindRedirect);
|
||||||
|
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoServerFoundTimeout);
|
||||||
|
job->setTimeout((account->url().scheme() == "https") ? 30 * 1000 : 10 * 1000);
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
// Step 2 and 3 are in slotFindServerBehindRedirect()
|
||||||
|
}
|
||||||
|
|
||||||
|
void OwncloudSetupWizard::slotFindServerBehindRedirect()
|
||||||
|
{
|
||||||
|
AccountPtr account = _ocWizard->account();
|
||||||
|
|
||||||
|
// Step 2: Resolve any permanent redirect chains on the base url
|
||||||
auto redirectCheckJob = account->sendRequest("GET", account->url());
|
auto redirectCheckJob = account->sendRequest("GET", account->url());
|
||||||
|
|
||||||
// Use a significantly reduced timeout for this redirect check:
|
// Use a significantly reduced timeout for this redirect check:
|
||||||
@@ -197,20 +230,20 @@ void OwncloudSetupWizard::slotContinueDetermineAuth()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// When done, start checking status.php.
|
// Step 3: When done, start checking status.php.
|
||||||
connect(redirectCheckJob, &SimpleNetworkJob::finishedSignal, this,
|
connect(redirectCheckJob, &SimpleNetworkJob::finishedSignal, this,
|
||||||
[this, account]() {
|
[this, account]() {
|
||||||
CheckServerJob *job = new CheckServerJob(account, this);
|
CheckServerJob *job = new CheckServerJob(account, this);
|
||||||
job->setIgnoreCredentialFailure(true);
|
job->setIgnoreCredentialFailure(true);
|
||||||
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotOwnCloudFoundAuth);
|
connect(job, &CheckServerJob::instanceFound, this, &OwncloudSetupWizard::slotFoundServer);
|
||||||
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotNoOwnCloudFoundAuth);
|
connect(job, &CheckServerJob::instanceNotFound, this, &OwncloudSetupWizard::slotNoServerFound);
|
||||||
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoOwnCloudFoundAuthTimeout);
|
connect(job, &CheckServerJob::timeout, this, &OwncloudSetupWizard::slotNoServerFoundTimeout);
|
||||||
job->setTimeout((account->url().scheme() == "https") ? 30 * 1000 : 10 * 1000);
|
job->setTimeout((account->url().scheme() == "https") ? 30 * 1000 : 10 * 1000);
|
||||||
job->start();
|
job->start();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObject &info)
|
void OwncloudSetupWizard::slotFoundServer(const QUrl &url, const QJsonObject &info)
|
||||||
{
|
{
|
||||||
auto serverVersion = CheckServerJob::version(info);
|
auto serverVersion = CheckServerJob::version(info);
|
||||||
|
|
||||||
@@ -230,14 +263,10 @@ void OwncloudSetupWizard::slotOwnCloudFoundAuth(const QUrl &url, const QJsonObje
|
|||||||
qCInfo(lcWizard) << " was redirected to" << url.toString();
|
qCInfo(lcWizard) << " was redirected to" << url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
|
slotDetermineAuthType();
|
||||||
job->setIgnoreCredentialFailure(true);
|
|
||||||
connect(job, &DetermineAuthTypeJob::authType,
|
|
||||||
_ocWizard, &OwncloudWizard::setAuthType);
|
|
||||||
job->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
void OwncloudSetupWizard::slotNoServerFound(QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
auto job = qobject_cast<CheckServerJob *>(sender());
|
auto job = qobject_cast<CheckServerJob *>(sender());
|
||||||
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
@@ -250,7 +279,7 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||||||
} else {
|
} else {
|
||||||
msg = tr("Failed to connect to %1 at %2:<br/>%3")
|
msg = tr("Failed to connect to %1 at %2:<br/>%3")
|
||||||
.arg(Utility::escape(Theme::instance()->appNameGUI()),
|
.arg(Utility::escape(Theme::instance()->appNameGUI()),
|
||||||
Utility::escape(reply->url().toString()),
|
Utility::escape(_ocWizard->account()->url().toString()),
|
||||||
Utility::escape(job->errorString()));
|
Utility::escape(job->errorString()));
|
||||||
}
|
}
|
||||||
bool isDowngradeAdvised = checkDowngradeAdvised(reply);
|
bool isDowngradeAdvised = checkDowngradeAdvised(reply);
|
||||||
@@ -265,7 +294,8 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||||||
QString serverError = reply->peek(1024 * 20);
|
QString serverError = reply->peek(1024 * 20);
|
||||||
qCDebug(lcWizard) << serverError;
|
qCDebug(lcWizard) << serverError;
|
||||||
QMessageBox messageBox(_ocWizard);
|
QMessageBox messageBox(_ocWizard);
|
||||||
messageBox.setText(serverError);
|
messageBox.setText(tr("The server reported the following error:"));
|
||||||
|
messageBox.setInformativeText(serverError);
|
||||||
messageBox.addButton(QMessageBox::Ok);
|
messageBox.addButton(QMessageBox::Ok);
|
||||||
messageBox.setTextFormat(Qt::RichText);
|
messageBox.setTextFormat(Qt::RichText);
|
||||||
messageBox.exec();
|
messageBox.exec();
|
||||||
@@ -279,12 +309,20 @@ void OwncloudSetupWizard::slotNoOwnCloudFoundAuth(QNetworkReply *reply)
|
|||||||
_ocWizard->account()->resetRejectedCertificates();
|
_ocWizard->account()->resetRejectedCertificates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::slotNoOwnCloudFoundAuthTimeout(const QUrl &url)
|
void OwncloudSetupWizard::slotNoServerFoundTimeout(const QUrl &url)
|
||||||
{
|
{
|
||||||
_ocWizard->displayError(
|
_ocWizard->displayError(
|
||||||
tr("Timeout while trying to connect to %1 at %2.")
|
tr("Timeout while trying to connect to %1 at %2.")
|
||||||
.arg(Utility::escape(Theme::instance()->appNameGUI()), Utility::escape(url.toString())),
|
.arg(Utility::escape(Theme::instance()->appNameGUI()), Utility::escape(url.toString())),
|
||||||
false);
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OwncloudSetupWizard::slotDetermineAuthType()
|
||||||
|
{
|
||||||
|
DetermineAuthTypeJob *job = new DetermineAuthTypeJob(_ocWizard->account(), this);
|
||||||
|
connect(job, &DetermineAuthTypeJob::authType,
|
||||||
|
_ocWizard, &OwncloudWizard::setAuthType);
|
||||||
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
|
void OwncloudSetupWizard::slotConnectToOCUrl(const QString &url)
|
||||||
|
|||||||
@@ -49,12 +49,16 @@ signals:
|
|||||||
void ownCloudWizardDone(int);
|
void ownCloudWizardDone(int);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotDetermineAuthType(const QString &);
|
void slotCheckServer(const QString &);
|
||||||
void slotSystemProxyLookupDone(const QNetworkProxy &proxy);
|
void slotSystemProxyLookupDone(const QNetworkProxy &proxy);
|
||||||
void slotContinueDetermineAuth();
|
|
||||||
void slotOwnCloudFoundAuth(const QUrl &, const QJsonObject &);
|
void slotFindServer();
|
||||||
void slotNoOwnCloudFoundAuth(QNetworkReply *reply);
|
void slotFindServerBehindRedirect();
|
||||||
void slotNoOwnCloudFoundAuthTimeout(const QUrl &url);
|
void slotFoundServer(const QUrl &, const QJsonObject &);
|
||||||
|
void slotNoServerFound(QNetworkReply *reply);
|
||||||
|
void slotNoServerFoundTimeout(const QUrl &url);
|
||||||
|
|
||||||
|
void slotDetermineAuthType();
|
||||||
|
|
||||||
void slotConnectToOCUrl(const QString &);
|
void slotConnectToOCUrl(const QString &);
|
||||||
void slotAuthError();
|
void slotAuthError();
|
||||||
|
|||||||
@@ -32,6 +32,19 @@
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
bool SortedTreeWidgetItem::operator<(const QTreeWidgetItem &other) const
|
||||||
|
{
|
||||||
|
int column = treeWidget()->sortColumn();
|
||||||
|
if (column != 0) {
|
||||||
|
return QTreeWidgetItem::operator<(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items with empty "File" column are larger than others,
|
||||||
|
// otherwise sort by time (this uses lexicographic ordering)
|
||||||
|
return std::forward_as_tuple(text(1).isEmpty(), data(0, Qt::UserRole).toDateTime())
|
||||||
|
< std::forward_as_tuple(other.text(1).isEmpty(), other.data(0, Qt::UserRole).toDateTime());
|
||||||
|
}
|
||||||
|
|
||||||
ProtocolWidget::ProtocolWidget(QWidget *parent)
|
ProtocolWidget::ProtocolWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, _ui(new Ui::ProtocolWidget)
|
, _ui(new Ui::ProtocolWidget)
|
||||||
@@ -86,6 +99,16 @@ void ProtocolWidget::showEvent(QShowEvent *ev)
|
|||||||
{
|
{
|
||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||||
|
|
||||||
|
// Sorting by section was newly enabled. But if we restore the header
|
||||||
|
// from a state where sorting was disabled, both of these flags will be
|
||||||
|
// false and sorting will be impossible!
|
||||||
|
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||||
|
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||||
|
|
||||||
|
// Switch back to "by time" ordering
|
||||||
|
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||||
|
|
||||||
QWidget::showEvent(ev);
|
QWidget::showEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,20 +181,21 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo
|
|||||||
columns << Utility::octetsToString(item._size);
|
columns << Utility::octetsToString(item._size);
|
||||||
}
|
}
|
||||||
|
|
||||||
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
|
QTreeWidgetItem *twitem = new SortedTreeWidgetItem(columns);
|
||||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||||
|
twitem->setData(0, Qt::UserRole, timestamp);
|
||||||
twitem->setIcon(0, icon);
|
twitem->setIcon(0, icon);
|
||||||
twitem->setToolTip(0, longTimeStr);
|
twitem->setToolTip(0, longTimeStr);
|
||||||
twitem->setToolTip(1, item._file);
|
twitem->setToolTip(1, item._file);
|
||||||
twitem->setToolTip(3, message);
|
|
||||||
twitem->setData(0, Qt::UserRole, item._status);
|
|
||||||
twitem->setData(2, Qt::UserRole, folder);
|
twitem->setData(2, Qt::UserRole, folder);
|
||||||
|
twitem->setToolTip(3, message);
|
||||||
|
twitem->setData(3, Qt::UserRole, item._status);
|
||||||
return twitem;
|
return twitem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||||
{
|
{
|
||||||
if (item->hasErrorStatus())
|
if (!item->showInProtocolTab())
|
||||||
return;
|
return;
|
||||||
QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item);
|
QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item);
|
||||||
if (line) {
|
if (line) {
|
||||||
|
|||||||
@@ -34,6 +34,21 @@ namespace Ui {
|
|||||||
}
|
}
|
||||||
class Application;
|
class Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A QTreeWidgetItem with special sorting.
|
||||||
|
*
|
||||||
|
* It allows items for global entries to be moved to the top if the
|
||||||
|
* sorting section is the "Time" column.
|
||||||
|
*/
|
||||||
|
class SortedTreeWidgetItem : public QTreeWidgetItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using QTreeWidgetItem::QTreeWidgetItem;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool operator<(const QTreeWidgetItem &other) const override;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The ProtocolWidget class
|
* @brief The ProtocolWidget class
|
||||||
* @ingroup gui
|
* @ingroup gui
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
<property name="uniformRowHeights">
|
<property name="uniformRowHeights">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="columnCount">
|
<property name="columnCount">
|
||||||
<number>4</number>
|
<number>4</number>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace OCC {
|
|||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcServerNotification, "gui.servernotification", QtInfoMsg)
|
||||||
|
|
||||||
|
const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications");
|
||||||
|
|
||||||
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
|
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{
|
{
|
||||||
@@ -47,7 +49,7 @@ void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if the previous notification job has finished, start next.
|
// 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,
|
QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived,
|
||||||
this, &ServerNotificationHandler::slotNotificationsReceived);
|
this, &ServerNotificationHandler::slotNotificationsReceived);
|
||||||
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState *>(ptr));
|
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState *>(ptr));
|
||||||
@@ -94,6 +96,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||||||
|
|
||||||
a._links.append(al);
|
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);
|
list.append(a);
|
||||||
}
|
}
|
||||||
emit newNotificationList(list);
|
emit newNotificationList(list);
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ static const float buttonSizeRatio = 1.618; // golden ratio
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
#include "settingsdialogcommon.cpp"
|
||||||
|
|
||||||
static QIcon circleMask(const QImage &avatar)
|
static QIcon circleMask(const QImage &avatar)
|
||||||
{
|
{
|
||||||
int dim = avatar.width();
|
int dim = avatar.width();
|
||||||
@@ -236,7 +238,7 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||||||
|
|
||||||
if (!brandingSingleAccount) {
|
if (!brandingSingleAccount) {
|
||||||
accountAction->setToolTip(s->account()->displayName());
|
accountAction->setToolTip(s->account()->displayName());
|
||||||
accountAction->setIconText(s->shortDisplayNameForSettings(height * buttonSizeRatio));
|
accountAction->setIconText(SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), height * buttonSizeRatio));
|
||||||
}
|
}
|
||||||
_toolBar->insertAction(_toolBar->actions().at(0), accountAction);
|
_toolBar->insertAction(_toolBar->actions().at(0), accountAction);
|
||||||
auto accountSettings = new AccountSettings(s, this);
|
auto accountSettings = new AccountSettings(s, this);
|
||||||
@@ -250,6 +252,10 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||||||
_gui, &ownCloudGui::slotFolderOpenAction);
|
_gui, &ownCloudGui::slotFolderOpenAction);
|
||||||
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList);
|
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList);
|
||||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged);
|
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);
|
slotRefreshActivity(s);
|
||||||
}
|
}
|
||||||
@@ -268,6 +274,20 @@ void SettingsDialog::slotAccountAvatarChanged()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::slotAccountDisplayNameChanged()
|
||||||
|
{
|
||||||
|
Account *account = static_cast<Account *>(sender());
|
||||||
|
if (account && _actionForAccount.contains(account)) {
|
||||||
|
QAction *action = _actionForAccount[account];
|
||||||
|
if (action) {
|
||||||
|
QString displayName = account->displayName();
|
||||||
|
action->setText(displayName);
|
||||||
|
auto height = _toolBar->sizeHint().height();
|
||||||
|
action->setIconText(SettingsDialogCommon::shortDisplayNameForSettings(account, height * buttonSizeRatio));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsDialog::accountRemoved(AccountState *s)
|
void SettingsDialog::accountRemoved(AccountState *s)
|
||||||
{
|
{
|
||||||
for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
|
for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
|
||||||
@@ -379,6 +399,11 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q
|
|||||||
return createActionWithIcon(coloredIcon, text, iconPath);
|
return createActionWithIcon(coloredIcon, text, iconPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::slotRefreshActivityAccountStateSender()
|
||||||
|
{
|
||||||
|
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
|
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
|
||||||
{
|
{
|
||||||
if (accountState) {
|
if (accountState) {
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ public slots:
|
|||||||
void showIssuesList(const QString &folderAlias);
|
void showIssuesList(const QString &folderAlias);
|
||||||
void slotSwitchPage(QAction *action);
|
void slotSwitchPage(QAction *action);
|
||||||
void slotRefreshActivity(AccountState *accountState);
|
void slotRefreshActivity(AccountState *accountState);
|
||||||
|
void slotRefreshActivityAccountStateSender();
|
||||||
void slotAccountAvatarChanged();
|
void slotAccountAvatarChanged();
|
||||||
|
void slotAccountDisplayNameChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void reject() Q_DECL_OVERRIDE;
|
void reject() Q_DECL_OVERRIDE;
|
||||||
|
|||||||
28
src/gui/settingsdialogcommon.cpp
Normal file
28
src/gui/settingsdialogcommon.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
namespace SettingsDialogCommon
|
||||||
|
{
|
||||||
|
|
||||||
|
/** display name with two lines that is displayed in the settings
|
||||||
|
* If width is bigger than 0, the string will be ellided so it does not exceed that width
|
||||||
|
*/
|
||||||
|
QString shortDisplayNameForSettings(Account* account, int width)
|
||||||
|
{
|
||||||
|
QString user = account->davDisplayName();
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
user = account->credentials()->user();
|
||||||
|
}
|
||||||
|
QString host = account->url().host();
|
||||||
|
int port = account->url().port();
|
||||||
|
if (port > 0 && port != 80 && port != 443) {
|
||||||
|
host.append(QLatin1Char(':'));
|
||||||
|
host.append(QString::number(port));
|
||||||
|
}
|
||||||
|
if (width > 0) {
|
||||||
|
QFont f;
|
||||||
|
QFontMetrics fm(f);
|
||||||
|
host = fm.elidedText(host, Qt::ElideMiddle, width);
|
||||||
|
user = fm.elidedText(user, Qt::ElideRight, width);
|
||||||
|
}
|
||||||
|
return user + QLatin1String("\n") + host;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
#include "generalsettings.h"
|
#include "generalsettings.h"
|
||||||
#include "networksettings.h"
|
#include "networksettings.h"
|
||||||
#include "accountsettings.h"
|
#include "accountsettings.h"
|
||||||
|
#include "accountstate.h"
|
||||||
#include "creds/abstractcredentials.h"
|
#include "creds/abstractcredentials.h"
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "progressdispatcher.h"
|
#include "progressdispatcher.h"
|
||||||
@@ -38,6 +39,9 @@
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
#include "settingsdialogcommon.cpp"
|
||||||
|
|
||||||
|
|
||||||
// Duplicate in settingsdialog.cpp
|
// Duplicate in settingsdialog.cpp
|
||||||
static QIcon circleMask(const QImage &avatar)
|
static QIcon circleMask(const QImage &avatar)
|
||||||
{
|
{
|
||||||
@@ -118,6 +122,7 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
|
|||||||
|
|
||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
cfg.restoreGeometry(this);
|
cfg.restoreGeometry(this);
|
||||||
|
_activitySettings->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsDialogMac::closeEvent(QCloseEvent *event)
|
void SettingsDialogMac::closeEvent(QCloseEvent *event)
|
||||||
@@ -146,7 +151,7 @@ void SettingsDialogMac::accountAdded(AccountState *s)
|
|||||||
QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts);
|
QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts);
|
||||||
auto accountSettings = new AccountSettings(s, this);
|
auto accountSettings = new AccountSettings(s, this);
|
||||||
|
|
||||||
QString displayName = Theme::instance()->multiAccount() ? s->shortDisplayNameForSettings() : tr("Account");
|
QString displayName = Theme::instance()->multiAccount() ? SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), 0) : tr("Account");
|
||||||
|
|
||||||
insertPreferencesPanel(0, accountIcon, displayName, accountSettings);
|
insertPreferencesPanel(0, accountIcon, displayName, accountSettings);
|
||||||
|
|
||||||
@@ -154,7 +159,11 @@ void SettingsDialogMac::accountAdded(AccountState *s)
|
|||||||
connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction);
|
connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction);
|
||||||
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialogMac::showIssuesList);
|
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialogMac::showIssuesList);
|
||||||
|
|
||||||
connect(s->account().data(), SIGNAL(accountChangedAvatar()), this, SLOT(slotAccountAvatarChanged()));
|
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);
|
slotRefreshActivity(s);
|
||||||
}
|
}
|
||||||
@@ -171,6 +180,11 @@ void SettingsDialogMac::accountRemoved(AccountState *s)
|
|||||||
_activitySettings->slotRemoveAccount(s);
|
_activitySettings->slotRemoveAccount(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialogMac::slotRefreshActivityAccountStateSender()
|
||||||
|
{
|
||||||
|
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsDialogMac::slotRefreshActivity(AccountState *accountState)
|
void SettingsDialogMac::slotRefreshActivity(AccountState *accountState)
|
||||||
{
|
{
|
||||||
if (accountState) {
|
if (accountState) {
|
||||||
@@ -192,4 +206,23 @@ void SettingsDialogMac::slotAccountAvatarChanged()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialogMac::slotAccountDisplayNameChanged()
|
||||||
|
{
|
||||||
|
Account *account = static_cast<Account *>(sender());
|
||||||
|
auto list = findChildren<AccountSettings *>(QString());
|
||||||
|
foreach (auto p, list) {
|
||||||
|
if (p->accountsState()->account() == account) {
|
||||||
|
int idx = indexForPanel(p);
|
||||||
|
QString displayName = account->displayName();
|
||||||
|
if (!displayName.isNull()) {
|
||||||
|
displayName = Theme::instance()->multiAccount()
|
||||||
|
? SettingsDialogCommon::shortDisplayNameForSettings(account, 0)
|
||||||
|
: tr("Account");
|
||||||
|
setPreferencesPanelTitle(idx, displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,11 +49,13 @@ public slots:
|
|||||||
void showActivityPage();
|
void showActivityPage();
|
||||||
void showIssuesList(const QString &folderAlias);
|
void showIssuesList(const QString &folderAlias);
|
||||||
void slotRefreshActivity(AccountState *accountState);
|
void slotRefreshActivity(AccountState *accountState);
|
||||||
|
void slotRefreshActivityAccountStateSender();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void accountAdded(AccountState *);
|
void accountAdded(AccountState *);
|
||||||
void accountRemoved(AccountState *);
|
void accountRemoved(AccountState *);
|
||||||
void slotAccountAvatarChanged();
|
void slotAccountAvatarChanged();
|
||||||
|
void slotAccountDisplayNameChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void closeEvent(QCloseEvent *event);
|
void closeEvent(QCloseEvent *event);
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||||||
job->setProperties(
|
job->setProperties(
|
||||||
QList<QByteArray>()
|
QList<QByteArray>()
|
||||||
<< "http://open-collaboration-services.org/ns:share-permissions"
|
<< "http://open-collaboration-services.org/ns:share-permissions"
|
||||||
|
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
|
||||||
<< "http://owncloud.org/ns:privatelink");
|
<< "http://owncloud.org/ns:privatelink");
|
||||||
job->setTimeout(10 * 1000);
|
job->setTimeout(10 * 1000);
|
||||||
connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
|
connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
|
||||||
@@ -160,9 +161,13 @@ void ShareDialog::slotPropfindReceived(const QVariantMap &result)
|
|||||||
qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
|
qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
|
||||||
}
|
}
|
||||||
auto privateLinkUrl = result["privatelink"].toString();
|
auto privateLinkUrl = result["privatelink"].toString();
|
||||||
|
auto numericFileId = result["fileid"].toByteArray();
|
||||||
if (!privateLinkUrl.isEmpty()) {
|
if (!privateLinkUrl.isEmpty()) {
|
||||||
qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl;
|
qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl;
|
||||||
_privateLinkUrl = privateLinkUrl;
|
_privateLinkUrl = privateLinkUrl;
|
||||||
|
} else if (!numericFileId.isEmpty()) {
|
||||||
|
qCInfo(lcSharing) << "Received numeric file id for" << _sharePath << numericFileId;
|
||||||
|
_privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
showSharingUi();
|
showSharingUi();
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
//Is this a file or folder?
|
//Is this a file or folder?
|
||||||
QFileInfo fi(localPath);
|
QFileInfo fi(localPath);
|
||||||
_isFile = fi.isFile();
|
_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
|
// the following progress indicator widgets are added to layouts which makes them
|
||||||
// automatically deleted once the dialog dies.
|
// automatically deleted once the dialog dies.
|
||||||
@@ -155,14 +157,15 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
|
|
||||||
// Prepare sharing menu
|
// Prepare sharing menu
|
||||||
|
|
||||||
_shareLinkMenu = new QMenu(this);
|
_linkContextMenu = new QMenu(this);
|
||||||
connect(_shareLinkMenu, &QMenu::triggered,
|
connect(_linkContextMenu, &QMenu::triggered,
|
||||||
this, &ShareLinkWidget::slotShareLinkActionTriggered);
|
this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
|
||||||
_openLinkAction = _shareLinkMenu->addAction(tr("Open link in browser"));
|
_openLinkAction = _linkContextMenu->addAction(tr("Open link in browser"));
|
||||||
_copyLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard"));
|
_copyLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard"));
|
||||||
_copyDirectLinkAction = _shareLinkMenu->addAction(tr("Copy link to clipboard (direct download)"));
|
_copyDirectLinkAction = _linkContextMenu->addAction(tr("Copy link to clipboard (direct download)"));
|
||||||
_emailLinkAction = _shareLinkMenu->addAction(tr("Send link by email"));
|
_emailLinkAction = _linkContextMenu->addAction(tr("Send link by email"));
|
||||||
_emailDirectLinkAction = _shareLinkMenu->addAction(tr("Send link by email (direct download)"));
|
_emailDirectLinkAction = _linkContextMenu->addAction(tr("Send link by email (direct download)"));
|
||||||
|
_deleteLinkAction = _linkContextMenu->addAction(tr("Delete"));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create the share manager and connect it properly
|
* Create the share manager and connect it properly
|
||||||
@@ -220,39 +223,34 @@ void ShareLinkWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shar
|
|||||||
// Connect all shares signals to gui slots
|
// Connect all shares signals to gui slots
|
||||||
connect(share.data(), &Share::serverError, this, &ShareLinkWidget::slotServerError);
|
connect(share.data(), &Share::serverError, this, &ShareLinkWidget::slotServerError);
|
||||||
connect(share.data(), &Share::shareDeleted, this, &ShareLinkWidget::slotDeleteShareFetched);
|
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(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
|
// Build the table row
|
||||||
auto row = table->rowCount();
|
auto row = table->rowCount();
|
||||||
table->insertRow(row);
|
table->insertRow(row);
|
||||||
|
|
||||||
auto nameItem = new QTableWidgetItem;
|
auto nameItem = new QTableWidgetItem;
|
||||||
QString name = linkShare->getName();
|
auto name = shareName(*linkShare);
|
||||||
if (name.isEmpty()) {
|
if (!_namesSupported) {
|
||||||
if (!_namesSupported) {
|
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
|
||||||
name = tr("Public link");
|
|
||||||
nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
|
|
||||||
} else {
|
|
||||||
name = linkShare->getToken();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
nameItem->setText(name);
|
nameItem->setText(name);
|
||||||
nameItem->setData(Qt::UserRole, QVariant::fromValue(linkShare));
|
nameItem->setData(Qt::UserRole, QVariant::fromValue(linkShare));
|
||||||
table->setItem(row, 0, nameItem);
|
table->setItem(row, 0, nameItem);
|
||||||
|
|
||||||
auto shareButton = new QToolButton;
|
auto dotdotdotButton = new QToolButton;
|
||||||
shareButton->setText("...");
|
dotdotdotButton->setText("...");
|
||||||
shareButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
dotdotdotButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
||||||
connect(shareButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotShareLinkButtonClicked);
|
connect(dotdotdotButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotContextMenuButtonClicked);
|
||||||
table->setCellWidget(row, 1, shareButton);
|
table->setCellWidget(row, 1, dotdotdotButton);
|
||||||
|
|
||||||
auto deleteButton = new QToolButton;
|
auto deleteButton = new QToolButton;
|
||||||
deleteButton->setIcon(deleteIcon);
|
deleteButton->setIcon(deleteIcon);
|
||||||
deleteButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
deleteButton->setProperty(propertyShareC, QVariant::fromValue(linkShare));
|
||||||
|
deleteButton->setToolTip(tr("Delete link share"));
|
||||||
connect(deleteButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotDeleteShareClicked);
|
connect(deleteButton, &QAbstractButton::clicked, this, &ShareLinkWidget::slotDeleteShareClicked);
|
||||||
table->setCellWidget(row, 2, deleteButton);
|
table->setCellWidget(row, 2, deleteButton);
|
||||||
|
|
||||||
@@ -514,22 +512,56 @@ void ShareLinkWidget::openShareLink(const QUrl &url)
|
|||||||
Utility::openBrowser(url, this);
|
Utility::openBrowser(url, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::slotShareLinkButtonClicked()
|
void ShareLinkWidget::confirmAndDeleteShare(const QSharedPointer<LinkShare> &share)
|
||||||
|
{
|
||||||
|
auto messageBox = new QMessageBox(
|
||||||
|
QMessageBox::Question,
|
||||||
|
tr("Confirm Link Share Deletion"),
|
||||||
|
tr("<p>Do you really want to delete the public link share <i>%1</i>?</p>"
|
||||||
|
"<p>Note: This action cannot be undone.</p>")
|
||||||
|
.arg(shareName(*share)),
|
||||||
|
QMessageBox::NoButton,
|
||||||
|
this);
|
||||||
|
QPushButton *yesButton =
|
||||||
|
messageBox->addButton(tr("Delete"), QMessageBox::YesRole);
|
||||||
|
messageBox->addButton(tr("Cancel"), QMessageBox::NoRole);
|
||||||
|
|
||||||
|
connect(messageBox, &QMessageBox::finished, this,
|
||||||
|
[messageBox, yesButton, share]() {
|
||||||
|
if (messageBox->clickedButton() == yesButton)
|
||||||
|
share->deleteShare();
|
||||||
|
});
|
||||||
|
messageBox->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ShareLinkWidget::shareName(const LinkShare &share) const
|
||||||
|
{
|
||||||
|
QString name = share.getName();
|
||||||
|
if (!name.isEmpty())
|
||||||
|
return name;
|
||||||
|
if (!_namesSupported)
|
||||||
|
return tr("Public link");
|
||||||
|
return share.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShareLinkWidget::slotContextMenuButtonClicked()
|
||||||
{
|
{
|
||||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||||
bool downloadEnabled = share->getShowFileListing();
|
bool downloadEnabled = share->getShowFileListing();
|
||||||
_copyDirectLinkAction->setVisible(downloadEnabled);
|
_copyDirectLinkAction->setVisible(downloadEnabled);
|
||||||
_emailDirectLinkAction->setVisible(downloadEnabled);
|
_emailDirectLinkAction->setVisible(downloadEnabled);
|
||||||
|
|
||||||
_shareLinkMenu->setProperty(propertyShareC, QVariant::fromValue(share));
|
_linkContextMenu->setProperty(propertyShareC, QVariant::fromValue(share));
|
||||||
_shareLinkMenu->exec(QCursor::pos());
|
_linkContextMenu->exec(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::slotShareLinkActionTriggered(QAction *action)
|
void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
||||||
{
|
{
|
||||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||||
|
|
||||||
if (action == _copyLinkAction) {
|
if (action == _deleteLinkAction) {
|
||||||
|
confirmAndDeleteShare(share);
|
||||||
|
} else if (action == _copyLinkAction) {
|
||||||
QApplication::clipboard()->setText(share->getLink().toString());
|
QApplication::clipboard()->setText(share->getLink().toString());
|
||||||
} else if (action == _copyDirectLinkAction) {
|
} else if (action == _copyDirectLinkAction) {
|
||||||
QApplication::clipboard()->setText(share->getDirectDownloadLink().toString());
|
QApplication::clipboard()->setText(share->getDirectDownloadLink().toString());
|
||||||
@@ -545,7 +577,7 @@ void ShareLinkWidget::slotShareLinkActionTriggered(QAction *action)
|
|||||||
void ShareLinkWidget::slotDeleteShareClicked()
|
void ShareLinkWidget::slotDeleteShareClicked()
|
||||||
{
|
{
|
||||||
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
auto share = sender()->property(propertyShareC).value<QSharedPointer<LinkShare>>();
|
||||||
share->deleteShare();
|
confirmAndDeleteShare(share);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::slotPermissionsCheckboxClicked()
|
void ShareLinkWidget::slotPermissionsCheckboxClicked()
|
||||||
|
|||||||
@@ -70,8 +70,8 @@ private slots:
|
|||||||
void slotPasswordChanged(const QString &newText);
|
void slotPasswordChanged(const QString &newText);
|
||||||
void slotNameEdited(QTableWidgetItem *item);
|
void slotNameEdited(QTableWidgetItem *item);
|
||||||
|
|
||||||
void slotShareLinkButtonClicked();
|
void slotContextMenuButtonClicked();
|
||||||
void slotShareLinkActionTriggered(QAction *action);
|
void slotLinkContextMenuActionTriggered(QAction *action);
|
||||||
|
|
||||||
void slotDeleteShareFetched();
|
void slotDeleteShareFetched();
|
||||||
void slotCreateShareFetched(const QSharedPointer<LinkShare> &share);
|
void slotCreateShareFetched(const QSharedPointer<LinkShare> &share);
|
||||||
@@ -93,6 +93,12 @@ private:
|
|||||||
void emailShareLink(const QUrl &url);
|
void emailShareLink(const QUrl &url);
|
||||||
void openShareLink(const QUrl &url);
|
void openShareLink(const QUrl &url);
|
||||||
|
|
||||||
|
/** Confirm with the user and then delete the share */
|
||||||
|
void confirmAndDeleteShare(const QSharedPointer<LinkShare> &share);
|
||||||
|
|
||||||
|
/** Retrieve a share's name, accounting for _namesSupported */
|
||||||
|
QString shareName(const LinkShare &share) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the selected share, returning 0 if none.
|
* Retrieve the selected share, returning 0 if none.
|
||||||
*/
|
*/
|
||||||
@@ -120,12 +126,13 @@ private:
|
|||||||
// the next time getShares() finishes. This stores its id.
|
// the next time getShares() finishes. This stores its id.
|
||||||
QString _newShareOverrideSelectionId;
|
QString _newShareOverrideSelectionId;
|
||||||
|
|
||||||
QMenu *_shareLinkMenu;
|
QMenu *_linkContextMenu = nullptr;
|
||||||
QAction *_openLinkAction;
|
QAction *_deleteLinkAction = nullptr;
|
||||||
QAction *_copyLinkAction;
|
QAction *_openLinkAction = nullptr;
|
||||||
QAction *_copyDirectLinkAction;
|
QAction *_copyLinkAction = nullptr;
|
||||||
QAction *_emailLinkAction;
|
QAction *_copyDirectLinkAction = nullptr;
|
||||||
QAction *_emailDirectLinkAction;
|
QAction *_emailLinkAction = nullptr;
|
||||||
|
QAction *_emailDirectLinkAction = nullptr;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,25 +15,41 @@
|
|||||||
#include "sharemanager.h"
|
#include "sharemanager.h"
|
||||||
#include "ocssharejob.h"
|
#include "ocssharejob.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
#include "accountstate.h"
|
||||||
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct CreateShare
|
|
||||||
{
|
|
||||||
QString path;
|
|
||||||
OCC::Share::ShareType shareType;
|
|
||||||
QString shareWith;
|
|
||||||
OCC::Share::Permissions permissions;
|
|
||||||
};
|
|
||||||
} // anonymous namespace
|
|
||||||
Q_DECLARE_METATYPE(CreateShare)
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a share is modified, we need to tell the folders so they can adjust overlay icons
|
||||||
|
*/
|
||||||
|
static void updateFolder(const AccountPtr &account, const QString &path)
|
||||||
|
{
|
||||||
|
foreach (Folder *f, FolderMan::instance()->map()) {
|
||||||
|
if (f->accountState()->account() != account)
|
||||||
|
continue;
|
||||||
|
auto folderPath = f->remotePath();
|
||||||
|
if (path.startsWith(folderPath) && (path == folderPath || folderPath.endsWith('/') || path[folderPath.size()] == '/')) {
|
||||||
|
// Workaround the fact that the server does not invalidate the etags of parent directories
|
||||||
|
// when something is shared.
|
||||||
|
auto relative = path.midRef(folderPath.size());
|
||||||
|
if (relative.startsWith('/'))
|
||||||
|
relative = relative.mid(1);
|
||||||
|
f->journalDb()->avoidReadFromDbOnNextSync(relative.toString());
|
||||||
|
|
||||||
|
// Schedule a sync so it can update the remote permission flag and let the socket API
|
||||||
|
// know about the shared icon.
|
||||||
|
f->scheduleThisFolderSoon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Share::Share(AccountPtr account,
|
Share::Share(AccountPtr account,
|
||||||
const QString &id,
|
const QString &id,
|
||||||
const QString &path,
|
const QString &path,
|
||||||
@@ -54,6 +70,11 @@ AccountPtr Share::account() const
|
|||||||
return _account;
|
return _account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Share::path() const
|
||||||
|
{
|
||||||
|
return _path;
|
||||||
|
}
|
||||||
|
|
||||||
QString Share::getId() const
|
QString Share::getId() const
|
||||||
{
|
{
|
||||||
return _id;
|
return _id;
|
||||||
@@ -99,6 +120,8 @@ void Share::deleteShare()
|
|||||||
void Share::slotDeleted()
|
void Share::slotDeleted()
|
||||||
{
|
{
|
||||||
emit shareDeleted();
|
emit shareDeleted();
|
||||||
|
|
||||||
|
updateFolder(_account, _path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Share::slotOcsError(int statusCode, const QString &message)
|
void Share::slotOcsError(int statusCode, const QString &message)
|
||||||
@@ -258,60 +281,46 @@ void ShareManager::slotLinkShareCreated(const QJsonDocument &reply)
|
|||||||
QSharedPointer<LinkShare> share(parseLinkShare(data));
|
QSharedPointer<LinkShare> share(parseLinkShare(data));
|
||||||
|
|
||||||
emit linkShareCreated(share);
|
emit linkShareCreated(share);
|
||||||
|
|
||||||
|
updateFolder(_account, share->path());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ShareManager::createShare(const QString &path,
|
void ShareManager::createShare(const QString &path,
|
||||||
const Share::ShareType shareType,
|
const Share::ShareType shareType,
|
||||||
const QString shareWith,
|
const QString shareWith,
|
||||||
const Share::Permissions permissions)
|
const Share::Permissions desiredPermissions)
|
||||||
{
|
{
|
||||||
auto job = new OcsShareJob(_account);
|
auto job = new OcsShareJob(_account);
|
||||||
|
|
||||||
// Store values that we need for creating this share later.
|
|
||||||
CreateShare continuation;
|
|
||||||
continuation.path = path;
|
|
||||||
continuation.shareType = shareType;
|
|
||||||
continuation.shareWith = shareWith;
|
|
||||||
continuation.permissions = permissions;
|
|
||||||
_jobContinuation[job] = QVariant::fromValue(continuation);
|
|
||||||
|
|
||||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotCreateShare);
|
|
||||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
||||||
|
connect(job, &OcsShareJob::shareJobFinished, this,
|
||||||
|
[=](const QJsonDocument &reply) {
|
||||||
|
// Find existing share permissions (if this was shared with us)
|
||||||
|
Share::Permissions existingPermissions = SharePermissionDefault;
|
||||||
|
foreach (const QJsonValue &element, reply.object()["ocs"].toObject()["data"].toArray()) {
|
||||||
|
auto map = element.toObject();
|
||||||
|
if (map["file_target"] == path)
|
||||||
|
existingPermissions = Share::Permissions(map["permissions"].toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit the permissions we request for a share to the ones the item
|
||||||
|
// was shared with initially.
|
||||||
|
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, validPermissions);
|
||||||
|
});
|
||||||
job->getSharedWithMe();
|
job->getSharedWithMe();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareManager::slotCreateShare(const QJsonDocument &reply)
|
|
||||||
{
|
|
||||||
if (!_jobContinuation.contains(sender()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
CreateShare cont = _jobContinuation[sender()].value<CreateShare>();
|
|
||||||
if (cont.path.isEmpty())
|
|
||||||
return;
|
|
||||||
_jobContinuation.remove(sender());
|
|
||||||
|
|
||||||
// Find existing share permissions (if this was shared with us)
|
|
||||||
Share::Permissions existingPermissions = SharePermissionDefault;
|
|
||||||
foreach (const QJsonValue &element, reply.object()["ocs"].toObject()["data"].toArray()) {
|
|
||||||
auto map = element.toObject();
|
|
||||||
if (map["file_target"] == cont.path)
|
|
||||||
existingPermissions = Share::Permissions(map["permissions"].toInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit the permissions we request for a share to the ones the item
|
|
||||||
// was shared with initially.
|
|
||||||
if (cont.permissions == SharePermissionDefault) {
|
|
||||||
cont.permissions = existingPermissions;
|
|
||||||
} else if (existingPermissions != SharePermissionDefault) {
|
|
||||||
cont.permissions &= existingPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
OcsShareJob *job = new OcsShareJob(_account);
|
|
||||||
connect(job, &OcsShareJob::shareJobFinished, this, &ShareManager::slotShareCreated);
|
|
||||||
connect(job, &OcsJob::ocsError, this, &ShareManager::slotOcsError);
|
|
||||||
job->createShare(cont.path, cont.shareType, cont.shareWith, cont.permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareManager::slotShareCreated(const QJsonDocument &reply)
|
void ShareManager::slotShareCreated(const QJsonDocument &reply)
|
||||||
{
|
{
|
||||||
@@ -320,6 +329,8 @@ void ShareManager::slotShareCreated(const QJsonDocument &reply)
|
|||||||
QSharedPointer<Share> share(parseShare(data));
|
QSharedPointer<Share> share(parseShare(data));
|
||||||
|
|
||||||
emit shareCreated(share);
|
emit shareCreated(share);
|
||||||
|
|
||||||
|
updateFolder(_account, share->path());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareManager::fetchShares(const QString &path)
|
void ShareManager::fetchShares(const QString &path)
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
AccountPtr account() const;
|
AccountPtr account() const;
|
||||||
|
|
||||||
|
QString path() const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get the id
|
* Get the id
|
||||||
*/
|
*/
|
||||||
@@ -293,13 +295,10 @@ private slots:
|
|||||||
void slotLinkShareCreated(const QJsonDocument &reply);
|
void slotLinkShareCreated(const QJsonDocument &reply);
|
||||||
void slotShareCreated(const QJsonDocument &reply);
|
void slotShareCreated(const QJsonDocument &reply);
|
||||||
void slotOcsError(int statusCode, const QString &message);
|
void slotOcsError(int statusCode, const QString &message);
|
||||||
void slotCreateShare(const QJsonDocument &reply);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<LinkShare> parseLinkShare(const QJsonObject &data);
|
QSharedPointer<LinkShare> parseLinkShare(const QJsonObject &data);
|
||||||
QSharedPointer<Share> parseShare(const QJsonObject &data);
|
QSharedPointer<Share> parseShare(const QJsonObject &data);
|
||||||
|
|
||||||
QMap<QObject *, QVariant> _jobContinuation;
|
|
||||||
AccountPtr _account;
|
AccountPtr _account;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,10 @@ SocketApi::SocketApi(QObject *parent)
|
|||||||
// Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
|
// Example for developer builds (with ad-hoc signing identity): "" "com.owncloud.desktopclient" ".socketApi"
|
||||||
// Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
|
// Example for official signed packages: "9B5WD74GWJ." "com.owncloud.desktopclient" ".socketApi"
|
||||||
socketPath = SOCKETAPI_TEAM_IDENTIFIER_PREFIX APPLICATION_REV_DOMAIN ".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()) {
|
} else if (Utility::isLinux() || Utility::isBSD()) {
|
||||||
QString runtimeDir;
|
QString runtimeDir;
|
||||||
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
|
||||||
@@ -220,6 +224,11 @@ SocketApi::~SocketApi()
|
|||||||
// All remaining sockets will be destroyed with _localServer, their parent
|
// All remaining sockets will be destroyed with _localServer, their parent
|
||||||
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
|
ASSERT(_listeners.isEmpty() || _listeners.first().socket->parent() == &_localServer);
|
||||||
_listeners.clear();
|
_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()
|
void SocketApi::slotNewConnection()
|
||||||
@@ -503,27 +512,29 @@ void fetchPrivateLinkUrl(const QString &localFile, SocketApi *target, void (Sock
|
|||||||
const QString localFileClean = QDir::cleanPath(localFile);
|
const QString localFileClean = QDir::cleanPath(localFile);
|
||||||
const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
|
const QString file = localFileClean.mid(shareFolder->cleanPath().length() + 1);
|
||||||
|
|
||||||
|
AccountPtr account = shareFolder->accountState()->account();
|
||||||
|
|
||||||
// Generate private link ourselves: used as a fallback
|
// Generate private link ourselves: used as a fallback
|
||||||
SyncJournalFileRecord rec;
|
SyncJournalFileRecord rec;
|
||||||
if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
|
if (!shareFolder->journalDb()->getFileRecord(file, &rec) || !rec.isValid())
|
||||||
return;
|
return;
|
||||||
const QString oldUrl =
|
const QString oldUrl =
|
||||||
shareFolder->accountState()->account()->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded);
|
account->deprecatedPrivateLinkUrl(rec.numericFileId()).toString(QUrl::FullyEncoded);
|
||||||
|
|
||||||
// If the server doesn't have the property, use the old url directly.
|
// Retrieve the new link or numeric file id by PROPFIND
|
||||||
if (!shareFolder->accountState()->account()->capabilities().privateLinkPropertyAvailable()) {
|
PropfindJob *job = new PropfindJob(account, file, target);
|
||||||
(target->*targetFun)(oldUrl);
|
job->setProperties(
|
||||||
return;
|
QList<QByteArray>()
|
||||||
}
|
<< "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
|
||||||
|
<< "http://owncloud.org/ns:privatelink");
|
||||||
// Retrieve the new link by PROPFIND
|
|
||||||
PropfindJob *job = new PropfindJob(shareFolder->accountState()->account(), file, target);
|
|
||||||
job->setProperties(QList<QByteArray>() << "http://owncloud.org/ns:privatelink");
|
|
||||||
job->setTimeout(10 * 1000);
|
job->setTimeout(10 * 1000);
|
||||||
QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) {
|
QObject::connect(job, &PropfindJob::result, target, [=](const QVariantMap &result) {
|
||||||
auto privateLinkUrl = result["privatelink"].toString();
|
auto privateLinkUrl = result["privatelink"].toString();
|
||||||
|
auto numericFileId = result["fileid"].toByteArray();
|
||||||
if (!privateLinkUrl.isEmpty()) {
|
if (!privateLinkUrl.isEmpty()) {
|
||||||
(target->*targetFun)(privateLinkUrl);
|
(target->*targetFun)(privateLinkUrl);
|
||||||
|
} else if (!numericFileId.isEmpty()) {
|
||||||
|
(target->*targetFun)(account->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded));
|
||||||
} else {
|
} else {
|
||||||
(target->*targetFun)(oldUrl);
|
(target->*targetFun)(oldUrl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,10 @@ void SslButton::slotUpdateMenu()
|
|||||||
|
|
||||||
AccountPtr account = _accountState->account();
|
AccountPtr account = _accountState->account();
|
||||||
|
|
||||||
|
if (account->isHttp2Supported()) {
|
||||||
|
_menu->addAction("HTTP/2")->setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
if (account->url().scheme() == QLatin1String("https")) {
|
if (account->url().scheme() == QLatin1String("https")) {
|
||||||
QString sslVersion = account->_sessionCipher.protocolString()
|
QString sslVersion = account->_sessionCipher.protocolString()
|
||||||
+ ", " + account->_sessionCipher.authenticationMethod()
|
+ ", " + account->_sessionCipher.authenticationMethod()
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ OCUpdater::OCUpdater(const QUrl &url)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OCUpdater::setUpdateUrl(const QUrl &url)
|
||||||
|
{
|
||||||
|
_updateUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
bool OCUpdater::performUpdate()
|
bool OCUpdater::performUpdate()
|
||||||
{
|
{
|
||||||
ConfigFile cfg;
|
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()
|
void OCUpdater::slotStartInstaller()
|
||||||
{
|
{
|
||||||
ConfigFile cfg;
|
ConfigFile cfg;
|
||||||
@@ -187,8 +267,30 @@ void OCUpdater::slotStartInstaller()
|
|||||||
settings.setValue(autoUpdateAttemptedC, true);
|
settings.setValue(autoUpdateAttemptedC, true);
|
||||||
settings.sync();
|
settings.sync();
|
||||||
qCInfo(lcUpdater) << "Running updater" << updateFile;
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
QString 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()
|
void OCUpdater::checkForUpdate()
|
||||||
@@ -298,7 +400,7 @@ void NSISUpdater::versionInfoArrived(const UpdateInfo &info)
|
|||||||
showDialog(info);
|
showDialog(info);
|
||||||
}
|
}
|
||||||
if (!url.isEmpty()) {
|
if (!url.isEmpty()) {
|
||||||
_targetFile = cfg.configPath() + url.mid(url.lastIndexOf('/'));
|
_targetFile = cfg.configPath() + url.mid(url.lastIndexOf('/')+1);
|
||||||
if (QFile(_targetFile).exists()) {
|
if (QFile(_targetFile).exists()) {
|
||||||
setDownloadState(DownloadComplete);
|
setDownloadState(DownloadComplete);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ public:
|
|||||||
UpdateOnlyAvailableThroughSystem };
|
UpdateOnlyAvailableThroughSystem };
|
||||||
explicit OCUpdater(const QUrl &url);
|
explicit OCUpdater(const QUrl &url);
|
||||||
|
|
||||||
|
void setUpdateUrl(const QUrl &url);
|
||||||
|
|
||||||
bool performUpdate();
|
bool performUpdate();
|
||||||
|
|
||||||
void checkForUpdate() Q_DECL_OVERRIDE;
|
void checkForUpdate() Q_DECL_OVERRIDE;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user